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

172 lines
9.0 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\Tenant;
use App\Services\Autopodbor\AutopodborCompetitorMerger;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Tests\Concerns\SharesSupplierPdo;
uses(DatabaseTransactions::class, SharesSupplierPdo::class);
function mergerTenant(): Tenant
{
$t = Tenant::factory()->create(['balance_rub' => '100000.00']);
DB::statement('SET app.current_tenant_id = '.$t->id);
return $t;
}
function mkComp(Tenant $t, string $name, ?string $site, array $dirs = []): AutopodborCompetitor
{
$run = AutopodborRun::create(['tenant_id' => $t->id, 'kind' => 'search', 'status' => 'done', 'region_code' => 16, 'params' => []]);
return AutopodborCompetitor::create([
'tenant_id' => $t->id, 'search_run_id' => $run->id, 'name' => $name,
'dedup_key' => 'c:'.md5($name), 'site_url' => $site, 'directory_urls' => $dirs,
]);
}
function mkSrc(Tenant $t, AutopodborCompetitor $c, string $type, string $ident, string $dedupKey, ?int $projectId = null, string $box = 'field'): AutopodborSource
{
$run = AutopodborRun::create(['tenant_id' => $t->id, 'kind' => 'study', 'status' => 'done', 'region_code' => 16, 'competitor_id' => $c->id, 'params' => []]);
return AutopodborSource::create([
'tenant_id' => $t->id, 'competitor_id' => $c->id, 'study_run_id' => $run->id,
'signal_type' => $type, 'identifier' => $ident, 'dedup_key' => $dedupKey,
'box' => $box, 'confirmations' => 1, 'created_project_id' => $projectId,
]);
}
it('переносит источники проигравшего на выжившего, дубль-конкурента удаляет, справочники объединяет', function () {
$t = mergerTenant();
$a = mkComp($t, 'AutoMoney', 'automoney-krsk.ru', ['https://2gis.ru/krasnoyarsk/firm/1']);
$b = mkComp($t, 'Автодом24', 'automoney-krsk.ru', ['https://yandex.ru/maps/org/x/2']);
mkSrc($t, $a, 'site', 'automoney-krsk.ru', 'site:automoney-krsk.ru');
mkSrc($t, $b, 'call', '79135396546', 'call:79135396546');
$survivorId = app(AutopodborCompetitorMerger::class)->merge($t->id, [$a->id, $b->id]);
expect(AutopodborSource::where('competitor_id', $survivorId)->count())->toBe(2);
$loserId = $survivorId === $a->id ? $b->id : $a->id;
expect(AutopodborCompetitor::find($loserId))->toBeNull();
$s = AutopodborCompetitor::find($survivorId);
expect($s->directory_urls)->toContain('https://2gis.ru/krasnoyarsk/firm/1')->toContain('https://yandex.ru/maps/org/x/2');
});
it('источник с проектом не теряется — переносится, created_project_id сохранён, конкурент с проектом выживает', function () {
$t = mergerTenant();
$a = mkComp($t, 'A', 'a.ru');
$b = mkComp($t, 'B', 'a.ru');
$project = Project::factory()->create(['tenant_id' => $t->id, 'is_active' => true, 'preflight_blocked_at' => null]);
mkSrc($t, $b, 'call', '79001112233', 'call:79001112233', $project->id);
$survivorId = app(AutopodborCompetitorMerger::class)->merge($t->id, [$a->id, $b->id]);
expect($survivorId)->toBe($b->id); // у B источник с проектом → он выживает
$src = AutopodborSource::where('created_project_id', $project->id)->first();
expect($src)->not->toBeNull();
expect($src->competitor_id)->toBe($survivorId);
});
it('коллизия ключа: у выжившего источник с проектом, дубль без проекта отброшен, проект цел', function () {
$t = mergerTenant();
$project = Project::factory()->create(['tenant_id' => $t->id, 'is_active' => true, 'preflight_blocked_at' => null]);
$a = mkComp($t, 'A', 'a.ru');
mkSrc($t, $a, 'call', '79001112233', 'call:79001112233', $project->id);
$b = mkComp($t, 'B', 'a.ru');
mkSrc($t, $b, 'call', '79001112233', 'call:79001112233'); // тот же ключ, без проекта
$survivorId = app(AutopodborCompetitorMerger::class)->merge($t->id, [$a->id, $b->id]);
expect($survivorId)->toBe($a->id);
expect(AutopodborSource::where('competitor_id', $survivorId)->where('dedup_key', 'call:79001112233')->count())->toBe(1);
$src = AutopodborSource::where('competitor_id', $survivorId)->where('dedup_key', 'call:79001112233')->first();
expect($src->created_project_id)->toBe($project->id);
});
it('три конкурента в группе → один выживший, два удалены, все источники у выжившего', function () {
$t = mergerTenant();
$a = mkComp($t, 'A', 'x.ru');
$b = mkComp($t, 'B', 'x.ru');
$c = mkComp($t, 'C', 'x.ru');
mkSrc($t, $a, 'site', 'x.ru', 'site:x.ru');
mkSrc($t, $b, 'call', '79001112233', 'call:79001112233');
mkSrc($t, $c, 'call', '79004445566', 'call:79004445566');
$survivorId = app(AutopodborCompetitorMerger::class)->merge($t->id, [$a->id, $b->id, $c->id]);
expect(AutopodborCompetitor::whereIn('id', [$a->id, $b->id, $c->id])->count())->toBe(1);
expect(AutopodborSource::where('competitor_id', $survivorId)->count())->toBe(3);
});
it('меньше двух конкурентов тенанта → исключение (ничего не сливаем)', function () {
$t = mergerTenant();
$a = mkComp($t, 'A', 'a.ru');
expect(fn () => app(AutopodborCompetitorMerger::class)->merge($t->id, [$a->id]))
->toThrow(InvalidArgumentException::class);
});
it('слияние принимает выбранное имя для объединённого конкурента', function () {
$t = mergerTenant();
$a = mkComp($t, 'Сибиряк', 'a.ru');
$b = mkComp($t, 'Нанжуль', 'a.ru');
$survivorId = app(AutopodborCompetitorMerger::class)->merge($t->id, [$a->id, $b->id], 'Сибиряк холдинг');
expect(AutopodborCompetitor::find($survivorId)->name)->toBe('Сибиряк холдинг');
});
it('слияние без имени — имя выжившего не трогаем', function () {
$t = mergerTenant();
$a = mkComp($t, 'Альфа', 'a.ru');
$b = mkComp($t, 'Бета', 'a.ru');
$survivorId = app(AutopodborCompetitorMerger::class)->merge($t->id, [$a->id, $b->id]);
// выживший — старейший (меньший id) = A, имя сохраняется
expect(AutopodborCompetitor::find($survivorId)->name)->toBe('Альфа');
});
it('слияние переименовывает проекты в имя выжившего конкурента', function () {
$t = mergerTenant();
$pA = Project::factory()->create(['tenant_id' => $t->id, 'name' => 'Сибиряк', 'is_active' => true, 'preflight_blocked_at' => null]);
$pB = Project::factory()->create(['tenant_id' => $t->id, 'name' => 'Другой проект', 'is_active' => true, 'preflight_blocked_at' => null]);
$a = mkComp($t, 'Сибиряк', 'sibiryak.ru');
mkSrc($t, $a, 'site', 'sibiryak.ru', 'site:sibiryak.ru', $pA->id);
$b = mkComp($t, 'Сибирская Строительная Компания', 'ssk.ru');
mkSrc($t, $b, 'site', 'ssk.ru', 'site:ssk.ru', $pB->id);
app(AutopodborCompetitorMerger::class)->merge($t->id, [$a->id, $b->id], 'Сибирская Строительная Компания');
$names = Project::whereIn('id', [$pA->id, $pB->id])->pluck('name')->all();
foreach ($names as $n) {
expect(str_starts_with((string) $n, 'Сибирская Строительная Компания'))->toBeTrue(); // имя следует за конкурентом
}
expect($names)->not->toContain('Сибиряк') // старого имени нет
->and(collect($names)->unique()->count())->toBe(2); // имена всё ещё разные (одно с источником)
});
it('слияние складывает элементы всех конкурентов и ничего не теряет', function () {
$t = mergerTenant();
$a = mkComp($t, 'Сибиряк', 'сибиряк.рф');
$a->update(['elements' => [['name' => 'Сибиряк', 'sites' => ['сибиряк.рф'], 'cards' => []]]]);
$b = mkComp($t, 'Нанжуль-Солнечный', 'nanzhul.ru', ['https://yandex.ru/maps/org/nanzhul_solnechny/1']);
$b->update(['elements' => [['name' => 'Нанжуль-Солнечный', 'sites' => ['nanzhul.ru'], 'cards' => ['https://yandex.ru/maps/org/nanzhul_solnechny/1']]]]);
$survivorId = app(AutopodborCompetitorMerger::class)->merge($t->id, [$a->id, $b->id]);
$s = AutopodborCompetitor::find($survivorId);
$names = collect($s->elements)->pluck('name')->sort()->values()->all();
expect($names)->toBe(['Нанжуль-Солнечный', 'Сибиряк']);
// сайт проигравшего не потерян — он внутри его элемента
$allSites = collect($s->elements)->flatMap(fn ($e) => $e['sites'])->all();
expect($allSites)->toContain('nanzhul.ru');
});