instance(VectorService::class, new VectorService( new AiArtworkVectorSearchService(new VectorGatewayClient(), $imageUrl), new ArtworkVectorIndexService(new VectorGatewayClient(), $imageUrl, new ArtworkVectorMetadataService()), )); } it('indexes artworks by latest updated_at descending by default', function (): void { $user = User::factory()->create(); $oldest = Artwork::factory()->for($user)->create([ 'title' => 'Oldest artwork', 'slug' => 'oldest-artwork', 'hash' => str_repeat('a', 32), 'is_public' => true, 'is_approved' => true, 'published_at' => now()->subDays(3), 'updated_at' => now()->subDays(3), ]); $middle = Artwork::factory()->for($user)->create([ 'title' => 'Middle artwork', 'slug' => 'middle-artwork', 'hash' => str_repeat('b', 32), 'is_public' => true, 'is_approved' => true, 'published_at' => now()->subDays(2), 'updated_at' => now()->subDays(2), ]); $latest = Artwork::factory()->for($user)->create([ 'title' => 'Latest artwork', 'slug' => 'latest-artwork', 'hash' => str_repeat('c', 32), 'is_public' => true, 'is_approved' => true, 'published_at' => now()->subDay(), 'updated_at' => now()->subHour(), ]); bindVectorService(); $code = Artisan::call('artworks:vectors-index', [ '--dry-run' => true, '--public-only' => true, '--limit' => 3, '--batch' => 3, ]); $output = Artisan::output(); expect($code)->toBe(0); $latestPos = strpos($output, 'Processing artwork=' . $latest->id); $middlePos = strpos($output, 'Processing artwork=' . $middle->id); $oldestPos = strpos($output, 'Processing artwork=' . $oldest->id); expect($latestPos)->not->toBeFalse() ->and($middlePos)->not->toBeFalse() ->and($oldestPos)->not->toBeFalse() ->and($latestPos)->toBeLessThan($middlePos) ->and($middlePos)->toBeLessThan($oldestPos); }); it('supports legacy id ascending order when explicitly requested', function (): void { $user = User::factory()->create(); $first = Artwork::factory()->for($user)->create([ 'title' => 'First artwork', 'slug' => 'first-artwork-' . Str::lower(Str::random(4)), 'hash' => str_repeat('d', 32), 'is_public' => true, 'is_approved' => true, 'published_at' => now()->subDay(), 'updated_at' => now(), ]); $second = Artwork::factory()->for($user)->create([ 'title' => 'Second artwork', 'slug' => 'second-artwork-' . Str::lower(Str::random(4)), 'hash' => str_repeat('e', 32), 'is_public' => true, 'is_approved' => true, 'published_at' => now()->subDays(2), 'updated_at' => now()->subDays(2), ]); bindVectorService(); $code = Artisan::call('artworks:vectors-index', [ '--dry-run' => true, '--public-only' => true, '--limit' => 2, '--batch' => 2, '--order' => 'id-asc', ]); $output = Artisan::output(); expect($code)->toBe(0); $firstPos = strpos($output, 'Processing artwork=' . $first->id); $secondPos = strpos($output, 'Processing artwork=' . $second->id); expect($firstPos)->not->toBeFalse() ->and($secondPos)->not->toBeFalse() ->and($firstPos)->toBeLessThan($secondPos); });