$map */ public function __construct(private array $map) {} public function html(string $url): string { return $this->map[$url] ?? ''; } }; } it('по одному личному номеру находит карточку проекта: её сайт и телефон', function () { $search = 'https://2gis.ru/krasnoyarsk/search/73912273531'; $firm = 'https://2gis.ru/krasnoyarsk/firm/111'; $fetch = fakeFetcher([ $search => 'Звезда, жилой комплекс', $firm => '{"url":"https://zvezda-dsk.ru","text":"сайт","type":"website"}' .'{"type":"phone","value":"+73912002288"}', ]); $exp = new PhoneReverseExpander($fetch); $res = $exp->expand('krasnoyarsk', ['73912273531'], []); $domains = array_column($res->sites, 'domain'); $phones = array_column($res->phones, 'digits'); expect($domains)->toContain('zvezda-dsk.ru') ->and($phones)->toContain('73912002288'); }); it('общий номер (фирм больше порога) пропускается — находок нет', function () { $search = 'https://2gis.ru/krasnoyarsk/search/78001234567'; $links = ''; foreach (range(201, 205) as $id) { $links .= 'Фирма '.$id.''; } $fetch = fakeFetcher([$search => $links]); $exp = new PhoneReverseExpander($fetch, crowdedCap: 4); $res = $exp->expand('krasnoyarsk', ['78001234567'], []); expect($res->sites)->toBe([]) ->and($res->phones)->toBe([]); }); it('пустой слаг города — разворот невозможен, пустой результат', function () { $exp = new PhoneReverseExpander(fakeFetcher([])); $res = $exp->expand('', ['73912273531'], []); expect($res->sites)->toBe([])->and($res->phones)->toBe([]); }); it('копает до талого при maxDepth>=2: телефон A → карточка с телефоном B → карточка B даёт сайт проекта', function () { $searchA = 'https://2gis.ru/krasnoyarsk/search/73912273531'; $firm1 = 'https://2gis.ru/krasnoyarsk/firm/111'; $searchB = 'https://2gis.ru/krasnoyarsk/search/73912002288'; $firm2 = 'https://2gis.ru/krasnoyarsk/firm/222'; $fetch = fakeFetcher([ $searchA => 'Управляющая компания', $firm1 => '{"type":"phone","value":"+73912002288"}', $searchB => 'Аллея парк, жилой комплекс', $firm2 => '{"url":"https://alleya-park.ru","type":"website"}{"type":"phone","value":"+73912002288"}', ]); $exp = new PhoneReverseExpander($fetch, maxDepth: 2); $res = $exp->expand('krasnoyarsk', ['73912273531'], []); expect(array_column($res->sites, 'domain'))->toContain('alleya-park.ru'); }); it('по умолчанию (глубина 1) НЕ уходит в цепочку — только фирмы с ЛИЧНЫМ номером, дрейф отсечён', function () { // Те же данные, что и в тесте «до талого», но БЕЗ maxDepth (по умолчанию 1). // firm/222 (Аллея парк) достижим только по номеру, найденному на firm/111 — цепочкой. // При глубине 1 мы туда не идём: это и есть защита от дрейфа в чужие фирмы. $searchA = 'https://2gis.ru/krasnoyarsk/search/73912273531'; $firm1 = 'https://2gis.ru/krasnoyarsk/firm/111'; $searchB = 'https://2gis.ru/krasnoyarsk/search/73912002288'; $firm2 = 'https://2gis.ru/krasnoyarsk/firm/222'; $fetch = fakeFetcher([ $searchA => 'Управляющая компания', $firm1 => '{"type":"phone","value":"+73912002288"}', $searchB => 'Аллея парк, жилой комплекс', $firm2 => '{"url":"https://alleya-park.ru","type":"website"}{"type":"phone","value":"+73912002288"}', ]); $exp = new PhoneReverseExpander($fetch); $res = $exp->expand('krasnoyarsk', ['73912273531'], []); expect(array_column($res->sites, 'domain'))->not->toContain('alleya-park.ru'); }); it('одну и ту же карточку не открывает дважды (visited-set)', function () { $searchA = 'https://2gis.ru/krasnoyarsk/search/73912273531'; $firm1 = 'https://2gis.ru/krasnoyarsk/firm/111'; $searchB = 'https://2gis.ru/krasnoyarsk/search/73912002288'; $fetch = new class($searchA, $firm1, $searchB) implements PageFetcher { public int $opens = 0; public function __construct(private string $sA, private string $f1, private string $sB) {} public function html(string $url): string { if ($url === $this->sA || $url === $this->sB) { return 'Фирма'; } if ($url === $this->f1) { $this->opens++; return '{"type":"phone","value":"+73912002288"}'; } return ''; } }; $exp = new PhoneReverseExpander($fetch); $exp->expand('krasnoyarsk', ['73912273531'], []); expect($fetch->opens)->toBe(1); }); it('потолок карточек не превышается', function () { $searchA = 'https://2gis.ru/krasnoyarsk/search/73912273531'; $fetch = new class($searchA) implements PageFetcher { public array $opens = []; public function __construct(private string $sA) {} public function html(string $url): string { if ($url === $this->sA) { return 'AB'; } $this->opens[] = $url; return '{"type":"phone","value":"+7391200'.substr($url, -4).'"}'; } }; $exp = new PhoneReverseExpander($fetch, maxFirms: 1); $exp->expand('krasnoyarsk', ['73912273531'], []); expect(count($fetch->opens))->toBe(1); }); it('уже известный домен фирмы в находки не повторяет (не дублирует главную карточку)', function () { $searchA = 'https://2gis.ru/krasnoyarsk/search/73912273531'; $firm1 = 'https://2gis.ru/krasnoyarsk/firm/111'; $fetch = fakeFetcher([ $searchA => 'ДСК', $firm1 => '{"url":"https://gk-dsk.ru","type":"website"}', ]); $exp = new PhoneReverseExpander($fetch); $res = $exp->expand('krasnoyarsk', ['73912273531'], ['gk-dsk.ru']); expect(array_column($res->sites, 'domain'))->not->toContain('gk-dsk.ru'); }); it('сайт без схемы в карточке — добавляем https://', function () { $search = 'https://2gis.ru/krasnoyarsk/search/73912273531'; $firm = 'https://2gis.ru/krasnoyarsk/firm/111'; $fetch = fakeFetcher([ $search => 'Проект', $firm => '{"url":"zvezda-dsk.ru","type":"website"}', ]); $exp = new PhoneReverseExpander($fetch); $res = $exp->expand('krasnoyarsk', ['73912273531'], []); $site = array_values(array_filter($res->sites, fn ($s) => $s['domain'] === 'zvezda-dsk.ru'))[0] ?? null; expect($site)->not->toBeNull() ->and($site['site_url'])->toBe('https://zvezda-dsk.ru'); }); it('ровно порог фирм — НЕ общая линия, находки берём', function () { $search = 'https://2gis.ru/krasnoyarsk/search/73912273531'; $links = ''; foreach (range(301, 304) as $id) { // ровно 4 = crowdedCap → не общая линия $links .= 'Фирма '.$id.''; } $map = [ $search => $links, 'https://2gis.ru/krasnoyarsk/firm/301' => '{"url":"https://proekt-301.ru","type":"website"}', 'https://2gis.ru/krasnoyarsk/firm/302' => '', 'https://2gis.ru/krasnoyarsk/firm/303' => '', 'https://2gis.ru/krasnoyarsk/firm/304' => '', ]; $exp = new PhoneReverseExpander(fakeFetcher($map), crowdedCap: 4); $res = $exp->expand('krasnoyarsk', ['73912273531'], []); expect(array_column($res->sites, 'domain'))->toContain('proekt-301.ru'); });