Files
portal/app/tests/Unit/Autopodbor/Fetch/XfetchDirectoryFetcherTest.php
T
Дмитрий df83fa836c feat(автоподбор): Конкурентное поле — финальный проход склейки, вскрыть всё, группы дублей, телефоны, отсев рекламных номеров 2ГИС
Шаг 1 «Конкурентное поле» — чекпоинт:
- Финальный проход «Найти и объединить дубли» на поле и предложениях; клиент решает по каждой группе, ничего не склеиваем молча.
- Тихая склейка только по сайту и коду справочника; телефон и людный номер у более чем 4 фирм — на решение клиента.
- Колонка phones jsonb; телефон-дубли видны уже на «Предложениях».
- Sonar даёт только имя и тип, сайт всегда через EXA; вскрываем все карточки без гейта «есть сайт».
- Яндекс-карточки через локальный Playwright параллельно; рубрика из заголовка идёт в описание.
- Фикс рекламных номеров: 2ГИС-парсер разбирает телефоны пообъектно и выкидывает рекламные кнопки с platforms/caption чужих фирм.

Тесты: автоподбор 224/224 unit, 105/105 feature; фронт-спеки зелёные. Времянки app/scripts/diag-*/probe-* в гит не входят.

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

160 lines
7.6 KiB
PHP

<?php
use App\Services\Autopodbor\Agent\Fetch\BatchPageFetcher;
use App\Services\Autopodbor\Agent\Fetch\PageFetcher;
use App\Services\Autopodbor\Agent\Fetch\XfetchDirectoryFetcher;
/** @param array<string,string> $pages */
function fakePages(array $pages): PageFetcher
{
return new class($pages) implements PageFetcher
{
/** @param array<string,string> $pages */
public function __construct(private array $pages) {}
public function html(string $url): string
{
return $this->pages[$url] ?? '';
}
};
}
/**
* Batch-загрузчик для теста: помнит, какими пачками звали htmlBatch (проверяем параллельный путь).
*
* @param array<string,string> $pages
*/
function fakeBatchPages(array $pages): BatchPageFetcher
{
return new class($pages) implements BatchPageFetcher
{
/** @var list<list<string>> */
public array $batchCalls = [];
/** @param array<string,string> $pages */
public function __construct(private array $pages) {}
public function html(string $url): string
{
return $this->pages[$url] ?? '';
}
public function htmlBatch(array $urls): array
{
$this->batchCalls[] = array_values($urls);
$out = [];
foreach ($urls as $u) {
$out[$u] = $this->pages[$u] ?? '';
}
return $out;
}
};
}
it('обходит список 2ГИС и собирает карточки филиалов с адресом', function () {
$fake = fakePages([
'https://2gis.ru/krasnoyarsk/search/X' => '<a href="/krasnoyarsk/firm/111">ф1</a><a href="/krasnoyarsk/firm/222">ф2</a>',
'https://2gis.ru/krasnoyarsk/firm/111' => '<title>КрасЛомбард, улица Весны, 7, Красноярск — 2ГИС</title><a href="tel:+73912550000">x</a>',
'https://2gis.ru/krasnoyarsk/firm/222' => '<title>КрасЛомбард, проспект Мира, 10, Красноярск — 2ГИС</title><a href="tel:+73912920000">x</a>',
]);
$cards = (new XfetchDirectoryFetcher($fake))->directory('https://2gis.ru/krasnoyarsk/search/X');
expect($cards)->toHaveCount(2);
$byNum = collect($cards)->keyBy('number');
expect($byNum['+73912550000']->office)->toBe('улица Весны, 7');
expect($byNum['+73912550000']->source)->toBe('2ГИС');
expect($byNum['+73912920000']->office)->toBe('проспект Мира, 10');
});
it('повторяет загрузку списка 2ГИС при флаки-рендере без ссылок на филиалы', function () {
// 1-й рендер списка — «ошибочная оболочка» без ссылок (флак); 2-й — нормальный список.
$seq = new class implements PageFetcher
{
public int $listCalls = 0;
public function html(string $url): string
{
if (str_contains($url, '/branches/') || str_contains($url, '/search/')) {
$this->listCalls++;
return $this->listCalls === 1
? '<div>Подождите, идёт загрузка…</div>' // флак: ссылок на филиалы нет
: '<a href="/krasnoyarsk/firm/111">ф1</a>'; // повтор удачен
}
return '<title>КрасЛомбард, улица Весны, 7, Красноярск — 2ГИС</title><a href="tel:+73912550000">x</a>';
}
};
$cards = (new XfetchDirectoryFetcher($seq))->directory('https://2gis.ru/krasnoyarsk/branches/985');
expect($cards)->toHaveCount(1);
expect($seq->listCalls)->toBe(2); // список перезагрузили после флака
});
it('карточки филиалов грузятся ОДНОЙ пачкой (параллельно) при batch-загрузчике', function () {
$fake = fakeBatchPages([
'https://2gis.ru/krasnoyarsk/search/X' => '<a href="/krasnoyarsk/firm/111">ф1</a><a href="/krasnoyarsk/firm/222">ф2</a>',
'https://2gis.ru/krasnoyarsk/firm/111' => '<title>КрасЛомбард, улица Весны, 7, Красноярск — 2ГИС</title><a href="tel:+73912550000">x</a>',
'https://2gis.ru/krasnoyarsk/firm/222' => '<title>КрасЛомбард, проспект Мира, 10, Красноярск — 2ГИС</title><a href="tel:+73912920000">x</a>',
]);
$cards = (new XfetchDirectoryFetcher($fake))->directory('https://2gis.ru/krasnoyarsk/search/X');
// те же карточки, что и в последовательном пути
expect($cards)->toHaveCount(2);
// но забраны ОДНИМ пакетным вызовом (не по одной)
expect($fake->batchCalls)->toHaveCount(1)
->and($fake->batchCalls[0])->toContain('https://2gis.ru/krasnoyarsk/firm/111')
->and($fake->batchCalls[0])->toContain('https://2gis.ru/krasnoyarsk/firm/222');
});
it('site() тянет HTML конкурента через тот же загрузчик', function () {
$fake = fakePages(['https://k.ru' => '<a href="tel:+73912920000">x</a>']);
$site = (new XfetchDirectoryFetcher($fake))->site('https://k.ru');
expect($site->url)->toBe('https://k.ru')
->and($site->rawHtml)->toContain('tel:+73912920000');
});
// ── Яндекс.Карты роутинг ────────────────────────────────────────────────────
it('directory(): яндекс-URL обрабатывается как одиночная карточка (не список филиалов)', function () {
$yandexUrl = 'https://yandex.ru/maps/org/avtodom24/8905494012/';
$yandexHtml = <<<'HTML'
<title>Автодом24, микрофинансовая организация, Красноярск, Караульная ул., 88 — Яндекс Карты</title>
<div class="orgpage-phones-view__phone-number" dir="ltr">+7 (913) 539-65-46</div>
<div class="business-urls-view__url">
<a class="business-urls-view__link" href="https://automoney-krsk.ru/"></a>
</div>
HTML;
$fake = fakePages([$yandexUrl => $yandexHtml]);
$cards = (new XfetchDirectoryFetcher($fake))->directory($yandexUrl);
// должна быть хотя бы одна карточка с телефоном
expect($cards)->not->toBeEmpty();
// номер хранится в сыром виде «+7 (913) 539-65-46» — проверяем по цифрам
$digitsAll = implode('', array_map(fn ($c) => preg_replace('/\D/', '', $c->number), $cards));
expect($digitsAll)->toContain('9135396546');
// siteUrl тоже должен прийти
expect($cards[0]->siteUrl)->toContain('automoney-krsk.ru');
expect($cards[0]->source)->toBe('Яндекс.Карты');
});
it('directory(): 2ГИС-URL по-прежнему идёт через parseBranchList (не сломан яндекс-роутингом)', function () {
$fake = fakePages([
'https://2gis.ru/krasnoyarsk/search/Y' => '<a href="/krasnoyarsk/firm/333">ф1</a>',
'https://2gis.ru/krasnoyarsk/firm/333' => '<title>Ломбард, ул. Мира, 5, Красноярск — 2ГИС</title><a href="tel:+73912000001">x</a>',
]);
$cards = (new XfetchDirectoryFetcher($fake))->directory('https://2gis.ru/krasnoyarsk/search/Y');
expect($cards)->toHaveCount(1);
expect($cards[0]->number)->toBe('+73912000001');
expect($cards[0]->source)->toBe('2ГИС');
});