optimizations
This commit is contained in:
153
tests/Feature/Vision/ArtworkVectorRepairQueueTest.php
Normal file
153
tests/Feature/Vision/ArtworkVectorRepairQueueTest.php
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Jobs\BackfillArtworkVectorIndexJob;
|
||||
use App\Jobs\SyncArtworkVectorIndexJob;
|
||||
use App\Models\Artwork;
|
||||
use App\Models\ArtworkEmbedding;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use function Pest\Laravel\artisan;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
function jobProperty(object $job, string $property): mixed
|
||||
{
|
||||
$reflection = new ReflectionProperty($job, $property);
|
||||
$reflection->setAccessible(true);
|
||||
|
||||
return $reflection->getValue($job);
|
||||
}
|
||||
|
||||
it('queues a resumable vector repair run from the command', function (): void {
|
||||
Queue::fake();
|
||||
|
||||
artisan('artworks:vectors-repair', [
|
||||
'--after-id' => 25,
|
||||
'--batch' => 50,
|
||||
'--public-only' => true,
|
||||
'--stale-hours' => 24,
|
||||
])->assertSuccessful();
|
||||
|
||||
Queue::assertPushed(BackfillArtworkVectorIndexJob::class, function (BackfillArtworkVectorIndexJob $job): bool {
|
||||
return jobProperty($job, 'afterId') === 25
|
||||
&& jobProperty($job, 'batchSize') === 50
|
||||
&& jobProperty($job, 'publicOnly') === true
|
||||
&& jobProperty($job, 'staleHours') === 24;
|
||||
});
|
||||
});
|
||||
|
||||
it('fans out queued vector repair only for artworks with local embeddings', function (): void {
|
||||
Queue::fake();
|
||||
|
||||
$embeddedArtwork = Artwork::factory()->create([
|
||||
'hash' => '1122334455667788',
|
||||
'is_public' => true,
|
||||
'is_approved' => true,
|
||||
'published_at' => now()->subDay(),
|
||||
]);
|
||||
ArtworkEmbedding::query()->create([
|
||||
'artwork_id' => $embeddedArtwork->id,
|
||||
'model' => 'clip',
|
||||
'model_version' => 'v1',
|
||||
'algo_version' => 'clip-cosine-v1',
|
||||
'dim' => 2,
|
||||
'embedding_json' => json_encode([0.6, 0.8], JSON_THROW_ON_ERROR),
|
||||
'source_hash' => '1122334455667788',
|
||||
'is_normalized' => true,
|
||||
'generated_at' => now(),
|
||||
'meta' => ['source' => 'clip'],
|
||||
]);
|
||||
|
||||
Artwork::factory()->create([
|
||||
'hash' => 'aabbccddeeff0011',
|
||||
'is_public' => true,
|
||||
'is_approved' => true,
|
||||
'published_at' => now()->subDay(),
|
||||
]);
|
||||
|
||||
(new BackfillArtworkVectorIndexJob(0, 100, true))->handle();
|
||||
|
||||
Queue::assertPushed(SyncArtworkVectorIndexJob::class, function (SyncArtworkVectorIndexJob $job) use ($embeddedArtwork): bool {
|
||||
return jobProperty($job, 'artworkId') === $embeddedArtwork->id;
|
||||
});
|
||||
Queue::assertNotPushed(BackfillArtworkVectorIndexJob::class);
|
||||
});
|
||||
|
||||
it('can target only stale or never-indexed artworks during queued repair', function (): void {
|
||||
Queue::fake();
|
||||
|
||||
$staleArtwork = Artwork::factory()->create([
|
||||
'hash' => '1111222233334444',
|
||||
'is_public' => true,
|
||||
'is_approved' => true,
|
||||
'published_at' => now()->subDay(),
|
||||
'last_vector_indexed_at' => now()->subHours(48),
|
||||
]);
|
||||
ArtworkEmbedding::query()->create([
|
||||
'artwork_id' => $staleArtwork->id,
|
||||
'model' => 'clip',
|
||||
'model_version' => 'v1',
|
||||
'algo_version' => 'clip-cosine-v1',
|
||||
'dim' => 2,
|
||||
'embedding_json' => json_encode([0.6, 0.8], JSON_THROW_ON_ERROR),
|
||||
'source_hash' => '1111222233334444',
|
||||
'is_normalized' => true,
|
||||
'generated_at' => now(),
|
||||
'meta' => ['source' => 'clip'],
|
||||
]);
|
||||
|
||||
$missingArtwork = Artwork::factory()->create([
|
||||
'hash' => '5555666677778888',
|
||||
'is_public' => true,
|
||||
'is_approved' => true,
|
||||
'published_at' => now()->subDay(),
|
||||
'last_vector_indexed_at' => null,
|
||||
]);
|
||||
ArtworkEmbedding::query()->create([
|
||||
'artwork_id' => $missingArtwork->id,
|
||||
'model' => 'clip',
|
||||
'model_version' => 'v1',
|
||||
'algo_version' => 'clip-cosine-v1',
|
||||
'dim' => 2,
|
||||
'embedding_json' => json_encode([0.6, 0.8], JSON_THROW_ON_ERROR),
|
||||
'source_hash' => '5555666677778888',
|
||||
'is_normalized' => true,
|
||||
'generated_at' => now(),
|
||||
'meta' => ['source' => 'clip'],
|
||||
]);
|
||||
|
||||
$freshArtwork = Artwork::factory()->create([
|
||||
'hash' => '9999aaaabbbbcccc',
|
||||
'is_public' => true,
|
||||
'is_approved' => true,
|
||||
'published_at' => now()->subDay(),
|
||||
'last_vector_indexed_at' => now()->subHours(2),
|
||||
]);
|
||||
ArtworkEmbedding::query()->create([
|
||||
'artwork_id' => $freshArtwork->id,
|
||||
'model' => 'clip',
|
||||
'model_version' => 'v1',
|
||||
'algo_version' => 'clip-cosine-v1',
|
||||
'dim' => 2,
|
||||
'embedding_json' => json_encode([0.6, 0.8], JSON_THROW_ON_ERROR),
|
||||
'source_hash' => '9999aaaabbbbcccc',
|
||||
'is_normalized' => true,
|
||||
'generated_at' => now(),
|
||||
'meta' => ['source' => 'clip'],
|
||||
]);
|
||||
|
||||
(new BackfillArtworkVectorIndexJob(0, 100, true, 24))->handle();
|
||||
|
||||
Queue::assertPushed(SyncArtworkVectorIndexJob::class, function (SyncArtworkVectorIndexJob $job) use ($staleArtwork): bool {
|
||||
return jobProperty($job, 'artworkId') === $staleArtwork->id;
|
||||
});
|
||||
Queue::assertPushed(SyncArtworkVectorIndexJob::class, function (SyncArtworkVectorIndexJob $job) use ($missingArtwork): bool {
|
||||
return jobProperty($job, 'artworkId') === $missingArtwork->id;
|
||||
});
|
||||
Queue::assertNotPushed(SyncArtworkVectorIndexJob::class, function (SyncArtworkVectorIndexJob $job) use ($freshArtwork): bool {
|
||||
return jobProperty($job, 'artworkId') === $freshArtwork->id;
|
||||
});
|
||||
Queue::assertNotPushed(BackfillArtworkVectorIndexJob::class);
|
||||
});
|
||||
Reference in New Issue
Block a user