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

138 lines
7.7 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
use App\Models\KnowledgeChunk;
use App\Services\Bot\KnowledgeSearch;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
/**
* Разбор пятого прогона четырьмя проверяющими (12.07.2026): поиск берёт соседнюю
* статью, бот выдумывает статусы и даёт юридические гарантии.
*/
beforeEach(function () {
KnowledgeChunk::query()->delete();
Artisan::call('help:rebuild-knowledge');
});
function topArticle(string $question): string
{
$hits = app(KnowledgeSearch::class)->search($question, 3);
expect($hits)->not->toBeEmpty();
return $hits[0]->title;
}
it('поиск берёт статью ПО ТЕМЕ, а не соседнюю с теми же словами', function () {
$cases = [
'как создать проект' => 'Создать проект — пошагово',
'какие поля надо заполнить при создании проекта' => 'Создать проект — пошагово',
'как запустить проект обратно' => 'Пауза и запуск проекта',
'что такое сделка' => 'Сделки',
'можно скачать заявки в csv' => 'Выгрузить сделки',
'есть ли абонентская плата' => 'Тарифы',
'в какое время дня приходят лиды' => 'Выгрузка по проекту',
'у меня один и тот же конкурент два раза' => 'Дубли конкурентов',
'нашел источник, что дальше делать' => 'Проект из найденного источника',
'а бесплатно можно?' => 'Бесплатный тест',
'дубликаты номеров бывают?' => 'Повторы и дубли номеров',
'насколько свежие заявки, сколько времени прошло' => 'Что вы получаете',
];
foreach ($cases as $q => $expected) {
expect(topArticle($q))->toContain($expected);
}
});
it('в инструкции нет выдуманных статусов «Отказ» и «Успешная»', function () {
// Судья 12.07.2026: бот советовал ставить статус «Отказ» — его нет. Пять статусов:
// Новая сделка / Просмотрено / В работе / Сделка / Не реализовано.
$all = KnowledgeChunk::query()->pluck('content')->implode(' ');
expect($all)->not->toContain('статус «Отказ»')
->and($all)->not->toContain('статус «Успешная»');
$deals = KnowledgeChunk::query()
->where('source_path', 'help/sdelki.md')
->orderBy('chunk_index')
->pluck('content')
->implode(' ');
expect($deals)->toContain('«Не реализовано»');
});
it('значение источника менять можно — бот не имеет права говорить «нельзя»', function () {
// Судья 12.07.2026: на «могу ли я поменять сайт в проекте» бот ответил «нельзя,
// заводите новый». Неправда: не меняется только ТИП источника, а сам сайт — меняется.
$hits = app(KnowledgeSearch::class)->search('могу ли я поменять сайт в уже созданном проекте', 3);
$text = collect($hits)->pluck('content')->implode(' ');
expect($hits[0]->title)->toContain('Сменить источник')
->and($text)->toContain('Менять можно значение');
});
it('за повтор номера деньги не берутся, а списание идёт за каждую заявку', function () {
// Судья 12.07.2026: на «списываются ли деньги за дубли» бот ответил, что деньги
// списываются ТОЛЬКО за услуги Конкурентного поля. Это враньё про деньги.
$hits = app(KnowledgeSearch::class)->search('списывается ли деньги за дубли', 3);
expect($hits[0]->title)->toContain('Повторы и дубли номеров');
$article = KnowledgeChunk::query()
->where('source_path', 'help/dubli-nomerov.md')
->orderBy('chunk_index')
->pluck('content')
->implode(' ');
expect($article)->toContain('30 дней')
->and($article)->toContain('дважды за него не заплатите');
});
it('про штрафы бот не даёт гарантий — ответственность на клиенте', function () {
// Судья 12.07.2026: бот категорично обещал «Нет, вас не оштрафуют». Гарантий не даём.
$hits = app(KnowledgeSearch::class)->search('меня оштрафуют если я буду звонить по вашим номерам', 3);
$text = collect($hits)->pluck('content')->implode(' ');
expect($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('support@liderra.ru');
});
it('на старте не требует пополнения — подарок уже на балансе', function () {
foreach (['с чего начать?', 'я только зарегистрировался что дальше'] as $q) {
$hits = app(KnowledgeSearch::class)->search($q, 3);
$text = collect($hits)->pluck('content')->implode(' ');
expect($text)->toContain('1000 ₽');
}
});
/**
* Правило владельца 13.07.2026: замены заявки НЕТ (недозвон браком не считается).
* Прежний текст статьи обещал «заменим» — это была ошибка, и бот её повторял клиентам.
*/
it('«верните деньги за заявку» — находится статья про недозвон, и она НЕ обещает замену', function () {
$hits = app(KnowledgeSearch::class)->search('верните деньги за плохую заявку', 3);
expect(collect($hits)->pluck('title')->implode(' | '))->toContain('не берёт трубку');
$text = collect($hits)->pluck('content')->implode(' ');
expect($text)->not->toContain('заменим')
->and($text)->not->toContain('можем заменить');
});
it('ночью и после лимита — отвечает по статье, без выдумок', function () {
$hits = app(KnowledgeSearch::class)->search('ночью заявки приходят?', 3);
$text = collect($hits)->pluck('content')->implode(' ');
expect($text)->toContain('Ночью заявки не приходят');
});