Files
portal/app/tests/Feature/Bot/GeminiFixesTest.php
T

78 lines
3.9 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
use App\Models\KnowledgeChunk;
use App\Services\Bot\BotAnswerService;
use App\Services\Bot\ChatTextCleaner;
use App\Services\Bot\KnowledgeSearch;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
uses(RefreshDatabase::class);
/**
* Слепое сравнение моделей 12.07.2026: Gemini выиграл у Яндекса 62:36, но судьи нашли
* пять болезней — враньё про платный повтор сбора, цены по памяти, «приглашу специалиста»,
* гарантии и markdown-разметка в чате.
*/
beforeEach(function () {
config()->set('services.yandexgpt', [
'api_key' => 'k', 'folder_id' => 'f', 'model' => 'yandexgpt-lite/latest',
'endpoint' => 'https://llm.api.cloud.yandex.net/foundationModels/v1/completion',
'timeout_seconds' => 8,
]);
KnowledgeChunk::query()->delete();
Artisan::call('help:rebuild-knowledge');
});
it('в чат не уходит markdown-разметка — Jivo её не рисует', function () {
$cleaner = app(ChatTextCleaner::class);
expect($cleaner->clean('**Важно:** пишите на `support@liderra.ru`'))
->toBe('Важно: пишите на support@liderra.ru')
->and($cleaner->clean("Причины:\n* первая\n* вторая"))
->toBe("Причины:\n— первая\n— вторая")
->and($cleaner->clean('### Заголовок'))->toBe('Заголовок');
});
it('ответ бота приходит клиенту без звёздочек и кавычек', function () {
Http::fake(['*' => Http::response([
'result' => ['alternatives' => [['message' => ['text' => '**Списания** видно в разделе `Биллинг`.']]]],
], 200)]);
$answer = app(BotAnswerService::class)->answer('где посмотреть списания');
expect($answer->text)->not->toContain('**')
->and($answer->text)->not->toContain('`')
->and($answer->text)->toContain('Списания');
});
it('статья честно предупреждает: повторный сбор платный даже без результата', function () {
// Судья 12.07.2026: обе модели соврали на «источников не нашлось — деньги спишутся?».
// Первый сбор без результата бесплатен, ПОВТОРНЫЙ списывается всегда.
$hits = app(KnowledgeSearch::class)->search('источников не нашлось, деньги спишутся?', 3);
$text = collect($hits)->pluck('content')->implode(' ');
expect($text)->toContain('Повторный сбор списывается всегда')
->and($text)->toContain('даже если ничего не нашлось');
});
it('правила для модели запрещают гарантии, цены по памяти и «приглашу специалиста»', function () {
// Правила живут в системном промпте — проверяем, что они там есть и доезжают до модели.
Http::fake(['*' => Http::response([
'result' => ['alternatives' => [['message' => ['text' => 'Ответ.']]]],
], 200)]);
app(BotAnswerService::class)->answer('сколько стоит заявка');
Http::assertSent(function ($request) {
$system = (string) ($request->data()['messages'][0]['text'] ?? '');
return str_contains($system, 'Никогда не называй цены по памяти')
&& str_contains($system, 'Не обещай «приглашу специалиста»')
&& str_contains($system, 'Не давай гарантий')
&& str_contains($system, 'Без markdown');
});
});