Files
portal/app/tests/Unit/Autopodbor/ChannelB/AitunnelResearcherTest.php
T
Дмитрий 5a65165114 feat(автоподбор): движок шага 1 пересобран под финал v4 (каналы А+В, EXA)
Замена вырожденного «одна фраза → одна страница» на §12/§11.3 финал:
- Шаг АНАЛИЗ (ChannelA\AitunnelQueryAnalyzer): описание → запросы-рубрики (мелкая модель).
- Канал А (ChannelA\CategoryScraper): скрейп категории 2ГИС с пагинацией → резолв карточек.
- Канал В (ChannelB\*): ОДНА модель sonar-reasoning-pro × 2 прохода → ТОЛЬКО имена
  федералов; стоп-лист = имена из А + примеры; сайт федерала через EXA (ExaSiteFinder),
  т.к. у федерала нет карточки в 2ГИС/Яндексе на регион.
- Оркестратор LiveFindCompetitors переписан: АНАЛИЗ→А→В→слияние→отсев→дедуп→похожесть→DTO.
- Провайдер перепрошит; config services.php +research_model/exa.
Похожесть — эмбеддер-модель (математически), резолвер/дедуп — без изменений.
Всё за тонкими границами, офлайн-тесты на фикстурах: модуль 130 unit + 74 feature зелёные.
Провайдер за флагом autopodbor.real_find; на проде не меняется.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 21:06:10 +03:00

61 lines
2.1 KiB
PHP

<?php
declare(strict_types=1);
use App\Services\Autopodbor\Agent\ChannelB\AitunnelResearcher;
use Illuminate\Http\Client\Factory as HttpFactory;
use Illuminate\Support\Facades\Config;
// Живая модель канала В за границей ResearcherClient. Сеть мокаем (Http fake) — без интернета.
it('без ключа возвращает пустой массив (движок деградирует, не падает)', function () {
Config::set('services.aitunnel.key', '');
$r = new AitunnelResearcher(app(HttpFactory::class));
expect($r->research('sys', 'user'))->toBe('[]');
});
it('шлёт system+user в chat/completions и возвращает content', function () {
Config::set('services.aitunnel', [
'key' => 'test-key',
'base_url' => 'https://api.aitunnel.ru/v1',
'research_model' => 'sonar-reasoning-pro',
'research_timeout_sec' => 120,
]);
$http = app(HttpFactory::class);
$http->fake([
'*/chat/completions' => $http->response([
'choices' => [['message' => ['content' => '[{"name":"CarMoney","type":"федеральная"}]']]],
], 200),
]);
$r = new AitunnelResearcher($http);
$out = $r->research('SYS-PROMPT', 'USER-PROMPT');
expect($out)->toContain('CarMoney');
$http->assertSent(function ($request) {
$body = $request->data();
return str_contains($request->url(), '/chat/completions')
&& $body['model'] === 'sonar-reasoning-pro'
&& $body['messages'][0]['role'] === 'system'
&& $body['messages'][0]['content'] === 'SYS-PROMPT'
&& $body['messages'][1]['role'] === 'user'
&& $body['messages'][1]['content'] === 'USER-PROMPT';
});
});
it('при ошибке сети возвращает пустой массив', function () {
Config::set('services.aitunnel.key', 'test-key');
$http = app(HttpFactory::class);
$http->fake(['*/chat/completions' => $http->response('boom', 500)]);
$r = new AitunnelResearcher($http);
expect($r->research('sys', 'user'))->toBe('[]');
});