171 lines
11 KiB
PHP
171 lines
11 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
use App\Services\Bot\AnswerGuard;
|
||
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||
|
|
|
||
|
|
uses(RefreshDatabase::class);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Находки прогона 250 вопросов × 2 режима (14.07.2026, гость + вошедший клиент).
|
||
|
|
* Судьи прочитали 500 ответов; цены оказались вылечены полностью, но вылез другой пласт:
|
||
|
|
* бот путает гостя с вошедшим клиентом, обещает объём и выдумывает пороги ступеней.
|
||
|
|
*
|
||
|
|
* Разбор с цитатами: docs/superpowers/findings/2026-07-14-bot-250-both-modes-run.md
|
||
|
|
*/
|
||
|
|
$card = <<<'CARD'
|
||
|
|
ДАННЫЕ ЭТОГО КЛИЕНТА (посчитаны только что):
|
||
|
|
Баланс: 11 025 ₽ ≈ 200 заявок, при текущем заказе хватит примерно на 10 дней.
|
||
|
|
Ступень: 1-я, 55 ₽ за заявку, получено в этом месяце 180 заявок, до следующей ступени — 320 заявок.
|
||
|
|
Проекты (3): «Стоматология — Москва» — работает, лимит 20 в день, сегодня 0; «Импланты — Санкт-Петербург» — на паузе с 14.07 (паузу поставил клиент); «Отбеливание — Казань» — остановлен из-за нехватки денег.
|
||
|
|
Заявки: сегодня 0, вчера 12, за 7 дней 25, за месяц 25.
|
||
|
|
Списано за месяц: 1 375 ₽. Последнее пополнение: 5 000 ₽ 11.07.2026.
|
||
|
|
CARD;
|
||
|
|
|
||
|
|
it('гостю не приписывают счёт и подарок: у него их нет', function () {
|
||
|
|
$guard = app(AnswerGuard::class);
|
||
|
|
|
||
|
|
$text = 'У вас уже есть 1000 ₽ подарка — они лежат на счёте, пополнять ничего не нужно. При регистрации мы дарим 1000 ₽ на баланс.';
|
||
|
|
$clean = $guard->clean($text, 'При регистрации мы дарим 1000 ₽ на баланс.', personalFactsKnown: false);
|
||
|
|
|
||
|
|
expect($clean)->not->toContain('У вас уже есть')
|
||
|
|
->and($clean)->not->toContain('лежат на счёте')
|
||
|
|
// Общее объяснение про подарок остаётся: человек не должен остаться без ответа.
|
||
|
|
->and($clean)->toContain('дарим 1000 ₽');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('вошедшему не называют его баланс подарком портала', function () use ($card) {
|
||
|
|
$guard = app(AnswerGuard::class);
|
||
|
|
|
||
|
|
$text = 'У вас на балансе 11 025 ₽ — это подарок портала, первые заявки пойдут за его счёт.';
|
||
|
|
$clean = $guard->clean($text, $card, personalFactsKnown: true);
|
||
|
|
|
||
|
|
expect($clean)->not->toContain('подарок портала');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('давнему клиенту не подсовывают сценарий новичка «живите на подарок»', function () use ($card) {
|
||
|
|
$guard = app(AnswerGuard::class);
|
||
|
|
|
||
|
|
// Подарок в 1000 ₽ этот клиент потратил давно: баланс 11 025 ₽, за месяц списано 1 375 ₽.
|
||
|
|
$text = 'Баланс — у вас уже есть. Первые заявки идут за счёт подарка, пополнять ничего не нужно. Проект создаётся в разделе «Проекты».';
|
||
|
|
$clean = $guard->clean($text, $card, personalFactsKnown: true);
|
||
|
|
|
||
|
|
expect($clean)->not->toContain('за счёт подарка')
|
||
|
|
->and($clean)->not->toContain('пополнять ничего не нужно')
|
||
|
|
->and($clean)->toContain('разделе «Проекты»');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('новичку, у которого подарок нетронут, про подарок сказать можно', function () {
|
||
|
|
$guard = app(AnswerGuard::class);
|
||
|
|
|
||
|
|
$freshCard = "ДАННЫЕ ЭТОГО КЛИЕНТА (посчитаны только что):\nБаланс: 1000 ₽ ≈ 18 заявок.\n";
|
||
|
|
$text = 'Первые заявки идут за счёт подарка, пополнять ничего не нужно.';
|
||
|
|
|
||
|
|
expect($guard->clean($text, $freshCard, personalFactsKnown: true))->toContain('за счёт подарка');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('вошедшему не врут про состояние его проектов', function () use ($card) {
|
||
|
|
$guard = app(AnswerGuard::class);
|
||
|
|
|
||
|
|
// Правда: работает ОДИН проект, один на паузе, один остановлен из-за денег.
|
||
|
|
$text = 'У вас сейчас три проекта: два работают («Стоматология — Москва» и «Отбеливание — Казань»), один на паузе.';
|
||
|
|
$clean = $guard->clean($text, $card, personalFactsKnown: true);
|
||
|
|
|
||
|
|
expect($clean)->not->toContain('два работают');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('вошедшему не выдают остановленный проект за работающий', function () use ($card) {
|
||
|
|
$guard = app(AnswerGuard::class);
|
||
|
|
|
||
|
|
$text = 'Проект «Отбеливание — Казань» работает, заявки по нему идут.';
|
||
|
|
$clean = $guard->clean($text, $card, personalFactsKnown: true);
|
||
|
|
|
||
|
|
expect($clean)->not->toContain('«Отбеливание — Казань» работает');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('бот не обещает, что от повышения лимита заявок станет больше', function () {
|
||
|
|
$guard = app(AnswerGuard::class);
|
||
|
|
|
||
|
|
$text = 'Лимит — это потолок, а не гарантия. Если заявок стабильно меньше, чем нужно, — увеличьте лимит, и их будет больше.';
|
||
|
|
$clean = $guard->clean($text, 'Лимит — это потолок, а не гарантия.', personalFactsKnown: false);
|
||
|
|
|
||
|
|
expect($clean)->toContain('потолок')
|
||
|
|
->and($clean)->not->toContain('их будет больше');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('бот не заявляет «такого не бывает» про списание без заявки', function () {
|
||
|
|
$guard = app(AnswerGuard::class);
|
||
|
|
|
||
|
|
$text = 'Такого не бывает — каждое списание привязано к конкретной заявке. Проверить каждое списание можно на вкладке «Списания».';
|
||
|
|
$clean = $guard->clean($text, 'Проверить каждое списание можно на вкладке «Списания».', personalFactsKnown: false);
|
||
|
|
|
||
|
|
expect($clean)->not->toContain('Такого не бывает')
|
||
|
|
->and($clean)->toContain('вкладке «Списания»');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('номер и заявка — одно и то же: «поштучно не продаём» бот больше не говорит', function () {
|
||
|
|
// Живой лог 14.07.2026, замечание владельца: «номера это и есть заявки, а он их делит».
|
||
|
|
// Мы продаём любой объём, хоть одну штуку — отказа в поштучной продаже нет и не было.
|
||
|
|
$guard = app(AnswerGuard::class);
|
||
|
|
|
||
|
|
$text = 'Мы не продаём номера поштучно — платите вы за заявки, которые получаете. Заявка стоит от 55 ₽ до 25 ₽.';
|
||
|
|
$clean = $guard->clean($text, 'Заявка стоит от 55 ₽ до 25 ₽. Номер, заявка и лид — это одно и то же.', personalFactsKnown: false);
|
||
|
|
|
||
|
|
expect($clean)->not->toContain('поштучно')
|
||
|
|
->and($clean)->toContain('от 55 ₽');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('число, названное самим клиентом, сторож не считает выдумкой', function () {
|
||
|
|
$guard = app(AnswerGuard::class);
|
||
|
|
|
||
|
|
// Клиент спросил «можно купить всего 5 номеров?» — повторить его же «5 номеров»
|
||
|
|
// в ответе не выдумка. Сторожу отдаётся и текст вопроса (BotAnswerService).
|
||
|
|
$context = "Минимального заказа нет.\n\nможно купить всего 5 номеров?";
|
||
|
|
$text = 'Да, конечно — можно взять и 5 номеров, минимального заказа нет.';
|
||
|
|
|
||
|
|
expect($guard->clean($text, $context, personalFactsKnown: false))->toContain('5 номеров');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('сколько заявок выйдет на подарочную тысячу — бот не обещает', function () {
|
||
|
|
$guard = app(AnswerGuard::class);
|
||
|
|
|
||
|
|
$text = 'При регистрации дарим 1000 ₽ — этого хватит на несколько десятков заявок. Заявка стоит от 55 ₽.';
|
||
|
|
$clean = $guard->clean($text, 'При регистрации дарим 1000 ₽. Заявка стоит от 55 ₽.', personalFactsKnown: false);
|
||
|
|
|
||
|
|
expect($clean)->not->toContain('несколько десятков')
|
||
|
|
->and($clean)->toContain('от 55 ₽');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('правду про возврат ОСТАТКА баланса сторож не режет', function () {
|
||
|
|
$guard = app(AnswerGuard::class);
|
||
|
|
|
||
|
|
$article = 'Неизрасходованный остаток баланса возвращается по заявлению на support@liderra.ru, '
|
||
|
|
.'до 3 рабочих дней, за вычетом комиссий. Стоимость уже переданных заявок возврату не подлежит.';
|
||
|
|
$text = 'Неизрасходованный остаток баланса можно вернуть по заявлению на support@liderra.ru — деньги придут на те же реквизиты.';
|
||
|
|
|
||
|
|
$clean = $guard->clean($text, $article, personalFactsKnown: false);
|
||
|
|
|
||
|
|
expect($clean)->toContain('остаток баланса можно вернуть');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('обещание вернуть деньги ЗА ЗАЯВКУ сторож по-прежнему режет', function () {
|
||
|
|
$guard = app(AnswerGuard::class);
|
||
|
|
|
||
|
|
$text = 'Мы вернём вам деньги за эту заявку, раз человек не взял трубку.';
|
||
|
|
$clean = $guard->clean($text, 'Недозвон — не брак.', personalFactsKnown: false);
|
||
|
|
|
||
|
|
expect($clean)->not->toContain('вернём вам деньги');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('после вырезанного предложения не остаётся обрубок «Они лежат…»', function () {
|
||
|
|
$guard = app(AnswerGuard::class);
|
||
|
|
|
||
|
|
// Первое предложение — выдумка про гостя, его режем. Второе без него бессмысленно.
|
||
|
|
$text = 'У вас уже есть 1000 ₽ подарка. Они лежат на счёте, и первые заявки идут за их счёт. Пополнять баланс можно картой или по счёту.';
|
||
|
|
$clean = $guard->clean($text, 'Пополнять баланс можно картой или по счёту.', personalFactsKnown: false);
|
||
|
|
|
||
|
|
expect($clean)->not->toContain('Они лежат')
|
||
|
|
->and($clean)->toContain('картой или по счёту');
|
||
|
|
});
|