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' => 24, 'params' => []]); return [$tenant, $user, $run]; } it('ФИКС ПОДТВЕРЖДЁН: демо-данные Омеги (5 РАЗНЫХ конкурентов, разные сайты, общий плейсхолдер firm/0) больше НЕ склеиваются в дубли', function () { // Точная копия поля из OmegaDemoFieldSeeder::run() — 5 РЕАЛЬНО разных фирм с разными доменами, // у которых карточки справочника ещё «не вскрыты» (плейсхолдер id «0» вместо настоящего). [$t, $u, $run] = fpBugSetup(); $gis = 'https://2gis.ru/krasnoyarsk/firm/0'; // плейсхолдер-id "0" — карточка есть, id не вскрыт $ya = 'https://yandex.ru/maps/org/0'; // тот же плейсхолдер у нескольких фирм $field = [ ['КрасЛомбард', 'kraslombard24.ru', [$gis, $ya]], ['Голд Авто Инвест', 'goldautoinvest.ru', [$gis, $ya]], ['Финео', 'fineo24.ru', [$gis]], ['Cashmotor', 'cashmotor.ru', []], ['Локо-Банк', 'lockobank.ru', [$ya]], ]; foreach ($field as [$name, $site, $dirs]) { AutopodborCompetitor::create([ 'tenant_id' => $t->id, 'search_run_id' => $run->id, 'name' => $name, 'site_url' => $site, 'directory_urls' => $dirs, 'dedup_key' => 'site:'.$site, 'box' => 'field', ]); } $resp = $this->actingAs($u)->getJson('/api/autopodbor/field/duplicate-groups')->assertOk(); // 5 РАЗНЫХ сайтов → 0 групп дублей вообще (ни одна пара из этих пяти не является реальным // дублем — только плейсхолдер "0" в ссылке-заглушке совпадал, а это не признак совпадения). $resp->assertJsonCount(0, 'groups'); }); it('ФИКС: у клиента нет ни одной группы для нажатия «Объединить» — реальные разные конкуренты остаются 5 отдельными карточками', function () { // Тот же сетап — раньше здесь 4 из 5 конкурентов схлопывались одним «Объединить». Проверяем, // что после фикса все 5 карточек живы и по отдельности. [$t, $u, $run] = fpBugSetup(); $gis = 'https://2gis.ru/krasnoyarsk/firm/0'; $ya = 'https://yandex.ru/maps/org/0'; $field = [ ['КрасЛомбард', 'kraslombard24.ru', [$gis, $ya]], ['Голд Авто Инвест', 'goldautoinvest.ru', [$gis, $ya]], ['Финео', 'fineo24.ru', [$gis]], ['Cashmotor', 'cashmotor.ru', []], ['Локо-Банк', 'lockobank.ru', [$ya]], ]; foreach ($field as [$name, $site, $dirs]) { AutopodborCompetitor::create([ 'tenant_id' => $t->id, 'search_run_id' => $run->id, 'name' => $name, 'site_url' => $site, 'directory_urls' => $dirs, 'dedup_key' => 'site:'.$site, 'box' => 'field', ]); } $this->actingAs($u)->getJson('/api/autopodbor/field/duplicate-groups') ->assertOk() ->assertJsonCount(0, 'groups'); expect(AutopodborCompetitor::where('tenant_id', $t->id)->count())->toBe(5); }); it('НАСТОЯЩИЙ дубль по справочнику (одинаковый НЕПУСТОЙ id 2ГИС) — по-прежнему находится и склеивается', function () { // Защита от перегиба: фикс должен резать только плейсхолдер "0", а не ломать реальную // склейку одной и той же карточки справочника (id 111 — настоящий, не плейсхолдер). [$t, $u, $run] = fpBugSetup(); $a = AutopodborCompetitor::create([ 'tenant_id' => $t->id, 'search_run_id' => $run->id, 'name' => 'Яричъ Ломбард', 'site_url' => 'old-yarich.ru', 'directory_urls' => ['https://2gis.ru/krasnoyarsk/firm/111'], 'dedup_key' => 'site:old-yarich.ru', 'box' => 'field', ]); $b = AutopodborCompetitor::create([ 'tenant_id' => $t->id, 'search_run_id' => $run->id, 'name' => 'Яричъ (новый сайт)', 'site_url' => 'new-yarich.ru', 'directory_urls' => ['https://2gis.ru/krasnoyarsk/firm/111'], 'dedup_key' => 'site:new-yarich.ru', 'box' => 'field', ]); $resp = $this->actingAs($u)->getJson('/api/autopodbor/field/duplicate-groups')->assertOk(); $resp->assertJsonCount(1, 'groups'); expect(mb_strtolower($resp->json('groups.0.reason')))->toContain('справочник'); $ids = collect($resp->json('groups.0.competitors'))->pluck('id')->sort()->values()->all(); expect($ids)->toBe(collect([$a->id, $b->id])->sort()->values()->all()); }); it('НАСТОЯЩИЙ дубль по САЙТУ (одинаковый непустой site_url) — находится, причина «совпал сайт»', function () { [$t, $u, $run] = fpBugSetup(); $a = AutopodborCompetitor::create([ 'tenant_id' => $t->id, 'search_run_id' => $run->id, 'name' => 'Окна Комфорт (карточка 1)', 'site_url' => 'okna-komfort.ru', 'directory_urls' => [], 'dedup_key' => 'site:okna-komfort.ru:1', 'box' => 'field', ]); $b = AutopodborCompetitor::create([ 'tenant_id' => $t->id, 'search_run_id' => $run->id, 'name' => 'Окна Комфорт (карточка 2)', 'site_url' => 'okna-komfort.ru', 'directory_urls' => [], 'dedup_key' => 'site:okna-komfort.ru:2', 'box' => 'field', ]); // третий, с другим сайтом — в группу попасть не должен AutopodborCompetitor::create([ 'tenant_id' => $t->id, 'search_run_id' => $run->id, 'name' => 'Совсем другая фирма', 'site_url' => 'sovsem-drugaya.ru', 'directory_urls' => [], 'dedup_key' => 'site:sovsem-drugaya.ru', 'box' => 'field', ]); $resp = $this->actingAs($u)->getJson('/api/autopodbor/field/duplicate-groups')->assertOk(); $resp->assertJsonCount(1, 'groups'); expect(mb_strtolower($resp->json('groups.0.reason')))->toContain('сайт'); $ids = collect($resp->json('groups.0.competitors'))->pluck('id')->sort()->values()->all(); expect($ids)->toBe(collect([$a->id, $b->id])->sort()->values()->all()); }); it('ОПРОВЕРЖЕНИЕ гипотезы «пустой сайт»: 5 конкурентов БЕЗ сайта (site_url=null) — CompetitorIdentity корректно НЕ склеивает их по «пустому» ключу', function () { [$t, $u, $run] = fpBugSetup(); $names = ['Ромашка', 'Восток', 'Северный Ветер', 'Прогресс', 'Гарант']; foreach ($names as $i => $name) { AutopodborCompetitor::create([ 'tenant_id' => $t->id, 'search_run_id' => $run->id, 'name' => $name, 'site_url' => null, 'directory_urls' => [], 'dedup_key' => 'noSite:'.$i, 'box' => 'field', ]); } $resp = $this->actingAs($u)->getJson('/api/autopodbor/field/duplicate-groups')->assertOk(); // Без сайта, без справочников, без телефонов, без общего адреса — опознавалок нет вовсе, // группировать не по чему. Ожидание: 0 групп. $resp->assertJsonCount(0, 'groups'); }); it('ОПРОВЕРЖЕНИЕ гипотезы «подстрока вместо равенства»: близкие, но РАЗНЫЕ домены не считаются дублями', function () { [$t, $u, $run] = fpBugSetup(); AutopodborCompetitor::create([ 'tenant_id' => $t->id, 'search_run_id' => $run->id, 'name' => 'Окна Комфорт', 'site_url' => 'okna-komfort.ru', 'directory_urls' => [], 'dedup_key' => 'site:okna-komfort.ru', 'box' => 'field', ]); AutopodborCompetitor::create([ 'tenant_id' => $t->id, 'search_run_id' => $run->id, 'name' => 'Окна Комфорт Плюс', 'site_url' => 'okna-komfort-plus.ru', 'directory_urls' => [], 'dedup_key' => 'site:okna-komfort-plus.ru', 'box' => 'field', ]); $resp = $this->actingAs($u)->getJson('/api/autopodbor/field/duplicate-groups')->assertOk(); $resp->assertJsonCount(0, 'groups'); });