Files
portal/app/tests/Unit/Autopodbor/LiveFindCompetitorsTest.php
T

161 lines
6.8 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
use App\Services\Autopodbor\Agent\Aggregator\AggregatorClassifier;
use App\Services\Autopodbor\Agent\Aggregator\AggregatorFilter;
use App\Services\Autopodbor\Agent\ChannelA\CategoryScraper;
use App\Services\Autopodbor\Agent\ChannelA\QueryAnalyzer;
use App\Services\Autopodbor\Agent\ChannelB\ChannelBSearch;
use App\Services\Autopodbor\Agent\ChannelB\ExaSiteFinder;
use App\Services\Autopodbor\Agent\ChannelB\ResearcherClient;
use App\Services\Autopodbor\Agent\ChannelB\ResearcherParser;
use App\Services\Autopodbor\Agent\Dto\FindCompetitorsRequest;
use App\Services\Autopodbor\Agent\FindCompetitorsAssembler;
use App\Services\Autopodbor\Agent\LiveFindCompetitors;
use App\Services\Autopodbor\Agent\Resolve\CompetitorResolver;
use App\Services\Autopodbor\Agent\Search\SearchResultsParser;
use App\Services\Autopodbor\Agent\Similarity\Embedder;
use App\Services\Autopodbor\Agent\Similarity\EmbeddingRelevance;
use App\Services\Autopodbor\AutopodborDedup;
use App\Services\Autopodbor\AutopodborNormalizer;
use Illuminate\Http\Client\Factory as HttpFactory;
use Illuminate\Support\Facades\Config;
// Сквозной офлайн-тест ФИНАЛЬНОГО оркестратора (ZAFIKSIROVANO §0-БИС): АНАЛИЗ → канал А (скрейп
// категории 2ГИС + резолв карточек) → канал В (имена федералов → САЙТ через EXA → резолв) → сборка.
// ИИ-границы фейкаем, страницы — stubPages на реальных фикстурах, exa — Http fake.
// stubPages()/autopodborFixture() — глобальные хелперы из tests/Pest.php.
function fakeAnalyzer(array $queries): QueryAnalyzer
{
return new class($queries) implements QueryAnalyzer
{
public function __construct(private array $queries) {}
public function analyze(string $description, string $region): array
{
return $this->queries;
}
};
}
function fakeResearcher(array $answers): ResearcherClient
{
return new class($answers) implements ResearcherClient
{
public function __construct(private array $answers) {}
public function research(string $system, string $user): string
{
static $i = 0;
return $this->answers[$i++] ?? '[]';
}
};
}
function noAiAssembler(): FindCompetitorsAssembler
{
$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);
}
};
return new FindCompetitorsAssembler(
new AggregatorFilter($nullClassifier),
new AutopodborDedup(new AutopodborNormalizer),
new EmbeddingRelevance($zeroEmbedder),
);
}
it('сквозной: канал А (2ГИС-карточка) + канал В (федерал с сайтом из EXA) попадают в результат', function () {
Config::set('services.exa', ['key' => 'k', 'base_url' => 'https://api.exa.ai', 'timeout_sec' => 30]);
$http = app(HttpFactory::class);
$http->fake(['api.exa.ai/*' => $http->response(['results' => [['url' => 'https://carmoney.ru/']]], 200)]);
$pages = stubPages([
'2gis.ru/krasnoyarsk/search/' => autopodborFixture('2gis-search-kraslombard.html'),
'/firm/' => autopodborFixture('2gis-firm-kraslombard.html'),
// Яндекс по «CarMoney …» вернёт КрасЛомбарда — резолвер отвергнет по несовпадению имени → федерал.
'maps/?text=' => autopodborFixture('yandex-search-kraslombard.html'),
'/maps/org/' => autopodborFixture('yandex-org-kraslombard.html'),
]);
$find = new LiveFindCompetitors(
fakeAnalyzer(['ломбард']),
new CategoryScraper($pages, new SearchResultsParser, maxPages: 2),
new CompetitorResolver($pages),
new ChannelBSearch(fakeResearcher(['[{"name":"CarMoney","type":"федеральная"}]', '[]']), new ResearcherParser),
new ExaSiteFinder($http),
noAiAssembler(),
);
$res = $find->find(new FindCompetitorsRequest(
regionCode: 29,
examples: ['automoney-krsk.ru'],
aboutSelf: ['займы под залог авто', 'lkomega.ru'],
includeFederal: true,
maxCompetitors: 20,
));
$names = array_column($res->competitors, 'name');
expect($names)->toContain('КрасЛомбард'); // канал А
$carmoney = collect($res->competitors)->firstWhere('name', 'CarMoney');
expect($carmoney)->not->toBeNull()
->and($carmoney['is_federal'])->toBeTrue()
->and($carmoney['site_url'])->toBe('carmoney.ru'); // сайт из EXA
});
it('includeFederal=false → канал В выключен, остаётся только местная А', function () {
Config::set('services.exa.key', 'k');
$http = app(HttpFactory::class);
$http->fake(['api.exa.ai/*' => $http->response(['results' => [['url' => 'https://carmoney.ru/']]], 200)]);
$pages = stubPages([
'2gis.ru/krasnoyarsk/search/' => autopodborFixture('2gis-search-kraslombard.html'),
'/firm/' => autopodborFixture('2gis-firm-kraslombard.html'),
]);
$find = new LiveFindCompetitors(
fakeAnalyzer(['ломбард']),
new CategoryScraper($pages, new SearchResultsParser, maxPages: 2),
new CompetitorResolver($pages),
new ChannelBSearch(fakeResearcher(['[{"name":"CarMoney","type":"федеральная"}]']), new ResearcherParser),
new ExaSiteFinder($http),
noAiAssembler(),
);
$res = $find->find(new FindCompetitorsRequest(29, [], ['ломбард', 'lkomega.ru'], false, 20));
$names = array_column($res->competitors, 'name');
expect($names)->toContain('КрасЛомбард')
->and($names)->not->toContain('CarMoney');
});
it('пусто везде → пустой список, без падения', function () {
Config::set('services.exa.key', '');
$find = new LiveFindCompetitors(
fakeAnalyzer(['ничего']),
new CategoryScraper(stubPages([]), new SearchResultsParser, maxPages: 2),
new CompetitorResolver(stubPages([])),
new ChannelBSearch(fakeResearcher(['[]', '[]']), new ResearcherParser),
new ExaSiteFinder(app(HttpFactory::class)),
noAiAssembler(),
);
$res = $find->find(new FindCompetitorsRequest(29, [], ['ничего', 'self.ru'], true, 10));
expect($res->competitors)->toBe([]);
});