Files
portal/app/tests/Feature/Autopodbor/CompetitorActualizeTest.php
T
Дмитрий 9a9164fee2 feat автоподбор Stage 2 актуализация — новый сайт фирмы поля через Добавить/Заменить в форму проекта
Секция «На актуализацию»: дельта-карточка показывает текущие сайты и карточки фирмы поля + новый сайт кликабельными ссылками, кнопки Добавить и Заменить ведут через подтверждение «Создать проект?» в существующую форму CreateScreen с подставленным новым источником. Имя фирмы берётся из поля, не перезатирается.

Бэкенд: endpoint POST /competitors/{id}/actualize action=add|replace new_site. add заводит сайт-источник рядом со старым, replace архивирует старые сайт-источники и заводит новый, при активном проекте по старому сайту возвращает 409 manage_via_project. /proposals обогащён полем matched с текущими сайтами и карточками совпавшей фирмы.

По TDD. Бэкенд автоподбора 254/254, фронт автоподбора 64/64, ESLint чисто, Pint чисто.

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

115 lines
5.7 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');
});