Files
SkinbaseNova/tests/Feature/Console/IndexArtworkVectorsCommandTest.php

129 lines
4.0 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\Artwork;
use App\Models\User;
use App\Services\Vision\AiArtworkVectorSearchService;
use App\Services\Vision\ArtworkVectorIndexService;
use App\Services\Vision\ArtworkVectorMetadataService;
use App\Services\Vision\ArtworkVisionImageUrl;
use App\Services\Vision\VectorGatewayClient;
use App\Services\Vision\VectorService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Str;
uses(RefreshDatabase::class);
function bindVectorService(): void
{
$imageUrl = new ArtworkVisionImageUrl();
app()->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);
});