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(); });