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); });