Files
portal/app/tests/Feature/Bot/LlmProviderTest.php
T
Дмитрий e969b7700a chore(chat): убрать настройки Jivo — чат теперь свой; экскурсии выключены
Config: jivo_bot -> bot (убраны неиспользуемые webhook_secret/outbound_url/token
- транспорт Jivo уже удалён), jivosite убран целиком (виджет больше не нужен).
Env: JIVO_BOT_* -> BOT_*; JIVO_WIDGET_ID/VITE_JIVO_WIDGET_ID удалены.
tours_enabled выключен по умолчанию (BOT_TOURS_ENABLED=false) — экскурсии
«Показать на портале» уходили не по теме вопроса (живая проверка 13.07.2026),
чинить релевантность отдельно; сторож-тест не даёт включить незаметно.

Заодно убраны обнаруженные хвосты того же виджета, оставшиеся от прежних
задач: JivoLivenessProbe (класс+тест+регистрация в мониторинге внешних
сервисов), плитка «JivoSite» в админ-дашборде, встроенный скрипт виджета
в welcome.blade.php, осиротевший тест resources/js/.../JivoWidget.vue
(компонент уже был удалён ранее), declaration VITE_JIVO_WIDGET_ID.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 08:32:21 +03:00

65 lines
3.0 KiB
PHP

<?php
declare(strict_types=1);
use App\Services\Bot\AitunnelChatClient;
use App\Services\Bot\LlmClient;
use App\Services\Bot\PersonalDataMask;
use App\Services\Bot\YandexGptClient;
use Illuminate\Support\Facades\Http;
/**
* Выбор модели под капотом (замечание владельца 12.07.2026: «может, проблема модели?»).
* Плюс главное ограничение: в зарубежную модель НЕЛЬЗЯ отправлять персональные данные —
* клиент пишет в чат свой телефон. Маскируем до отправки.
*/
it('провайдер выбирается настройкой: яндекс или зарубежная модель через AITunnel', function () {
config()->set('services.bot.llm', 'yandex');
expect(app(LlmClient::class))->toBeInstanceOf(YandexGptClient::class);
config()->set('services.bot.llm', 'aitunnel');
expect(app(LlmClient::class))->toBeInstanceOf(AitunnelChatClient::class);
});
it('маскирует телефоны и почту до отправки в модель', function () {
$mask = app(PersonalDataMask::class);
expect($mask->hide('мой номер +7 916 123-45-67, звоните'))
->toBe('мой номер <телефон>, звоните')
->and($mask->hide('телефон 89161234567'))->toBe('телефон <телефон>')
->and($mask->hide('пишите на ivan.petrov@mail.ru'))->toBe('пишите на <почта>')
->and($mask->hide('лимит 10000 заявок в день'))->toBe('лимит 10000 заявок в день');
});
it('зарубежная модель получает вопрос БЕЗ телефона клиента', function () {
config()->set('services.bot.llm', 'aitunnel');
config()->set('services.aitunnel.key', 'test-key');
config()->set('services.bot.aitunnel_model', 'claude-sonnet-5');
Http::fake(['*' => Http::response([
'choices' => [['message' => ['content' => 'Передал специалисту.']]],
], 200)]);
$answer = app(AitunnelChatClient::class)->complete(
'Ты консультант.',
'мой телефон 89161234567, перезвоните'
);
expect($answer)->toBe('Передал специалисту.');
Http::assertSent(function ($request) {
$body = json_encode($request->data(), JSON_UNESCAPED_UNICODE);
return str_contains($body, '<телефон>')
&& ! str_contains($body, '89161234567')
&& str_contains($body, 'claude-sonnet-5');
});
});
it('на ошибку зарубежной модели отвечает пусто — бот уйдёт в эскалацию', function () {
config()->set('services.aitunnel.key', 'test-key');
Http::fake(['*' => Http::response([], 500)]);
expect(app(AitunnelChatClient::class)->complete('Ты консультант.', 'вопрос'))->toBeNull();
});