Files
portal/app/tests/Feature/Bot/BotRunQuestionsCommandTest.php
T
Дмитрий b1b1686b0e fix(bot): прогон 250 вопросов × 2 режима — 11 находок закрыто, все проверены перепрогоном
Владелец: «проверь их обоих на тех 250 вопросах» → «правь все».
Разбор: docs/superpowers/findings/2026-07-14-bot-250-both-modes-run.md

Цены оказались вылечены полностью (0 выдуманных цен на 500 ответов), но вылез другой
пласт — бот путал гостя с вошедшим клиентом и импровизировал там, где правды нет в статьях.

ЧТО ПОЧИНЕНО (было → стало на перепрогоне):
- Гостю приписывали счёт и подарок («у вас уже есть 1000 ₽, они лежат на счёте») 2 → 0.
  У гостя нет ни счёта, ни подарка: он не зарегистрирован.
- Вошедшему называли его баланс подарком портала 2 → 0 и подсовывали сценарий новичка
  («живите на подарок», «создайте первый проект») — подарок он потратил давно.
- Вошедшему врали про состояние проектов («два работают», а работает один) 4 → 0
  и про заявки («сегодня 12», а сегодня 0) 1 → 0.
- «Сотая заявка уже дешевле» 2 → 0: ступень меняется по ОБЪЁМУ (первая — 500 заявок).
- «1000 ₽ хватит на несколько десятков заявок» 1 → 0. По решению владельца число заявок
  на подарок НЕ называем вообще: сколько придёт, зависит от источника.
- «Поднимите лимит — заявок будет больше» 1 → 0: лимит это ПОТОЛОК, а не источник.
- «Такого не бывает» про списание без заявки 1 → 0: на проде фантомные списания БЫЛИ.
- «Для Москвы — Московскую область» 1 → 0: Москва и СПб — самостоятельные субъекты.
- Обрубки фраз после чистки сторожем («Они лежат…», «Нажмите её…») 3 → 0.

ЗАМЕЧАНИЕ ВЛАДЕЛЬЦА ПО ЖИВОМУ ЛОГУ: «номера это и есть заявки, а он их делит».
Фраза «мы не продаём номера поштучно» была ВЫДУМКОЙ бота — в статьях её нет.
Теперь: номер = заявка = лид = контакт, продаём любой объём (хоть одну штуку),
минимального заказа нет; спросили цену — бот называет цифру ПЕРВОЙ фразой.
Живьём: «Сколько стоит один номер?» → «Заявка стоит от 55 ₽ до 25 ₽».

ПРАВДА ПРО ВОЗВРАТ ДЕНЕГ вынесена в статью vozvrat-deneg.md — дословно из «Политики
возврата» (/legal/refund): остаток по заявлению на почту, до 3 рабочих дней, за вычетом
комиссий; стоимость переданных заявок не возвращается. Раньше бот придумывал процедуру.

Плюс: сторож больше не считает выдумкой число, названное САМИМ клиентом («можно купить
всего 5 номеров?»); демо-стенд перестал сам себе противоречить (счётчик проекта «сегодня»
разошёлся с числом сделок); добавлен прогонщик bot:run-questions для таких проверок.

Тесты: бот 258/258, весь бэкенд 2738/2744 (0 падений). Сторож не перестарался: из 250
старых одобренных ответов режет целиком 1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 18:32:40 +03:00

58 lines
2.5 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\KnowledgeChunk;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
uses(RefreshDatabase::class);
/**
* Прогонщик вопросов — наша проверка ИИ-фичи (массовый прогон + слепые судьи).
* Продуктом не является: в бою не вызывается, но ломаться не должен, иначе прогон
* молча соврёт нам самим.
*/
beforeEach(function () {
config()->set('services.support.email', 'support@liderra.ru');
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::create([
'source_path' => 'help/b.md', 'title' => 'Как пополнить баланс', 'tour' => null,
'topics' => 'пополнить, баланс', 'chunk_index' => 0,
'content' => 'Пополнить баланс можно картой или по счёту.',
]);
});
it('прогоняет вопросы и пишет ответы построчно', function () {
Http::fake(['llm.api.cloud.yandex.net/*' => Http::response([
'result' => ['alternatives' => [['message' => ['role' => 'assistant', 'text' => 'Пополнить баланс можно в разделе «Биллинг».']]]],
])]);
$dir = sys_get_temp_dir().'/bot-run-'.uniqid();
mkdir($dir);
$file = $dir.'/q.json';
$out = $dir.'/out.jsonl';
file_put_contents($file, json_encode(['как пополнить баланс?', 'а картой можно?'], JSON_UNESCAPED_UNICODE));
$this->artisan('bot:run-questions', ['--file' => $file, '--out' => $out, '--as' => 'guest'])
->assertExitCode(0);
$lines = array_filter(explode("\n", (string) file_get_contents($out)));
expect($lines)->toHaveCount(2);
$first = json_decode((string) $lines[0], true);
expect($first['n'])->toBe(1)
->and($first['as'])->toBe('guest')
->and($first['q'])->toBe('как пополнить баланс?')
->and($first['a'])->toContain('Биллинг');
});
it('без файла вопросов честно падает, а не делает вид, что прогнал', function () {
$this->artisan('bot:run-questions', ['--file' => 'нет-такого.json', '--out' => 'x.jsonl'])
->assertExitCode(1);
});