Files
portal/app/tests/Feature/Autopodbor/CompetitorActualizeTest.php
T
Дмитрий 43012c982e
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
feat(autopodbor): «Удалить» на актуализации — мягкое отклонение находки
Кнопка «Удалить» на карточках «На актуализацию» рядом с Добавить/Заменить.
Отклоняет ТОЛЬКО находку (новый сайт/адрес): фирма поля остаётся, находка → архив,
её новые ключи запоминаются у фирмы поля (новая колонка dismissed_actualize_keys jsonb).
Классификатор вычитает отклонённые ключи → та же находка больше не всплывает; реально
другой новый сайт даст новый ключ → покажется. Не глушим по имени, конкурента не убираем.

Бэк: миграция + модель-каст + ProposalClassifier (вычитание) + эндпоинт dismissActualize
+ роут. Фронт: api + store + кнопка «Удалить». Тесты: классификатор 8/8, ветка 185/185,
фронт 30/30. Canon-sync schema.sql (v8.62) — follow-up (миграция idempotent-guarded).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 15:50:59 +03:00

150 lines
7.8 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\AutopodborCompetitor;
use App\Models\AutopodborRun;
use App\Models\AutopodborSource;
use App\Models\Project;
use App\Models\Tenant;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Tests\Concerns\SharesSupplierPdo;
uses(DatabaseTransactions::class, SharesSupplierPdo::class);
/**
* Фирма в поле с одним сайт-источником (изучена — study_run_id есть).
*
* @return array{0: Tenant, 1: User, 2: AutopodborRun, 3: AutopodborCompetitor, 4: AutopodborSource}
*/
function actualizeSetup(): array
{
$tenant = Tenant::factory()->create();
$user = User::factory()->create(['tenant_id' => $tenant->id]);
DB::statement('SET app.current_tenant_id = '.$tenant->id);
$run = AutopodborRun::create(['tenant_id' => $tenant->id, 'kind' => 'search', 'status' => 'done', 'region_code' => 16, 'params' => []]);
$field = AutopodborCompetitor::create([
'tenant_id' => $tenant->id, 'search_run_id' => $run->id, 'study_run_id' => $run->id, 'studied_at' => now(),
'name' => 'Яричъ', 'box' => 'field', 'site_url' => 'yarich.ru', 'dedup_key' => 'site:yarich.ru',
]);
$oldSrc = AutopodborSource::create([
'tenant_id' => $tenant->id, 'competitor_id' => $field->id, 'study_run_id' => $run->id,
'signal_type' => 'site', 'identifier' => 'yarich.ru', 'box' => 'field', 'dedup_key' => 'site:yarich.ru',
]);
return [$tenant, $user, $run, $field, $oldSrc];
}
it('actualize add — новый сайт заводится источником, старый цел', function () {
[$tenant, $user, $run, $field, $oldSrc] = actualizeSetup();
$resp = $this->actingAs($user)->postJson("/api/autopodbor/competitors/{$field->id}/actualize", [
'action' => 'add', 'new_site' => 'https://www.Yarich-New.ru/contacts',
])->assertOk();
$new = AutopodborSource::find($resp->json('source_id'));
expect($new)->not->toBeNull()
->and($new->signal_type)->toBe('site')
->and($new->identifier)->toBe('yarich-new.ru') // нормализован
->and($new->box)->toBe('field');
// старый сайт-источник не тронут (оба сайта живут)
expect($oldSrc->fresh()->box)->toBe('field');
});
it('actualize replace без активного проекта — старый сайт в архив, новый заводится', function () {
[$tenant, $user, $run, $field, $oldSrc] = actualizeSetup();
$resp = $this->actingAs($user)->postJson("/api/autopodbor/competitors/{$field->id}/actualize", [
'action' => 'replace', 'new_site' => 'yarich-new.ru',
])->assertOk();
expect($oldSrc->fresh()->box)->toBe('archived'); // старый умер — в архив
$new = AutopodborSource::find($resp->json('source_id'));
expect($new->identifier)->toBe('yarich-new.ru')->and($new->box)->toBe('field');
});
it('actualize replace при активном проекте по старому сайту — 409 manage_via_project', function () {
[$tenant, $user, $run, $field, $oldSrc] = actualizeSetup();
$project = Project::factory()->create(['tenant_id' => $tenant->id, 'is_active' => true, 'preflight_blocked_at' => null]);
$oldSrc->update(['created_project_id' => $project->id]);
$this->actingAs($user)->postJson("/api/autopodbor/competitors/{$field->id}/actualize", [
'action' => 'replace', 'new_site' => 'yarich-new.ru',
])->assertStatus(409)->assertJsonPath('error', 'manage_via_project');
// ничего не тронуто — управлять надо через проект
expect($oldSrc->fresh()->box)->toBe('field');
});
it('actualize чужой тенант — 404', function () {
[$tenant, $user, $run, $field, $oldSrc] = actualizeSetup();
$other = User::factory()->create(['tenant_id' => Tenant::factory()->create()->id]);
$this->actingAs($other)->postJson("/api/autopodbor/competitors/{$field->id}/actualize", [
'action' => 'add', 'new_site' => 'x.ru',
])->assertStatus(404);
});
it('GET /proposals — actualize-карточка несёт matched с текущими сайтами фирмы', function () {
$tenant = Tenant::factory()->create();
$user = User::factory()->create(['tenant_id' => $tenant->id]);
DB::statement('SET app.current_tenant_id = '.$tenant->id);
$run = AutopodborRun::create(['tenant_id' => $tenant->id, 'kind' => 'search', 'status' => 'done', 'region_code' => 16, 'params' => []]);
// в поле: Яричъ, сайт yarich.ru, карточка 2ГИС 111
$field = AutopodborCompetitor::create([
'tenant_id' => $tenant->id, 'search_run_id' => $run->id, 'study_run_id' => $run->id, 'studied_at' => now(),
'name' => 'Яричъ', 'box' => 'field', 'site_url' => 'yarich.ru',
'directory_urls' => ['https://2gis.ru/x/firm/111'], 'dedup_key' => 'site:yarich.ru',
]);
// предложение: та же карточка 2ГИС 111, но новый сайт → на актуализацию
AutopodborCompetitor::create([
'tenant_id' => $tenant->id, 'search_run_id' => $run->id,
'name' => 'Яричъ (нашли заново)', 'box' => 'proposal', 'site_url' => 'yarich-new.ru',
'directory_urls' => ['https://2gis.ru/y/firm/111'], 'dedup_key' => 'site:yarich-new.ru',
]);
$resp = $this->actingAs($user)->getJson('/api/autopodbor/proposals')->assertOk();
$resp->assertJsonPath('groups.actualize.0.matched_id', $field->id)
->assertJsonPath('groups.actualize.0.matched.name', 'Яричъ');
expect(collect($resp->json('groups.actualize.0.matched.sites')))->toContain('yarich.ru');
});
it('dismiss-actualize — находка в архив, ключ запомнен, та же находка больше не всплывает', function () {
$tenant = Tenant::factory()->create();
$user = User::factory()->create(['tenant_id' => $tenant->id]);
DB::statement('SET app.current_tenant_id = '.$tenant->id);
$run = AutopodborRun::create(['tenant_id' => $tenant->id, 'kind' => 'search', 'status' => 'done', 'region_code' => 16, 'params' => []]);
// Фирма поля: сайт old.ru + карточка 2ГИС firm/111.
$field = AutopodborCompetitor::create([
'tenant_id' => $tenant->id, 'search_run_id' => $run->id, 'name' => 'X', 'box' => 'field',
'site_url' => 'old.ru', 'directory_urls' => ['https://2gis.ru/x/firm/111'], 'dedup_key' => 'site:old.ru',
]);
// Находка: та же карточка, но новый сайт new.ru → actualize.
$find = AutopodborCompetitor::create([
'tenant_id' => $tenant->id, 'search_run_id' => $run->id, 'name' => 'X', 'box' => 'proposal',
'site_url' => 'new.ru', 'directory_urls' => ['https://2gis.ru/x/firm/111'], 'dedup_key' => 'site:new.ru',
]);
$before = $this->actingAs($user)->getJson('/api/autopodbor/proposals')->assertOk()->json('groups.actualize');
expect(collect($before)->pluck('id'))->toContain($find->id);
$this->actingAs($user)->postJson("/api/autopodbor/competitors/{$find->id}/dismiss-actualize")
->assertOk()->assertJson(['dismissed' => true]);
expect($find->fresh()->box)->toBe('archived')
->and($field->fresh()->dismissed_actualize_keys)->toContain('s:new.ru');
// Новая такая же находка (new.ru) больше НЕ попадает в actualize.
$find2 = AutopodborCompetitor::create([
'tenant_id' => $tenant->id, 'search_run_id' => $run->id, 'name' => 'X', 'box' => 'proposal',
'site_url' => 'new.ru', 'directory_urls' => ['https://2gis.ru/x/firm/111'], 'dedup_key' => 'site:new.ru-2',
]);
$after = $this->actingAs($user)->getJson('/api/autopodbor/proposals')->assertOk()->json('groups.actualize');
expect(collect($after)->pluck('id'))->not->toContain($find2->id);
});