html($u); } return $out; } }; } function fakeReader(): TwoGisReader { return new class implements TwoGisReader { public function listing(string $payload): array { return []; } public function card(string $payload, string $url): array { if ($payload === 'CLIENT') { return [new DirectoryCard(number: '8 (391) 200-20-05', office: null, url: $url, source: '2ГИС', siteUrl: null, description: null)]; } if ($payload === 'FOREIGN') { return [new DirectoryCard(number: '8 (391) 230-33-03', office: null, url: $url, source: '2ГИС', siteUrl: 'http://salon.ru', description: 'Парикмахерские')]; } return []; } public function branchList(string $payload): array { return $payload === 'SEARCH' ? ['/krasnoyarsk/firm/FOREIGN'] : []; } }; } function emptyOpener(): SiteOpener { $fetcher = new class implements Fetcher { public function site(string $url): FetchedSite { return new FetchedSite($url, ''); } public function directory(string $url): array { return []; } }; return new SiteOpener($fetcher); } it('привязанная карточка = якорь: её номер в источниках, чужой салон из слепого поиска отсеян', function () { $collector = new DeepStudyCollector(xfetch: fakeXfetch(), opener: emptyOpener(), reader: fakeReader()); $fp = new Fingerprint('Притяжение', '', 'Красноярск', '391', 'krasnoyarsk', false, '', [CLIENT_CARD]); $calls = array_map( fn ($r) => $r['identifier'], array_values(array_filter($collector->collect($fp), fn ($r) => $r['signal_type'] === 'call')), ); expect($calls)->toContain('73912002005') // номер с привязанной карточки — есть ->and($calls)->not->toContain('73912303303'); // салон из слепого поиска — отсеян }); it('нет якоря совсем (без сайта и без карточек) — слепой поиск не берётся, пусто', function () { $collector = new DeepStudyCollector(xfetch: fakeXfetch(), opener: emptyOpener(), reader: fakeReader()); $fp = new Fingerprint('Притяжение', '', 'Красноярск', '391', 'krasnoyarsk', false, '', []); expect($collector->collect($fp))->toBe([]); });