Files
portal/app/tests/Feature/Autopodbor/ProposalGroupsTest.php
T
Дмитрий 6d48503a0e feat(автоподбор): сверка находок со состоянием клиента — 3 группы предложений + скрытые дубли, мягкое удаление в архив, убран вычет себя
Stage 1: опознавалка фирмы по сайту/карточкам, ProposalClassifier new/actualize/archived/hidden, box=archived, bulk удалить всех ранее удалённых. Backend 249/249, front autopodbor 61/61.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 15:45:11 +03:00

138 lines
6.5 KiB
PHP

<?php
declare(strict_types=1);
use App\Jobs\Autopodbor\RunAutopodborSearchJob;
use App\Models\AutopodborCompetitor;
use App\Models\AutopodborRun;
use App\Models\SystemSetting;
use App\Models\Tenant;
use App\Models\User;
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\Agent\FakeCompetitorAgent;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Tests\Concerns\SharesSupplierPdo;
uses(DatabaseTransactions::class, SharesSupplierPdo::class);
/** Подставной агент: findCompetitors отдаёт заданный список; study/resolve — из заглушки. */
function agentReturning(array $competitors): CompetitorAgent
{
return new class($competitors) implements CompetitorAgent
{
private FakeCompetitorAgent $inner;
public function __construct(private array $competitors)
{
$this->inner = new FakeCompetitorAgent;
}
public function findCompetitors(FindCompetitorsRequest $r): FindCompetitorsResult
{
return new FindCompetitorsResult($this->competitors);
}
public function studyCompetitor(StudyCompetitorRequest $r): StudyCompetitorResult
{
return $this->inner->studyCompetitor($r);
}
public function resolveByName(ResolveByNameRequest $r): ResolveByNameResult
{
return $this->inner->resolveByName($r);
}
};
}
function groupsTestSettings(): void
{
SystemSetting::updateOrCreate(['key' => 'autopodbor_price_search_rub'], ['value' => '300', 'type' => 'decimal']);
SystemSetting::updateOrCreate(['key' => 'autopodbor_max_competitors'], ['value' => '15', 'type' => 'int']);
}
function mkSearchRun(int $tenantId): AutopodborRun
{
return AutopodborRun::create([
'tenant_id' => $tenantId, 'kind' => 'search', 'status' => 'queued', 'region_code' => 16,
'params' => ['examples' => [], 'about_self' => ['ломбард'], 'include_federal' => false],
]);
}
it('находка, совпавшая с АРХИВОМ, сохраняется в предложения (не прячется на записи)', function () {
Mail::fake();
$tenant = Tenant::factory()->create(['balance_rub' => '100000.00']);
DB::statement('SET app.current_tenant_id = '.$tenant->id);
groupsTestSettings();
// В архиве — ранее удалённый «Яричъ» с сайтом yarich.ru
AutopodborCompetitor::create([
'tenant_id' => $tenant->id, 'name' => 'Яричъ', 'origin' => 'auto', 'box' => 'archived',
'site_url' => 'yarich.ru', 'dedup_key' => 'site:yarich.ru',
]);
app()->bind(CompetitorAgent::class, fn () => agentReturning([
['name' => 'Яричъ', 'site_url' => 'yarich.ru', 'description' => 'ломбард', 'is_federal' => false,
'directory_urls' => [], 'relevance_pct' => 90, 'provenance' => []],
]));
$run = mkSearchRun($tenant->id);
app()->call([new RunAutopodborSearchJob($run->id), 'handle']);
expect(AutopodborCompetitor::where('tenant_id', $tenant->id)->where('box', 'proposal')->where('name', 'Яричъ')->exists())
->toBeTrue();
});
it('находка — полный дубль АКТИВНОЙ фирмы поля — на записи выкидывается (пусто)', function () {
Mail::fake();
$tenant = Tenant::factory()->create(['balance_rub' => '100000.00']);
DB::statement('SET app.current_tenant_id = '.$tenant->id);
groupsTestSettings();
AutopodborCompetitor::create([
'tenant_id' => $tenant->id, 'name' => 'Яричъ', 'origin' => 'auto', 'box' => 'field',
'site_url' => 'yarich.ru', 'dedup_key' => 'site:yarich.ru',
]);
app()->bind(CompetitorAgent::class, fn () => agentReturning([
['name' => 'Яричъ', 'site_url' => 'yarich.ru', 'description' => 'ломбард', 'is_federal' => false,
'directory_urls' => [], 'relevance_pct' => 90, 'provenance' => []],
]));
$run = mkSearchRun($tenant->id);
app()->call([new RunAutopodborSearchJob($run->id), 'handle']);
expect(AutopodborCompetitor::where('tenant_id', $tenant->id)->where('box', 'proposal')->exists())->toBeFalse()
->and($run->fresh()->status)->toBe('empty');
});
it('GET /proposals раскладывает по группам; bulk убирает всю группу «ранее удалённые»', function () {
$tenant = Tenant::factory()->create();
$user = User::factory()->create(['tenant_id' => $tenant->id]);
DB::statement('SET app.current_tenant_id = '.$tenant->id);
// в поле — Alpha (a.ru); в архиве — Beta (b.ru)
AutopodborCompetitor::create(['tenant_id' => $tenant->id, 'name' => 'Alpha', 'origin' => 'auto', 'box' => 'field', 'site_url' => 'a.ru', 'dedup_key' => 'site:a.ru']);
AutopodborCompetitor::create(['tenant_id' => $tenant->id, 'name' => 'Beta', 'origin' => 'auto', 'box' => 'archived', 'site_url' => 'b.ru', 'dedup_key' => 'site:b.ru']);
// предложения: Gamma (новый), «Beta заново» (совпал с архивом b.ru → ранее удалён)
$new = AutopodborCompetitor::create(['tenant_id' => $tenant->id, 'name' => 'Gamma', 'origin' => 'auto', 'box' => 'proposal', 'site_url' => 'g.ru', 'dedup_key' => 'site:g.ru']);
$del = AutopodborCompetitor::create(['tenant_id' => $tenant->id, 'name' => 'Beta заново', 'origin' => 'auto', 'box' => 'proposal', 'site_url' => 'b.ru', 'dedup_key' => 'site:b.ru']);
$groups = $this->actingAs($user)->getJson('/api/autopodbor/proposals')->assertOk()->json('groups');
expect($groups['new'][0]['name'])->toBe('Gamma')
->and($groups['archived'][0]['name'])->toBe('Beta заново');
// bulk: убрать всю группу «ранее удалённые»
$this->actingAs($user)->postJson('/api/autopodbor/proposals/dismiss-archived')->assertOk();
expect(AutopodborCompetitor::find($del->id)->box)->toBe('archived') // ранее удалённый снова в архив
->and(AutopodborCompetitor::find($new->id)->box)->toBe('proposal'); // новых не трогаем
});