'null']); }); // ───────────────────────────────────────────────────────────────────────────── // /discover/for-you // ───────────────────────────────────────────────────────────────────────────── it('redirects guests away from /discover/for-you', function () { $this->get('/discover/for-you') ->assertRedirect(); }); it('renders For You page for authenticated user', function () { $user = User::factory()->create(); $this->actingAs($user) ->get('/discover/for-you') ->assertOk(); }); it('For You page shows empty state with no prior activity', function () { $user = User::factory()->create(); $this->actingAs($user) ->get('/discover/for-you') ->assertOk() ->assertSee('For You'); }); it('For You page uses cached recommendations when available', function () { $user = User::factory()->create(); $artwork = Artwork::factory()->create([ 'is_public' => true, 'is_approved' => true, 'published_at' => now()->subMinutes(5), ]); UserRecommendationCache::query()->create([ 'user_id' => $user->id, 'algo_version' => (string) config('discovery.algo_version', 'clip-cosine-v1'), 'cache_version' => (string) config('discovery.cache_version', 'cache-v1'), 'recommendations_json' => [ 'items' => [ ['artwork_id' => $artwork->id, 'score' => 0.9, 'source' => 'profile'], ], ], 'generated_at' => now(), 'expires_at' => now()->addMinutes(30), ]); $this->actingAs($user) ->get('/discover/for-you') ->assertOk(); }); // ───────────────────────────────────────────────────────────────────────────── // /api/user/suggestions/creators // ───────────────────────────────────────────────────────────────────────────── it('requires auth for suggested creators endpoint', function () { $this->getJson('/api/user/suggestions/creators') ->assertUnauthorized(); }); it('returns data array from suggested creators endpoint', function () { $user = User::factory()->create(); $this->actingAs($user) ->getJson('/api/user/suggestions/creators') ->assertOk() ->assertJsonStructure(['data']); }); it('suggested creators does not include the requesting user', function () { $user = User::factory()->create(); $response = $this->actingAs($user) ->getJson('/api/user/suggestions/creators') ->assertOk(); $ids = collect($response->json('data'))->pluck('id')->all(); expect($ids)->not->toContain($user->id); }); it('suggested creators excludes already-followed creators', function () { $user = User::factory()->create(); $followed = User::factory()->create(); UserFollower::create([ 'user_id' => $followed->id, 'follower_id' => $user->id, ]); $response = $this->actingAs($user) ->getJson('/api/user/suggestions/creators') ->assertOk(); $ids = collect($response->json('data'))->pluck('id')->all(); expect($ids)->not->toContain($followed->id); }); // ───────────────────────────────────────────────────────────────────────────── // /api/user/suggestions/tags // ───────────────────────────────────────────────────────────────────────────── it('requires auth for suggested tags endpoint', function () { $this->getJson('/api/user/suggestions/tags') ->assertUnauthorized(); }); it('returns data array from suggested tags endpoint', function () { $user = User::factory()->create(); $this->actingAs($user) ->getJson('/api/user/suggestions/tags') ->assertOk() ->assertJsonStructure(['data']); }); it('suggested tags returns correct shape', function () { $user = User::factory()->create(); $response = $this->actingAs($user) ->getJson('/api/user/suggestions/tags') ->assertOk(); $data = $response->json('data'); expect($data)->toBeArray(); // If non-empty each item must have these keys foreach ($data as $item) { expect($item)->toHaveKeys(['id', 'name', 'slug', 'usage_count', 'source']); } }); // ───────────────────────────────────────────────────────────────────────────── // Similar artworks cache TTL // ───────────────────────────────────────────────────────────────────────────── it('similar artworks endpoint returns 200 for a valid public artwork', function () { $artwork = Artwork::factory()->create([ 'is_public' => true, 'is_approved' => true, 'published_at' => now()->subDay(), ]); $this->getJson("/api/art/{$artwork->id}/similar") ->assertOk() ->assertJsonStructure(['data']); }); it('similar artworks response is cached (second call hits cache layer)', function () { $artwork = Artwork::factory()->create([ 'is_public' => true, 'is_approved' => true, 'published_at' => now()->subDay(), ]); // Two consecutive calls – the second must also succeed (confirming cache does not corrupt) $this->getJson("/api/art/{$artwork->id}/similar")->assertOk(); $this->getJson("/api/art/{$artwork->id}/similar")->assertOk(); });