[0.0], $texts); } }; $assembler = new FindCompetitorsAssembler( new AggregatorFilter($nullClassifier), new AutopodborDedup(new AutopodborNormalizer), new EmbeddingRelevance($zeroEmbedder), ); return new LiveFindCompetitors( stubPages([ // выдача '2gis.ru/krasnoyarsk/search/' => autopodborFixture('2gis-search-kraslombard.html'), 'maps/?text=' => autopodborFixture('yandex-search-kraslombard.html'), // карточки '/firm/' => autopodborFixture('2gis-firm-kraslombard.html'), '/maps/org/' => autopodborFixture('yandex-org-kraslombard.html'), ]), new SearchResultsParser, new TwoGisResolver, new YandexResolver, $assembler, ); } it('живой поиск по нише → настоящий конкурент, склеенный из 2ГИС и Яндекса', function () { $res = liveEngine()->find(new FindCompetitorsRequest( regionCode: 29, // Красноярский край → город Красноярск, слаг krasnoyarsk examples: [], aboutSelf: ['ломбард', 'moy-lombard.ru'], // ниша + ВАШ сайт (другой — себя НЕ вычитаем) includeFederal: true, maxCompetitors: 20, )); $names = array_column($res->competitors, 'name'); expect($names)->toContain('КрасЛомбард'); // найден живым поиском по нише $kl = collect($res->competitors)->firstWhere('name', 'КрасЛомбард'); expect($kl['site_url'])->toBe('http://kraslombard24.ru'); // склейка одной фирмы из 2ГИС + Яндекс: в «где нашли» обе ссылки справочников expect(collect($kl['directory_urls'])->contains(fn (string $u): bool => str_contains($u, '/firm/')))->toBeTrue(); expect(collect($kl['directory_urls'])->contains(fn (string $u): bool => str_contains($u, '/maps/org/')))->toBeTrue(); }); it('свой сайт вычитается из конкурентов', function () { $res = liveEngine()->find(new FindCompetitorsRequest( regionCode: 29, examples: [], aboutSelf: ['ломбард', 'kraslombard24.ru'], // теперь КрасЛомбард = это «мы» includeFederal: true, maxCompetitors: 20, )); expect(array_column($res->competitors, 'name'))->not->toContain('КрасЛомбард'); }); it('повторяет флакующую выдачу 2ГИС, пока не появятся фирмы', function () { // первый запрос страницы 2ГИС-поиска — пусто (капча/флак), второй — реальная выдача $pages = new class implements PageFetcher { private int $searchHits = 0; public function html(string $url): string { if (str_contains($url, '/search/')) { $this->searchHits++; return $this->searchHits >= 2 ? autopodborFixture('2gis-search-kraslombard.html') : ''; } if (str_contains($url, '/firm/')) { return autopodborFixture('2gis-firm-kraslombard.html'); } return ''; // Яндекс пропускаем } }; $nullClassifier = new class implements AggregatorClassifier { public function isAggregator(string $name, ?string $siteUrl, ?string $description): ?bool { return null; } }; $zeroEmbedder = new class implements Embedder { public function embed(array $texts): array { return array_map(fn () => [0.0], $texts); } }; $engine = new LiveFindCompetitors( $pages, new SearchResultsParser, new TwoGisResolver, new YandexResolver, new FindCompetitorsAssembler( new AggregatorFilter($nullClassifier), new AutopodborDedup(new AutopodborNormalizer), new EmbeddingRelevance($zeroEmbedder), ), ); $res = $engine->find(new FindCompetitorsRequest(29, [], ['ломбард', 'moy.ru'], true, 20)); expect(array_column($res->competitors, 'name'))->toContain('КрасЛомбард'); }); it('пустая выдача → пустой список, без падения', function () { $engine = new LiveFindCompetitors( stubPages([]), // на всё '' new SearchResultsParser, new TwoGisResolver, new YandexResolver, new FindCompetitorsAssembler( new AggregatorFilter(new class implements AggregatorClassifier { public function isAggregator(string $name, ?string $siteUrl, ?string $description): ?bool { return null; } }), new AutopodborDedup(new AutopodborNormalizer), new EmbeddingRelevance(new class implements Embedder { public function embed(array $texts): array { return array_map(fn () => [0.0], $texts); } }), ), ); $res = $engine->find(new FindCompetitorsRequest(29, [], ['ничего', 'self.ru'], false, 10)); expect($res->competitors)->toBe([]); });