5211048bce
Задача 2.0 (разведка) — РЕШЕНА без входа в кабинет: ответ уже был в находках Фазы 2. Билинг МТС за «показы своей базе» — НАКОПИТЕЛЬНЫЙ (резерв → списание по факту показов → возврат остатка, экран /payment). Следствие: списание «по факту» (была задача 2.4) на успехе отправки честно сделать нельзя — показов ещё нет; перенесено в Этап 3 (опрос завершения). Этап 2 закрыт составом 2.1 (отмена) + 2.3 (возврат брони) + 2.2. - 2.2 PHP: RobotResult несёт `actualCostRub` (nullable string) — заготовка, чтобы позже (Этап 3) прочитать фактическую стоимость из кабинета и списать её с кошелька клиента. Проброшено в fromRobotJson (робот начнёт класть поле позже; нет поля → null). Мёртвых фабрик launched()/draftReady() не добавлял. - 2.2 Node: чистая утилита `parseCost(text)` в cabinet.js — «Стоимость кампании от 201,6 ₽» → «201.60» (запятая→точка, разделители тысяч включая неразрывный пробел код 160, дробь до 2 знаков без округления, нет числа → null). Вынесена отдельной покрытой функцией; к DOM-потоку НЕ подключена (селектор строки стоимости подтвердим живьём в Этапе 3). - Разведка билинга и решение по 2.4 зафиксированы в FLOW-FINDINGS.md; план Этапа 2 обновлён (2.0 решён, 2.4 → Этап 3, Этап 2 закрыт). TDD, робот замокан, тесты на liderra_testing. Приёмка: Node 40/40 (npm test), Pest RobotResultTest 5/5 + RobotRunnerTest (потребитель) зелёный, регрессия ClientTg 120/120, phpstan RobotResult.php 0, pint чисто, deptrac 0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
52 lines
1.5 KiB
PHP
52 lines
1.5 KiB
PHP
<?php
|
|
|
|
use App\Services\ClientTg\RobotResult;
|
|
|
|
it('пробрасывает строковый actualCostRub из JSON робота', function () {
|
|
$r = RobotResult::fromRobotJson([
|
|
'ok' => true,
|
|
'matched' => 437,
|
|
'actualCostRub' => '201.60',
|
|
]);
|
|
|
|
expect($r->actualCostRub)->toBe('201.60');
|
|
expect($r->ok)->toBeTrue();
|
|
});
|
|
|
|
it('приводит числовой actualCostRub к строке', function () {
|
|
$r = RobotResult::fromRobotJson([
|
|
'ok' => true,
|
|
'actualCostRub' => 201.6,
|
|
]);
|
|
|
|
expect($r->actualCostRub)->toBe('201.6');
|
|
expect($r->actualCostRub)->not->toBeNull();
|
|
});
|
|
|
|
it('без поля actualCostRub оставляет null и не ломает остальные поля', function () {
|
|
$r = RobotResult::fromRobotJson([
|
|
'ok' => true,
|
|
'matched' => 12,
|
|
'campaignId' => '555',
|
|
]);
|
|
|
|
expect($r->actualCostRub)->toBeNull();
|
|
expect($r->ok)->toBeTrue();
|
|
expect($r->matched)->toBe(12);
|
|
expect($r->campaignId)->toBe('555');
|
|
});
|
|
|
|
it('failed() не выставляет actualCostRub', function () {
|
|
$r = RobotResult::failed('таймаут');
|
|
|
|
expect($r->actualCostRub)->toBeNull();
|
|
expect($r->ok)->toBeFalse();
|
|
expect($r->reason)->toBe('таймаут');
|
|
});
|
|
|
|
it('конструктор без actualCostRub даёт null по умолчанию', function () {
|
|
$r = new RobotResult(ok: true);
|
|
|
|
expect($r->actualCostRub)->toBeNull();
|
|
});
|