assertGreaterThan( (int) config('horizon.defaults.supervisor-default.timeout'), (int) config('queue.connections.redis.retry_after'), ); } public function test_behavior_job_processes_a_batch_and_dispatches_the_next_cursor(): void { $user = User::factory()->create(); $artworks = Artwork::factory()->count(3)->create(['user_id' => $user->id]); DB::table('rec_item_pairs')->insert([ [ 'a_artwork_id' => $artworks[0]->id, 'b_artwork_id' => $artworks[1]->id, 'weight' => 1.5, 'updated_at' => now(), ], [ 'a_artwork_id' => $artworks[0]->id, 'b_artwork_id' => $artworks[2]->id, 'weight' => 1.0, 'updated_at' => now(), ], ]); Queue::fake(); (new RecComputeSimilarByBehaviorJob(null, 2))->handle(); $this->assertNotNull( RecArtworkRec::query() ->where('artwork_id', $artworks[0]->id) ->where('rec_type', 'similar_behavior') ->first(), ); $expectedCursor = (int) $artworks->sortBy('id')->values()[1]->id; Queue::assertPushed( RecComputeSimilarByBehaviorJob::class, fn (RecComputeSimilarByBehaviorJob $job): bool => $job->cursorAfterArtworkId() === $expectedCursor, ); } public function test_behavior_job_does_not_dispatch_when_the_batch_is_partial(): void { Artwork::factory()->count(2)->create(); Queue::fake(); (new RecComputeSimilarByBehaviorJob(null, 10))->handle(); Queue::assertNothingPushed(); } public function test_hybrid_job_is_idempotent_for_a_single_artwork(): void { $user = User::factory()->create(); $source = Artwork::factory()->create(['user_id' => $user->id]); $related = Artwork::factory()->create(['user_id' => $user->id]); RecArtworkRec::query()->create([ 'artwork_id' => $source->id, 'rec_type' => 'similar_tags', 'model_version' => (string) config('recommendations.similarity.model_version', 'sim_v1'), 'recs' => [$related->id], 'computed_at' => now(), ]); (new RecComputeSimilarHybridJob($source->id))->handle(); (new RecComputeSimilarHybridJob($source->id))->handle(); $this->assertSame( 1, RecArtworkRec::query() ->where('artwork_id', $source->id) ->where('rec_type', 'similar_hybrid') ->count(), ); } public function test_hybrid_batch_dispatches_continuation_when_full(): void { Artwork::factory()->count(3)->create(); Queue::fake(); (new RecComputeSimilarHybridJob(null, 2))->handle(); Queue::assertPushed(RecComputeSimilarHybridJob::class); } public function test_tag_idf_group_by_is_cached_across_handle_calls(): void { Cache::flush(); Artwork::factory()->create([ 'is_public' => true, 'is_approved' => true, 'published_at' => now(), ]); $idfQueries = 0; DB::listen(function ($query) use (&$idfQueries): void { if (str_contains(strtolower($query->sql), 'artwork_tag') && str_contains(strtolower($query->sql), 'group by')) { $idfQueries++; } }); (new RecComputeSimilarByTagsJob(null, 10))->handle(); (new RecComputeSimilarByTagsJob(null, 10))->handle(); $this->assertSame(1, $idfQueries); } }