'null']); }); it('allForUser includes a for_you key', function () { $user = User::factory()->create(); Cache::flush(); $service = app(HomepageService::class); $result = $service->allForUser($user); expect($result)->toHaveKey('for_you') ->and($result['for_you'])->toBeArray(); }); it('allForUser for_you is an array even with no cached recommendations', function () { $user = User::factory()->create(); $service = app(HomepageService::class); Cache::flush(); $result = $service->allForUser($user); expect($result['for_you'])->toBeArray(); }); it('allForUser for_you returns items when cache exists', function () { $user = User::factory()->create(); $artwork = Artwork::factory()->create([ 'is_public' => true, 'is_approved' => true, 'published_at' => now()->subHour(), ]); 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(60), ]); Cache::flush(); $service = app(HomepageService::class); $result = $service->allForUser($user); expect($result['for_you'])->toBeArray(); // At least one item should have the base shape (id, title, slug, url) if (count($result['for_you']) > 0) { expect($result['for_you'][0])->toHaveKeys(['id', 'title', 'slug', 'url']); } }); it('allForUser for_you uses group publisher payload for group-published artworks', function () { $user = User::factory()->create(); $owner = User::factory()->create(); $group = Group::factory()->create([ 'owner_user_id' => $owner->id, 'name' => 'Nova Group', 'slug' => 'nova-group', ]); $artwork = Artwork::factory()->create([ 'user_id' => $owner->id, 'group_id' => $group->id, 'published_as_type' => Artwork::PUBLISHED_AS_GROUP, 'title' => 'Group Recommended Artwork', 'hash' => 'grouprecommendedartwork', 'thumb_ext' => 'webp', 'is_public' => true, 'is_approved' => true, 'published_at' => now()->subHour(), ]); 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.91, 'source' => 'profile'], ], ], 'generated_at' => now(), 'expires_at' => now()->addMinutes(60), ]); Cache::flush(); $result = app(HomepageService::class)->allForUser($user); expect($result['for_you'])->not->toBeEmpty() ->and($result['for_you'][0]['author'])->toBe('Nova Group') ->and($result['for_you'][0]['published_as_type'])->toBe(Artwork::PUBLISHED_AS_GROUP) ->and($result['for_you'][0]['publisher']['type'])->toBe('group') ->and($result['for_you'][0]['publisher']['name'])->toBe('Nova Group'); });