111 lines
5.8 KiB
PHP
111 lines
5.8 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* СВЯЗКА между генератором карточки (ClientFacts) и сторожом (AnswerGuard).
|
||
|
|
*
|
||
|
|
* AnswerGuardTest.php ловит перепутанные цифры на карточке, написанной ВРУЧНУЮ в самом
|
||
|
|
* тесте (trapCard()) — она устроена так, как сторож ЕЁ понимает, но не проверяет, что
|
||
|
|
* сторож понимает НАСТОЯЩУЮ карточку, которую строит ClientFacts::card() на реальных
|
||
|
|
* данных. Если кто-то поменяет формулировку в ClientFacts («Баланс:» → «Ваш баланс:»),
|
||
|
|
* сторож молча перестанет находить факты — и пропустит враньё про цифры клиента.
|
||
|
|
* Ручной тест этого не заметит, а этот — обязан.
|
||
|
|
*
|
||
|
|
* 🔴 Если этот тест УПАЛ после правки текста в ClientFacts — это значит сторож ОСЛЕП,
|
||
|
|
* а не что тест «слишком строгий». Чинить нужно разбор в AnswerGuard::parseCardFacts()
|
||
|
|
* (синхронно с новой формулировкой), а НЕ подгонять этот тест под новый текст.
|
||
|
|
*/
|
||
|
|
|
||
|
|
use App\Models\Deal;
|
||
|
|
use App\Models\PricingTier;
|
||
|
|
use App\Models\Project;
|
||
|
|
use App\Models\Tenant;
|
||
|
|
use App\Models\User;
|
||
|
|
use App\Services\Bot\AnswerGuard;
|
||
|
|
use App\Services\Bot\ClientFacts;
|
||
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||
|
|
|
||
|
|
uses(RefreshDatabase::class);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Те же две ступени, что в ClientFactsTest::tiers() — имя своё (guardContract*), чтобы
|
||
|
|
* не столкнуться с глобальной функцией `tiers()` из ClientFactsTest.php при общем прогоне.
|
||
|
|
*/
|
||
|
|
function guardContractPricingTiers(): void
|
||
|
|
{
|
||
|
|
PricingTier::factory()->create(['tier_no' => 1, 'leads_in_tier' => 300, 'price_per_lead_kopecks' => 5000]);
|
||
|
|
PricingTier::factory()->create(['tier_no' => 2, 'leads_in_tier' => null, 'price_per_lead_kopecks' => 4000]);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Строит НАСТОЯЩУЮ карточку через ClientFacts::card() на нарочно подобранных данных:
|
||
|
|
* баланс 2 000 ₽ по 50 ₽/заявку (1-я ступень) = РОВНО 40 заявок — число нарочно совпадает
|
||
|
|
* с «получено в этом месяце» (12) не будучи им, чтобы ловушка «перепутанные местами
|
||
|
|
* цифры» была видна: 40 в карточке РЕАЛЬНО есть (баланс), но не как «получено в этом
|
||
|
|
* месяце»; 12 — другое число (месяц), не «сегодня»; 3 — третье число (сегодня).
|
||
|
|
*/
|
||
|
|
function guardContractRealCard(): string
|
||
|
|
{
|
||
|
|
guardContractPricingTiers();
|
||
|
|
|
||
|
|
$tenant = Tenant::factory()->create(['balance_rub' => '2000.00', 'delivered_in_month' => 12]);
|
||
|
|
|
||
|
|
$project = Project::factory()->create([
|
||
|
|
'tenant_id' => $tenant->id, 'is_active' => true, 'daily_limit_target' => 10,
|
||
|
|
]);
|
||
|
|
Project::factory()->create([
|
||
|
|
'tenant_id' => $tenant->id, 'is_active' => true, 'daily_limit_target' => 5,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$user = User::factory()->create(['tenant_id' => $tenant->id]);
|
||
|
|
|
||
|
|
// Сегодня — 3 заявки (received_at внутри текущего месяца — партиции deals помесячные).
|
||
|
|
Deal::factory()->count(3)->create([
|
||
|
|
'tenant_id' => $tenant->id,
|
||
|
|
'project_id' => $project->id,
|
||
|
|
'received_at' => now()->startOfDay()->addHours(9),
|
||
|
|
]);
|
||
|
|
|
||
|
|
$card = app(ClientFacts::class)->card((int) $user->id);
|
||
|
|
expect($card)->not->toBeNull();
|
||
|
|
|
||
|
|
// Проверяем саму ловушку ДО сторожа: если это условие не выполняется — подстроенные
|
||
|
|
// данные сломались, а не сторож (иначе тесты ниже могли бы пройти по случайной причине).
|
||
|
|
expect($card)->toContain('40 заявок')
|
||
|
|
->and($card)->toContain('получено в этом месяце 12')
|
||
|
|
->and($card)->toContain('Заявки: сегодня 3');
|
||
|
|
|
||
|
|
return (string) $card;
|
||
|
|
}
|
||
|
|
|
||
|
|
it('а) враньё режется: «получено 40» — число из ДРУГОГО факта настоящей карточки (баланс)', function () {
|
||
|
|
$card = guardContractRealCard();
|
||
|
|
$guard = app(AnswerGuard::class);
|
||
|
|
|
||
|
|
expect($guard->clean('В этом месяце получено 40 заявок.', $card))->toBe('');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('б) враньё режется: «сегодня 12» — число из ДРУГОГО факта настоящей карточки (месяц)', function () {
|
||
|
|
$card = guardContractRealCard();
|
||
|
|
$guard = app(AnswerGuard::class);
|
||
|
|
|
||
|
|
expect($guard->clean('Сегодня пришло 12 заявок.', $card))->toBe('');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('в) честное остаётся: «получено 12» — ровно то число, что в настоящей карточке', function () {
|
||
|
|
$card = guardContractRealCard();
|
||
|
|
$guard = app(AnswerGuard::class);
|
||
|
|
|
||
|
|
expect($guard->clean('В этом месяце получено 12 заявок.', $card))
|
||
|
|
->toBe('В этом месяце получено 12 заявок.');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('г) честное остаётся: «сегодня 3» — ровно то число, что в настоящей карточке', function () {
|
||
|
|
$card = guardContractRealCard();
|
||
|
|
$guard = app(AnswerGuard::class);
|
||
|
|
|
||
|
|
expect($guard->clean('Сегодня пришло 3 заявки.', $card))
|
||
|
|
->toBe('Сегодня пришло 3 заявки.');
|
||
|
|
});
|