Files
portal/app/tests/Feature/Autopodbor/AutopodborCompetitorCrudApiTest.php
T
Дмитрий 2130cd74e9 feat(autopodbor): формы создания/правки конкурента через элементы - правка не теряет карточки
Бэк: хелпер CompetitorElements нормализует элементы и выводит легаси site_url/directory_urls
из ВСЕХ карточек. Ручки manual/update принимают elements.
Фронт: компонент CompetitorElementsEditor встроен в формы создания и правки на экранах
Поле и Предложения. Прежний баг - пересбор directory_urls из двух полей - закрыт.

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

134 lines
5.9 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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);
/** @return array{0: Tenant, 1: User, 2: AutopodborRun, 3: AutopodborCompetitor} */
function compCrudSetup(): 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' => []]);
$comp = AutopodborCompetitor::create([
'tenant_id' => $tenant->id, 'search_run_id' => $run->id,
'name' => 'Старое имя', 'dedup_key' => 'old', 'relevance_pct' => 50, 'box' => 'proposal',
]);
return [$tenant, $user, $run, $comp];
}
it('PATCH competitors/{id} — правит поля карточки', function () {
[$tenant, $user, $run, $comp] = compCrudSetup();
$this->actingAs($user)->patchJson("/api/autopodbor/competitors/{$comp->id}", [
'name' => 'Окна Премиум',
'description' => 'Премиальные окна',
'is_federal' => true,
'relevance_pct' => 88,
'site_url' => 'okna-premium.ru',
])->assertOk()
->assertJsonPath('data.name', 'Окна Премиум')
->assertJsonPath('data.relevance_pct', 88)
->assertJsonPath('data.is_federal', true);
$fresh = $comp->fresh();
expect($fresh->name)->toBe('Окна Премиум')
->and($fresh->site_url)->toBe('okna-premium.ru');
});
it('PATCH competitors/{id} — elements сохраняются, directory_urls из ВСЕХ карточек всех элементов (не теряем)', function () {
[$tenant, $user, $run, $comp] = compCrudSetup();
$this->actingAs($user)->patchJson("/api/autopodbor/competitors/{$comp->id}", [
'name' => 'Сибиряк',
'elements' => [
['name' => 'Нанжуль', 'sites' => ['nanzhul.ru'], 'cards' => ['https://2gis.ru/x/firm/1', 'https://yandex.ru/maps/org/n/2']],
['name' => 'Плодово', 'sites' => ['plodovo.ru'], 'cards' => ['https://2gis.ru/x/firm/3']],
],
])->assertOk()
->assertJsonCount(2, 'data.elements');
$fresh = $comp->fresh();
expect($fresh->elements)->toHaveCount(2)
->and($fresh->directory_urls)->toBe(['https://2gis.ru/x/firm/1', 'https://yandex.ru/maps/org/n/2', 'https://2gis.ru/x/firm/3'])
->and($fresh->site_url)->toBe('nanzhul.ru');
});
it('POST competitors/manual — создаёт с elements, легаси-поля выведены', function () {
$tenant = Tenant::factory()->create();
$user = User::factory()->create(['tenant_id' => $tenant->id]);
DB::statement('SET app.current_tenant_id = '.$tenant->id);
$resp = $this->actingAs($user)->postJson('/api/autopodbor/competitors/manual', [
'name' => 'Сибиряк',
'elements' => [
['name' => 'Нанжуль', 'sites' => ['nanzhul.ru'], 'cards' => ['https://yandex.ru/maps/org/n/2']],
],
])->assertStatus(201);
$comp = AutopodborCompetitor::find($resp->json('data.id'));
expect($comp->elements)->toHaveCount(1)
->and($comp->site_url)->toBe('nanzhul.ru')
->and($comp->directory_urls)->toBe(['https://yandex.ru/maps/org/n/2']);
});
it('PATCH competitors/{id} — отвергает похожесть вне 0..100 (422)', function () {
[$tenant, $user, $run, $comp] = compCrudSetup();
$this->actingAs($user)->patchJson("/api/autopodbor/competitors/{$comp->id}", [
'relevance_pct' => 150,
])->assertStatus(422);
});
it('PATCH competitors/{id} — чужой тенант не правит (404)', function () {
[$tenant, $user, $run, $comp] = compCrudSetup();
$other = User::factory()->create(['tenant_id' => Tenant::factory()->create()->id]);
$this->actingAs($other)->patchJson("/api/autopodbor/competitors/{$comp->id}", ['name' => 'Взлом'])
->assertStatus(404);
});
it('DELETE competitors/{id} — прячет конкурента в архив (box=archived), источники сохраняет', function () {
[$tenant, $user, $run, $comp] = compCrudSetup();
$src = AutopodborSource::create([
'tenant_id' => $tenant->id, 'competitor_id' => $comp->id, 'study_run_id' => $run->id,
'signal_type' => 'site', 'identifier' => 'okna.ru', 'dedup_key' => 'site:okna.ru',
]);
$this->actingAs($user)->deleteJson("/api/autopodbor/competitors/{$comp->id}")
->assertStatus(204);
// мягкое удаление: строка жива, box='archived', источник не тронут (спека §4)
expect(AutopodborCompetitor::find($comp->id))->not->toBeNull()
->and($comp->fresh()->box)->toBe('archived')
->and(AutopodborSource::find($src->id))->not->toBeNull();
});
it('DELETE competitors/{id} — блок, если у источника активный проект (409)', function () {
[$tenant, $user, $run, $comp] = compCrudSetup();
$project = Project::factory()->create(['tenant_id' => $tenant->id, 'is_active' => true, 'preflight_blocked_at' => null]);
AutopodborSource::create([
'tenant_id' => $tenant->id, 'competitor_id' => $comp->id, 'study_run_id' => $run->id,
'signal_type' => 'site', 'identifier' => 'okna.ru', 'dedup_key' => 'site:okna.ru',
'created_project_id' => $project->id,
]);
$this->actingAs($user)->deleteJson("/api/autopodbor/competitors/{$comp->id}")
->assertStatus(409)
->assertJsonPath('error', 'has_active_projects');
expect(AutopodborCompetitor::find($comp->id))->not->toBeNull();
});