Files
portal/app/tests/Unit/Autopodbor/Extract/DirectoryParserTest.php
T

286 lines
17 KiB
PHP
Raw Normal View History

<?php
use App\Services\Autopodbor\Agent\Extract\DirectoryParser;
it('берёт ссылки на карточки филиалов из списка 2ГИС', function () {
$html = '<a href="/krasnoyarsk/firm/111">Ф1</a><a href="/krasnoyarsk/firm/222">Ф2</a>'
.'<a href="/krasnoyarsk/firm/111">дубль</a><a href="/about">не филиал</a>';
$links = (new DirectoryParser)->parseBranchList($html);
expect($links)->toBe(['/krasnoyarsk/firm/111', '/krasnoyarsk/firm/222']);
});
it('берёт ссылки на филиалы, даже когда они в данных страницы, а не в href', function () {
// 2ГИС иногда рендерит карточки без чистого href — ссылка лежит в JSON-состоянии страницы
$html = '{"items":[{"link":"/krasnoyarsk/firm/111"},{"link":"/krasnoyarsk/firm/222"}]}'
.'<a href="/krasnoyarsk/firm/111">дубль</a>';
$links = (new DirectoryParser)->parseBranchList($html);
expect($links)->toBe(['/krasnoyarsk/firm/111', '/krasnoyarsk/firm/222']);
});
it('из карточки 2ГИС берёт телефон из tel: и адрес из заголовка', function () {
$html = '<html><head><title>КрасЛомбард, улица Ладо Кецховели, 30, Красноярск — 2ГИС</title></head>'
.'<body><h1>КрасЛомбард</h1><a href="tel:+73912920000">+7 (391) 2...</a></body></html>';
$cards = (new DirectoryParser)->parseFirmCard($html, 'https://2gis.ru/krasnoyarsk/firm/111', '2ГИС');
expect($cards)->toHaveCount(1);
expect($cards[0]->number)->toBe('+73912920000');
expect($cards[0]->office)->toBe('улица Ладо Кецховели, 30');
expect($cards[0]->url)->toBe('https://2gis.ru/krasnoyarsk/firm/111');
expect($cards[0]->source)->toBe('2ГИС');
});
it('схлопывает неразрывные/двойные пробелы в адресе из заголовка', function () {
$html = "<title>КрасЛомбард,\u{00A0}улица Весны,\u{00A0}7а, Красноярск — 2ГИС</title>"
.'<a href="tel:+73912550000">x</a>';
$cards = (new DirectoryParser)->parseFirmCard($html, 'u', '2ГИС');
expect($cards[0]->office)->toBe('улица Весны, 7а');
});
// ── Яндекс.Карты ────────────────────────────────────────────────────────────
it('parseYandexCard: извлекает телефон из div orgpage-phones-view и сайт из business-urls-link', function () {
$html = <<<'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 itemprop="url" class="business-urls-view__link" href="https://automoney-krsk.ru/" target="_blank" rel="nofollow">
<span class="business-urls-view__text">automoney-krsk.ru</span>
</a>
</div>
HTML;
$cards = (new DirectoryParser)->parseYandexCard($html, 'https://yandex.ru/maps/org/avtodom24/8905494012/');
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');
expect($cards[0]->siteUrl)->toContain('automoney-krsk.ru');
expect($cards[0]->source)->toBe('Яндекс.Карты');
});
it('parseYandexCard: дедуп телефонов — wa.me не дублирует основной номер', function () {
$html = <<<'HTML'
<title>Автодом24, микрофинансовая организация, Красноярск, Красная ул., 1 — Яндекс Карты</title>
<div class="orgpage-phones-view__phone-number" dir="ltr">+7 (913) 539-65-46</div>
<a href="https://wa.me/79135396546" role="button" itemprop="sameAs"></a>
<div class="business-urls-view__url">
<a class="business-urls-view__link" href="https://automoney-krsk.ru/"></a>
</div>
HTML;
$cards = (new DirectoryParser)->parseYandexCard($html, 'https://yandex.ru/maps/org/avtodom24/1/');
// wa.me не должен дублировать тот же номер
expect($cards)->toHaveCount(1);
expect($cards[0]->siteUrl)->toContain('automoney-krsk.ru');
});
it('parseYandexCard: если телефонов нет но сайт есть — одна карточка с пустым number', function () {
$html = <<<'HTML'
<title>Тест, Красноярск — Яндекс Карты</title>
<div class="business-urls-view__url">
<a class="business-urls-view__link" href="https://example.com/"></a>
</div>
HTML;
$cards = (new DirectoryParser)->parseYandexCard($html, 'https://yandex.ru/maps/org/test/1/');
expect($cards)->toHaveCount(1);
expect($cards[0]->number)->toBe('');
expect($cards[0]->siteUrl)->toContain('example.com');
expect($cards[0]->source)->toBe('Яндекс.Карты');
});
it('parseYandexCard: нет ни телефона, ни сайта — возвращает пустой массив', function () {
$html = '<title>Тест — Яндекс Карты</title><div>нет контактов</div>';
$cards = (new DirectoryParser)->parseYandexCard($html, 'https://yandex.ru/maps/org/test/2/');
expect($cards)->toBe([]);
});
it('parseYandexCard: адрес офиса берётся из заголовка страницы Яндекса', function () {
$html = <<<'HTML'
<title>Автодом24, микрофинансовая организация, Красноярск, Караульная ул., 88 — Яндекс Карты</title>
<div class="orgpage-phones-view__phone-number" dir="ltr">+7 (913) 539-65-46</div>
HTML;
$cards = (new DirectoryParser)->parseYandexCard($html, 'https://yandex.ru/maps/org/avtodom24/1/');
expect($cards)->not->toBeEmpty();
// officeFromTitle должен отрезать «Автодом24» и «Яндекс Карты», оставить «микрофинансовая организация, Красноярск, Караульная ул., 88»
// Точнее: первый кусок = имя, последний = город; середина = адрес
expect($cards[0]->office)->not->toBeNull();
});
// ── 2ГИС «вскрыть всё»: сайт + все телефоны + описание из карточки ───────────
it('parse2gisCard: сайт берёт из поля url объекта-контакта website, НЕ из value-редиректа', function () {
// Контакты 2ГИС — JSON-массив объектов. У сайта настоящий адрес в "url",
// а в "value" — редирект link.2gis.ru (его брать нельзя).
$html = '<title>AutoMoney, Красноярск, Ленина, 1 — 2ГИС</title>'
.'{"url":"http://automoney-krsk.ru","text":"automoney-krsk.ru","code":"70000001093406076","type":"website","value":"http://link.2gis.ru/redirect/xxx"}'
.'{"contact_text":"+79135396546","type":"phone","value":"+79135396546"}';
$cards = (new DirectoryParser)->parse2gisCard($html, 'https://2gis.ru/krasnoyarsk/firm/111');
expect($cards)->not->toBeEmpty();
expect($cards[0]->siteUrl)->toContain('automoney-krsk.ru');
expect($cards[0]->siteUrl)->not->toContain('link.2gis.ru');
expect($cards[0]->source)->toBe('2ГИС');
});
it('parse2gisCard: берёт ВСЕ телефоны из value объектов phone, без дублей', function () {
$html = '<title>Тест, Красноярск — 2ГИС</title>'
.'{"contact_text":"+7 391 279-33-79","type":"phone","value":"+73912793379"}'
.'{"contact_text":"+7 391 296-01-11","type":"phone","value":"+73912960111"}'
.'{"contact_text":"+7 391 240-00-68","type":"phone","value":"+73912400068"}'
.'{"type":"phone","value":"+73912793379"}'; // дубль первого
$cards = (new DirectoryParser)->parse2gisCard($html, 'https://2gis.ru/krasnoyarsk/firm/222');
$digits = array_map(fn ($c) => preg_replace('/\D/', '', $c->number), $cards);
expect($digits)->toContain('73912793379');
expect($digits)->toContain('73912960111');
expect($digits)->toContain('73912400068');
expect(array_unique($digits))->toHaveCount(3);
});
it('parse2gisCard: достаёт описание «чем занимается» из og:description', function () {
$html = '<title>AutoMoney, Красноярск — 2ГИС</title>'
.'<meta property="og:description" content="Займы под залог ПТС/авто от 2,5% в месяц за 15 минут">'
.'{"contact_text":"+7 913 539-65-46","type":"phone","value":"+79135396546"}';
$cards = (new DirectoryParser)->parse2gisCard($html, 'https://2gis.ru/krasnoyarsk/firm/333');
expect($cards)->not->toBeEmpty();
expect($cards[0]->description)->toContain('Займы под залог');
});
it('parse2gisCard: НЕ берёт служебную заглушку 2ГИС в описание (оценка/фото/отзывы)', function () {
$html = '<title>AutoMoney, Красноярск — 2ГИС</title>'
.'<meta property="og:description" content="Оценка 5, 11 фото, 93 отзыва. Вся информация об этом месте есть в 2ГИС">'
.'{"contact_text":"+7 913 539-65-46","type":"phone","value":"+79135396546"}';
$cards = (new DirectoryParser)->parse2gisCard($html, 'https://2gis.ru/krasnoyarsk/firm/xx');
expect($cards)->not->toBeEmpty();
expect($cards[0]->description)->toBeNull(); // заглушку не пишем
});
it('parse2gisCard: рубрика из заголовка идёт в описание, когда своего описания нет', function () {
// og-описания нет → «чем занимается» берём из рубрики (2-й кусок заголовка): «Имя, РУБРИКА, адрес…, Город».
$html = '<title>Ломбарджини, ломбард, Взлётная улица, 6а, Красноярск — 2ГИС</title>'
.'{"url":"http://lombardzhini.ru","text":"x","code":"1","type":"website"}';
$cards = (new DirectoryParser)->parse2gisCard($html, 'https://2gis.ru/krasnoyarsk/firm/1');
expect($cards[0]->description)->toBe('ломбард');
});
it('parse2gisCard: 2-й кусок-адрес НЕ считается рубрикой (описание пустое)', function () {
// «КрасЛомбард, улица Ладо Кецховели, 30, Красноярск» — 2-й кусок это АДРЕС, не рубрика → описания нет.
$html = '<title>КрасЛомбард, улица Ладо Кецховели, 30, Красноярск — 2ГИС</title>'
.'{"url":"http://k.ru","text":"x","code":"1","type":"website"}';
$cards = (new DirectoryParser)->parse2gisCard($html, 'https://2gis.ru/krasnoyarsk/firm/2');
expect($cards[0]->description)->toBeNull();
});
it('parseYandexCard: рубрика из заголовка идёт в описание', function () {
$html = '<title>Твои Займы, микрофинансовая организация, Ястынская ул., 19А, Красноярск — Яндекс Карты</title>'
.'<a class="business-urls-view__link" href="https://tvoizaymy.ru/"></a>';
$cards = (new DirectoryParser)->parseYandexCard($html, 'https://yandex.ru/maps/org/tvoi_zaymy/1/');
expect($cards[0]->description)->toBe('микрофинансовая организация');
});
it('parse2gisCard: мусор-домены из <script src> не считаются сайтом', function () {
// Счётчики/трекеры (top100, mc.yandex) сидят в <script src>, это НЕ контакт-сайт.
$html = '<title>Тест, Красноярск — 2ГИС</title>'
.'<script src="https://st.top100.ru/top100/top100.js"></script>'
.'<script src="https://mc.yandex.ru/metrika/tag.js"></script>'
.'{"url":"http://realsite.рф","text":"realsite.рф","code":"1","type":"website","value":"http://link.2gis.ru/y"}'
.'{"contact_text":"+7 391 200-00-00","type":"phone","value":"+73912000000"}';
$cards = (new DirectoryParser)->parse2gisCard($html, 'https://2gis.ru/krasnoyarsk/firm/444');
expect($cards[0]->siteUrl)->toContain('realsite');
expect($cards[0]->siteUrl)->not->toContain('top100');
expect($cards[0]->siteUrl)->not->toContain('mc.yandex');
});
it('parse2gisCard: телефонов нет, но сайт есть — одна карточка с пустым number', function () {
$html = '<title>Тест, Красноярск — 2ГИС</title>'
.'{"url":"http://onlysite.ru","text":"onlysite.ru","code":"1","type":"website","value":"http://link.2gis.ru/z"}';
$cards = (new DirectoryParser)->parse2gisCard($html, 'https://2gis.ru/krasnoyarsk/firm/555');
expect($cards)->toHaveCount(1);
expect($cards[0]->number)->toBe('');
expect($cards[0]->siteUrl)->toContain('onlysite.ru');
});
it('parse2gisCard: нет ни телефона, ни сайта — пустой массив', function () {
$html = '<title>Тест — 2ГИС</title><div>нет контактов</div>';
$cards = (new DirectoryParser)->parse2gisCard($html, 'https://2gis.ru/krasnoyarsk/firm/666');
expect($cards)->toBe([]);
});
it('parseYandexCard: достаёт ВСЕ три телефона из скрытого JSON, без дублей', function () {
// Яндекс показывает только первый телефон в видимом блоке,
// остальные прячет за раскрывашкой — но все три лежат в JSON на той же странице.
$html = <<<'HTML'
<title>Автокредит24, Красноярск, Взлётная ул., 5 — Яндекс Карты</title>
<script>
window.__REDUX_STATE__={"phones":[{"number":"+7 (391) 279-33-79","type":"phone","value":"+73912793379"},{"number":"+7 (391) 296-01-11","type":"phone","value":"+73912960111"},{"number":"+7 (391) 240-00-68","type":"phone","value":"+73912400068"}]};
</script>
<div class="orgpage-phones-view__phone-number" dir="ltr">+7 (391) 279-33-79</div>
<div class="business-urls-view__url">
<a class="business-urls-view__link" href="https://autocredit24.ru/"></a>
</div>
HTML;
$cards = (new DirectoryParser)->parseYandexCard($html, 'https://yandex.ru/maps/org/autocredit24/1/');
// Должны получить ровно 3 карточки (по одной на телефон)
expect($cards)->toHaveCount(3);
// Проверяем по цифрам (без +, скобок и т.п.)
$allDigits = array_map(fn ($c) => preg_replace('/\D/', '', $c->number), $cards);
expect($allDigits)->toContain('73912793379');
expect($allDigits)->toContain('73912960111');
expect($allDigits)->toContain('73912400068');
// Нет дублей
expect(array_unique($allDigits))->toHaveCount(3);
// Сайт присутствует у каждой карточки
foreach ($cards as $card) {
expect($card->siteUrl)->toContain('autocredit24.ru');
}
});
it('parse2gisCard: НЕ берёт рекламный телефон-кнопку (caption/platforms), только настоящий контакт', function () {
// Живой случай: на карточке «Ломбард Бодрый» 2ГИС показал рекламный блок «Связаться с
// менеджером» — это кнопка-действие ЧУЖОЙ фирмы (Ломбард Айклауд). У неё caption/platforms и
// НЕТ code/print_text. Её номер брать нельзя: иначе чужой телефон попадает в контакты Бодрого
// и на «Предложениях» даёт ложное «объединить?».
$html = '<title>Ломбард Бодрый, ломбард, Красноярск — 2ГИС</title>'
// настоящий контакт фирмы: с code (id) и print_text
.'{"text":"+7 (391) 296-78-88","code":"70000001085640830","print_text":"+7 (391) 296-78-88","type":"phone","value":"+73912967888"}'
// рекламная кнопка чужой фирмы: caption + platforms, без code/print_text
.'{"caption":"Связаться с менеджером","name":"Связаться с менеджером","platforms":["ios","android","online"],"type":"phone","value":"+79237575575"}';
$cards = (new DirectoryParser)->parse2gisCard($html, 'https://2gis.ru/krasnoyarsk/firm/70000001085640828');
$digits = array_map(fn ($c) => preg_replace('/\D/', '', $c->number), $cards);
expect($digits)->toContain('73912967888'); // настоящий телефон Бодрого — берём
expect($digits)->not->toContain('79237575575'); // рекламный номер Айклауда — НЕ берём
});