Files
portal/app/tests/Feature/Autopodbor/AutopodborManualApiTest.php
T

150 lines
9.4 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
use App\Models\AutopodborCompetitor;
use App\Models\AutopodborRun;
use App\Models\AutopodborSource;
use App\Models\Project;
use App\Models\SystemSetting;
use App\Models\Tenant;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Queue;
use Tests\Concerns\SharesSupplierPdo;
uses(DatabaseTransactions::class, SharesSupplierPdo::class);
beforeEach(fn () => Queue::fake());
it('manual-study по сайту создаёт ручного конкурента и study-прогон', function () {
$tenant = Tenant::factory()->create(['balance_rub' => '100000.00']);
$user = User::factory()->create(['tenant_id' => $tenant->id]);
SystemSetting::updateOrCreate(['key' => 'autopodbor_price_study_rub'], ['value' => '1', 'type' => 'decimal']);
$this->actingAs($user)->postJson('/api/autopodbor/manual-study', [
'site_url' => 'https://okna-komfort-kzn.ru/contacts', 'region_code' => 16,
])->assertCreated()->assertJsonPath('data.kind', 'study');
DB::statement('SET app.current_tenant_id = '.$tenant->id);
expect(AutopodborCompetitor::where('tenant_id', $tenant->id)->where('origin', 'manual')->exists())->toBeTrue();
});
it('manual-study без названия и без сайта → 422', function () {
$tenant = Tenant::factory()->create(['balance_rub' => '100000.00']);
$user = User::factory()->create(['tenant_id' => $tenant->id]);
SystemSetting::updateOrCreate(['key' => 'autopodbor_price_study_rub'], ['value' => '1', 'type' => 'decimal']);
$this->actingAs($user)->postJson('/api/autopodbor/manual-study', ['region_code' => 16])
->assertStatus(422);
});
it('sources/manual добавляет источник изученному конкуренту', 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' => 'study', 'status' => 'done', 'region_code' => 16, 'params' => []]);
$comp = AutopodborCompetitor::create(['tenant_id' => $tenant->id, 'search_run_id' => $run->id, 'study_run_id' => $run->id, 'studied_at' => now(), 'name' => 'Окна Комфорт', 'origin' => 'auto', 'dedup_key' => 'okna']);
$this->actingAs($user)->postJson('/api/autopodbor/sources/manual', [
'competitor_id' => $comp->id, 'raw' => 'okna-komfort.ru',
])->assertCreated()
->assertJsonPath('data.signal_type', 'site')
->assertJsonPath('data.box', 'field'); // ручной источник — сразу «в работу», не в предложения
expect(AutopodborSource::where('competitor_id', $comp->id)->where('identifier', 'okna-komfort.ru')->first())
->box->toBe('field');
});
// ——— #4: защита от дублей источника ———
function makeStudiedCompetitors(int $tenantId): array
{
$run = AutopodborRun::create(['tenant_id' => $tenantId, 'kind' => 'study', 'status' => 'done', 'region_code' => 16, 'params' => []]);
$a = AutopodborCompetitor::create(['tenant_id' => $tenantId, 'search_run_id' => $run->id, 'study_run_id' => $run->id, 'studied_at' => now(), 'name' => 'Конкурент А', 'origin' => 'auto', 'dedup_key' => 'a']);
$b = AutopodborCompetitor::create(['tenant_id' => $tenantId, 'search_run_id' => $run->id, 'study_run_id' => $run->id, 'studied_at' => now(), 'name' => 'Конкурент Б', 'origin' => 'auto', 'dedup_key' => 'b']);
return [$run, $a, $b];
}
it('sources/manual — такой же источник у другого конкурента → 409 duplicate_source', function () {
$tenant = Tenant::factory()->create();
$user = User::factory()->create(['tenant_id' => $tenant->id]);
DB::statement('SET app.current_tenant_id = '.$tenant->id);
[$run, $a, $b] = makeStudiedCompetitors($tenant->id);
AutopodborSource::create(['tenant_id' => $tenant->id, 'competitor_id' => $a->id, 'study_run_id' => $run->id, 'signal_type' => 'site', 'identifier' => 'dubel-test.ru', 'dedup_key' => 's:dubel-test.ru', 'box' => 'field']);
$this->actingAs($user)->postJson('/api/autopodbor/sources/manual', [
'competitor_id' => $b->id, 'raw' => 'dubel-test.ru',
])->assertStatus(409)
->assertJsonPath('error', 'duplicate_source')
->assertJsonPath('duplicate.competitor_name', 'Конкурент А')
->assertJsonPath('duplicate.has_project', false);
});
it('sources/manual — по источнику уже есть проект → 409 с has_project', function () {
$tenant = Tenant::factory()->create();
$user = User::factory()->create(['tenant_id' => $tenant->id]);
DB::statement('SET app.current_tenant_id = '.$tenant->id);
[$run, $a, $b] = makeStudiedCompetitors($tenant->id);
$project = Project::factory()->asSiteSignal('dubel-test.ru')->create(['tenant_id' => $tenant->id]);
$this->actingAs($user)->postJson('/api/autopodbor/sources/manual', [
'competitor_id' => $b->id, 'raw' => 'dubel-test.ru',
])->assertStatus(409)
->assertJsonPath('error', 'duplicate_source')
->assertJsonPath('duplicate.has_project', true)
->assertJsonPath('duplicate.project_id', $project->id);
});
it('sources/manual — force+absorb переносит источник и привязывает существующий проект, старый источник удалён', function () {
$tenant = Tenant::factory()->create();
$user = User::factory()->create(['tenant_id' => $tenant->id]);
DB::statement('SET app.current_tenant_id = '.$tenant->id);
[$run, $a, $b] = makeStudiedCompetitors($tenant->id);
$srcA = AutopodborSource::create(['tenant_id' => $tenant->id, 'competitor_id' => $a->id, 'study_run_id' => $run->id, 'signal_type' => 'site', 'identifier' => 'dubel-test.ru', 'dedup_key' => 's:dubel-test.ru', 'box' => 'field']);
$project = Project::factory()->asSiteSignal('dubel-test.ru')->create(['tenant_id' => $tenant->id]);
$this->actingAs($user)->postJson('/api/autopodbor/sources/manual', [
'competitor_id' => $b->id, 'raw' => 'dubel-test.ru', 'force' => true, 'absorb_source_id' => $srcA->id,
])->assertCreated()->assertJsonPath('data.box', 'field');
$newSrc = AutopodborSource::where('competitor_id', $b->id)->where('identifier', 'dubel-test.ru')->first();
expect($newSrc->created_project_id)->toBe($project->id); // проект привязан к новому источнику
expect(AutopodborSource::find($srcA->id))->toBeNull(); // старый источник-дубль убран
expect($project->fresh()->name)->toBe('Конкурент Б'); // имя проекта обновлено на нового конкурента
});
it('sources/manual — перенос проекта: проект остаётся привязан РОВНО к одному источнику', function () {
// Регресс на жёсткий баг с дублями: раньше проект мог «висеть» в двух конкурентах сразу
// (created_project_id стоял и у старого, и у нового источника).
$tenant = Tenant::factory()->create();
$user = User::factory()->create(['tenant_id' => $tenant->id]);
DB::statement('SET app.current_tenant_id = '.$tenant->id);
[$run, $a, $b] = makeStudiedCompetitors($tenant->id);
$project = Project::factory()->asSiteSignal('dubel-test.ru')->create(['tenant_id' => $tenant->id]);
$srcA = AutopodborSource::create(['tenant_id' => $tenant->id, 'competitor_id' => $a->id, 'study_run_id' => $run->id, 'signal_type' => 'site', 'identifier' => 'dubel-test.ru', 'dedup_key' => 's:dubel-test.ru', 'box' => 'field', 'created_project_id' => $project->id]);
// переносим тот же источник в конкурента Б (force, БЕЗ absorb)
$this->actingAs($user)->postJson('/api/autopodbor/sources/manual', [
'competitor_id' => $b->id, 'raw' => 'dubel-test.ru', 'force' => true,
])->assertCreated();
$srcB = AutopodborSource::where('competitor_id', $b->id)->where('identifier', 'dubel-test.ru')->first();
expect($srcB->created_project_id)->toBe($project->id); // новый источник владеет проектом
expect($srcA->fresh()->created_project_id)->toBeNull(); // старый больше НЕ привязан (инвариант)
expect(AutopodborSource::where('tenant_id', $tenant->id)->where('created_project_id', $project->id)->count())->toBe(1); // ровно один владелец
});
it('sources/manual телефоном создаёт call-источник', 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' => 'study', 'status' => 'done', 'region_code' => 16, 'params' => []]);
$comp = AutopodborCompetitor::create(['tenant_id' => $tenant->id, 'search_run_id' => $run->id, 'study_run_id' => $run->id, 'studied_at' => now(), 'name' => 'Окна', 'origin' => 'auto', 'dedup_key' => 'okna2']);
$this->actingAs($user)->postJson('/api/autopodbor/sources/manual', [
'competitor_id' => $comp->id, 'raw' => '+7 (843) 200-11-22',
])->assertCreated()->assertJsonPath('data.signal_type', 'call');
});