bind(CompetitorAgent::class, fn () => new class($siteIdentifiers) implements CompetitorAgent { public function __construct(private array $sites) {} public function findCompetitors(FindCompetitorsRequest $r): FindCompetitorsResult { return new FindCompetitorsResult([]); } public function studyCompetitor(StudyCompetitorRequest $r): StudyCompetitorResult { return new StudyCompetitorResult(array_map(fn (string $id): array => [ 'signal_type' => 'site', 'identifier' => $id, 'phone_kind' => null, 'phone_type' => null, 'provenance_url' => null, 'provenance_label' => 'тест', ], $this->sites)); } public function resolveByName(ResolveByNameRequest $r): ResolveByNameResult { return new ResolveByNameResult([]); } }); } it('повторный сбор: новые в предложения, ранее удалённые помечаются «вернуть», известные не тронуты', function () { $tenant = Tenant::factory()->create(['balance_rub' => '100000.00']); DB::statement('SET app.current_tenant_id = '.$tenant->id); SystemSetting::updateOrCreate(['key' => 'autopodbor_price_study_rub'], ['value' => '0', 'type' => 'decimal']); $norm = app(AutopodborNormalizer::class); $oldRun = AutopodborRun::create(['tenant_id' => $tenant->id, 'kind' => 'study', 'status' => 'done', 'region_code' => 16, 'params' => []]); $comp = AutopodborCompetitor::create([ 'tenant_id' => $tenant->id, 'name' => 'Окна', 'dedup_key' => 'okna', 'box' => 'field', 'studied_at' => now(), 'study_run_id' => $oldRun->id, ]); // Уже известный источник, взятый в работу (box=field) — трогать нельзя. $known = AutopodborSource::create([ 'tenant_id' => $tenant->id, 'competitor_id' => $comp->id, 'study_run_id' => $oldRun->id, 'signal_type' => 'site', 'identifier' => 'known.ru', 'dedup_key' => $norm->sourceKey('site', 'known.ru'), 'box' => 'field', ]); // Ранее удалённый источник (box=archived) — помним. $deleted = AutopodborSource::create([ 'tenant_id' => $tenant->id, 'competitor_id' => $comp->id, 'study_run_id' => $oldRun->id, 'signal_type' => 'site', 'identifier' => 'deleted.ru', 'dedup_key' => $norm->sourceKey('site', 'deleted.ru'), 'box' => 'archived', ]); // Движок при повторе находит: известный + ранее удалённый + СОВСЕМ новый. bindStudyAgentReturning(['known.ru', 'deleted.ru', 'new.ru']); $run = AutopodborRun::create([ 'tenant_id' => $tenant->id, 'kind' => 'study', 'status' => 'queued', 'region_code' => 16, 'params' => [], 'competitor_id' => $comp->id, ]); app()->call([new RunAutopodborStudyJob($run->id), 'handle']); // Новый → в предложения. $new = AutopodborSource::where('competitor_id', $comp->id)->where('identifier', 'new.ru')->first(); expect($new)->not->toBeNull()->and($new->box)->toBe('proposal'); // Ранее удалённый остаётся в архиве, но помечен последним прогоном (для блока «вернуть»). $deleted->refresh(); expect($deleted->box)->toBe('archived')->and($deleted->study_run_id)->toBe($run->id); // Известный (в работе) не тронут. $known->refresh(); expect($known->box)->toBe('field'); // Итог прогона: 1 новый, 1 ранее удалённый (с его id). expect($run->fresh()->result)->toEqual([ 'new_sources' => 1, 'refound_deleted' => 1, 'refound_deleted_ids' => [$deleted->id], ]); });