Files
portal/app/tests/Feature/Autopodbor/StudyRepeatCollectTest.php
T
Дмитрий 892ca06093 feat(autopodbor): повторный сбор источников — бэкенд
Клиент может собрать источники по конкуренту ещё раз. Новые → в предложения,
ранее удалённые (найдены снова) → помечаются для возврата, уже известные не
задваиваются и не трогаются.

- удаление источника стало мягким (box=archived) — помним, чтобы показать «ранее удалён»
- снят жёсткий стоп «уже изучали» в startStudy (повтор разрешён; от нахлёста — assertNoInFlight)
- колонка result (jsonb) на прогоне + cast + RunResource
- джоба изучения классифицирует источники и пишет итог {new_sources, refound_deleted, ids}
- тесты: мягкое удаление, повтор, классификация

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 06:08:12 +03:00

99 lines
4.8 KiB
PHP

<?php
declare(strict_types=1);
use App\Jobs\Autopodbor\RunAutopodborStudyJob;
use App\Models\AutopodborCompetitor;
use App\Models\AutopodborRun;
use App\Models\AutopodborSource;
use App\Models\SystemSetting;
use App\Models\Tenant;
use App\Services\Autopodbor\Agent\CompetitorAgent;
use App\Services\Autopodbor\Agent\Dto\FindCompetitorsRequest;
use App\Services\Autopodbor\Agent\Dto\FindCompetitorsResult;
use App\Services\Autopodbor\Agent\Dto\ResolveByNameRequest;
use App\Services\Autopodbor\Agent\Dto\ResolveByNameResult;
use App\Services\Autopodbor\Agent\Dto\StudyCompetitorRequest;
use App\Services\Autopodbor\Agent\Dto\StudyCompetitorResult;
use App\Services\Autopodbor\AutopodborNormalizer;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Tests\Concerns\SharesSupplierPdo;
uses(DatabaseTransactions::class, SharesSupplierPdo::class);
/** Агент отдаёт при изучении фиксированные сайты (site-источники). */
function bindStudyAgentReturning(array $siteIdentifiers): void
{
app()->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],
]);
});