create(['role' => 'admin']); $sourceA = Artwork::factory()->create(['title' => 'Source A']); $similarA = Artwork::factory()->create(['title' => 'Similar A']); $sourceB = Artwork::factory()->create(['title' => 'Source B']); $similarB = Artwork::factory()->create(['title' => 'Similar B']); $inRangeDate = now()->subDay()->toDateString(); $outRangeDate = now()->subDays(5)->toDateString(); DB::table('similar_artwork_events')->insert([ [ 'event_date' => $inRangeDate, 'event_type' => 'impression', 'algo_version' => 'clip-cosine-v1', 'source_artwork_id' => $sourceA->id, 'similar_artwork_id' => $similarA->id, 'position' => 1, 'items_count' => 4, 'occurred_at' => now()->subDay(), 'created_at' => now(), 'updated_at' => now(), ], [ 'event_date' => $inRangeDate, 'event_type' => 'click', 'algo_version' => 'clip-cosine-v1', 'source_artwork_id' => $sourceA->id, 'similar_artwork_id' => $similarA->id, 'position' => 1, 'items_count' => null, 'occurred_at' => now()->subDay(), 'created_at' => now(), 'updated_at' => now(), ], [ 'event_date' => $inRangeDate, 'event_type' => 'impression', 'algo_version' => 'clip-cosine-v2', 'source_artwork_id' => $sourceB->id, 'similar_artwork_id' => $similarB->id, 'position' => 2, 'items_count' => 4, 'occurred_at' => now()->subDay(), 'created_at' => now(), 'updated_at' => now(), ], [ 'event_date' => $outRangeDate, 'event_type' => 'click', 'algo_version' => 'clip-cosine-v1', 'source_artwork_id' => $sourceA->id, 'similar_artwork_id' => $similarA->id, 'position' => 1, 'items_count' => null, 'occurred_at' => now()->subDays(5), 'created_at' => now(), 'updated_at' => now(), ], ]); $response = $this->actingAs($admin)->getJson('/api/admin/reports/similar-artworks?from=' . $inRangeDate . '&to=' . $inRangeDate); $response->assertOk(); $response->assertJsonPath('meta.from', $inRangeDate); $response->assertJsonPath('meta.to', $inRangeDate); $byAlgo = collect($response->json('by_algo_version')); expect($byAlgo->count())->toBe(2); $v1 = $byAlgo->firstWhere('algo_version', 'clip-cosine-v1'); expect($v1['impressions'])->toBe(1); expect($v1['clicks'])->toBe(1); expect((float) $v1['ctr'])->toBe(1.0); $top = collect($response->json('top_similarities')); expect($top->isNotEmpty())->toBeTrue(); expect($top->first()['source_artwork_id'])->toBe($sourceA->id); expect($top->first()['similar_artwork_id'])->toBe($similarA->id); expect((float) $top->first()['ctr'])->toBe(1.0); }); it('non-admin is denied similar artwork report endpoint', function () { $user = User::factory()->create(['role' => 'user']); $response = $this->actingAs($user)->getJson('/api/admin/reports/similar-artworks'); $response->assertStatus(403); });