Files
portal/app/tests/Feature/ClientTg/ZhivaLiSistemaTest.php
T
Дмитрий 337f6b9897 feat телеграм: дата старта показов, признак живости системы и человеческий язык ошибок
Дата старта (решение владельца 06.08.2026). Портал не передаёт кабинету МТС ни одной
даты — их ставит кабинет своими умолчаниями. Живой прогон показал: старт оказался
ЗАВТРАШНИМ, тогда как экран обещал клиенту показы «7 дней», подразумевая сегодня.
Робот читает день начала из ТОЙ ЖЕ строки списка, куда и так ходит за вердиктом —
ни одного лишнего захода в кабинет; портал хранит его в client_tg_campaigns.starts_on
и показывает клиенту «Показы начнутся 7 августа». Проверено на ЖИВОМ кабинете:
три задания подряд вернули startDate 2026-08-07 по кампании МТС 2237821.
Мастер перестал молчать о том, что день начала ставит кабинет, а не мы.

Ф-2, карточка приёмки Т-Ф4. У кампаний в движении видно «Проверяли 5 минут назад».
Считаются только ЗАКОНЧЕННЫЕ проверки, включая неудачные: задание в очереди работой
не является, а неудачная проверка — всё равно признак жизни. Именно в такой тишине
владелец 36 часов не знал, что робот вообще не может войти в кабинет.

Ф-3. Имена полей в ошибках формы по-русски: «Лимит на объявление не может быть меньше
1 ₽» вместо «Поле budget cap rub должно быть не меньше 1». Серая кнопка «Запустить»
называет причину и шаг, куда вернуться, а не гаснет молча.

Карточки Т-Р3 и Т-Р4 закрыты тестами (живьём не воспроизвести). Попутно найдено: обе
защиты, стерегущие ЕДИНСТВЕННОЕ место траты живых денег роботом, были без единого
теста. Сторожа доказаны вырезанием.

Тесты: ClientTg 378 зелёных, экраны 2158, робот 197. Статанализ 0, стиль 0, типы чисто.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 18:33:35 +03:00

155 lines
6.8 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\ClientTg\Campaign;
use App\Models\ClientTg\RobotJob;
use App\Models\Tenant;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
/**
* Дефект Ф-2 приёмки 06.08.2026, карточка ранбука Т-Ф4 — не видно, жива ли система.
*
* 🔴 Кампания может неделями висеть «На модерации», и клиент никак не отличит работу от
* поломки: портал не отдавал ему ни времени последнего опроса робота, ни времени
* последней проверки вердикта. Именно в такой тишине владелец 36 часов не знал, что
* робот вообще не может войти в кабинет.
*
* Отдаём время ПОСЛЕДНЕЙ ЗАКОНЧЕННОЙ проверки. Не «поставлено в очередь» — задание
* могло быть поставлено и не выполниться ни разу; такое «мы работаем» было бы враньём.
*/
uses(RefreshDatabase::class);
beforeEach(function () {
$this->tenant = Tenant::factory()->create();
$this->user = User::factory()->create(['tenant_id' => $this->tenant->id]);
DB::statement('SET LOCAL app.current_tenant_id = '.$this->tenant->id);
$this->actingAs($this->user);
});
function kampaniyaNaModeracii(int $tenantId): Campaign
{
return Campaign::query()->create([
'tenant_id' => $tenantId,
'status' => Campaign::STATUS_MODERATING,
'ad_text' => 'Приходите к нам',
'ad_link' => 'https://t.me/x',
'ord_category' => 'Размещение рекламы',
'budget_cap_rub' => '500.00',
'audience_kind' => Campaign::AUDIENCE_LIST,
'planned_count' => 400,
'estimated_cost_rub' => '268.80',
'mts_campaign_id' => '2237821',
'created_by' => 1,
]);
}
it('клиент видит время последней ЗАКОНЧЕННОЙ проверки робота', function () {
$campaign = kampaniyaNaModeracii($this->tenant->id);
RobotJob::query()->create([
'tenant_id' => $this->tenant->id,
'campaign_id' => $campaign->id,
'mode' => RobotJob::MODE_READ_STATUS,
'payload' => ['mode' => 'read-status'],
'status' => RobotJob::STATUS_DONE,
'finished_at' => '2026-08-06 14:31:00',
]);
$this->getJson('/api/telegram/campaigns')
->assertOk()
->assertJsonPath('campaigns.0.last_robot_check_at', '2026-08-06T14:31:00.000000Z');
});
// 🪤 Провалившаяся проверка — ТОЖЕ проверка: система живая, просто кабинет не ответил.
// Показывать только успешные значило бы молчать ровно в тот момент, когда что-то не так.
it('неудачная проверка тоже считается — система работала', function () {
$campaign = kampaniyaNaModeracii($this->tenant->id);
RobotJob::query()->create([
'tenant_id' => $this->tenant->id,
'campaign_id' => $campaign->id,
'mode' => RobotJob::MODE_READ_STATUS,
'payload' => ['mode' => 'read-status'],
'status' => RobotJob::STATUS_FAILED,
'finished_at' => '2026-08-06 15:00:00',
'failure_reason' => 'Список кампаний не отрисовался',
]);
$this->getJson('/api/telegram/campaigns')
->assertOk()
->assertJsonPath('campaigns.0.last_robot_check_at', '2026-08-06T15:00:00.000000Z');
});
// 🪤 Задание в очереди — ещё НЕ проверка. Показать по нему «проверяли» значило бы
// обещать работу, которой не было: именно так выглядели 36 часов молчания.
it('задание только поставлено в очередь — это ещё не проверка', function () {
$campaign = kampaniyaNaModeracii($this->tenant->id);
RobotJob::query()->create([
'tenant_id' => $this->tenant->id,
'campaign_id' => $campaign->id,
'mode' => RobotJob::MODE_READ_STATUS,
'payload' => ['mode' => 'read-status'],
'status' => RobotJob::STATUS_QUEUED,
'finished_at' => null,
]);
$this->getJson('/api/telegram/campaigns')
->assertOk()
->assertJsonPath('campaigns.0.last_robot_check_at', null);
});
it('из нескольких проверок показывается САМАЯ СВЕЖАЯ', function () {
$campaign = kampaniyaNaModeracii($this->tenant->id);
foreach (['2026-08-06 12:00:00', '2026-08-06 16:45:00', '2026-08-06 14:00:00'] as $kogda) {
RobotJob::query()->create([
'tenant_id' => $this->tenant->id,
'campaign_id' => $campaign->id,
'mode' => RobotJob::MODE_READ_STATUS,
'payload' => ['mode' => 'read-status'],
'status' => RobotJob::STATUS_DONE,
'finished_at' => $kogda,
]);
}
$this->getJson('/api/telegram/campaigns')
->assertOk()
->assertJsonPath('campaigns.0.last_robot_check_at', '2026-08-06T16:45:00.000000Z');
});
it('робота ещё не звали — поле пустое, а не выдуманное', function () {
kampaniyaNaModeracii($this->tenant->id);
$this->getJson('/api/telegram/campaigns')
->assertOk()
->assertJsonPath('campaigns.0.last_robot_check_at', null);
});
// 🔴 Чужие проверки в свою карточку не пускаем: это тот же класс дыры, что и чужие
// кампании. Время чужой работы — не наше дело и не наш признак живости.
it('проверка ЧУЖОЙ кампании в карточку не попадает', function () {
$moya = kampaniyaNaModeracii($this->tenant->id);
$chuzhoy = Tenant::factory()->create();
DB::statement('SET LOCAL app.current_tenant_id = '.$chuzhoy->id);
$chuzhaya = kampaniyaNaModeracii($chuzhoy->id);
RobotJob::query()->create([
'tenant_id' => $chuzhoy->id,
'campaign_id' => $chuzhaya->id,
'mode' => RobotJob::MODE_READ_STATUS,
'payload' => ['mode' => 'read-status'],
'status' => RobotJob::STATUS_DONE,
'finished_at' => '2026-08-06 23:59:00',
]);
DB::statement('SET LOCAL app.current_tenant_id = '.$this->tenant->id);
$this->getJson('/api/telegram/campaigns')
->assertOk()
->assertJsonPath('campaigns.0.id', $moya->id)
->assertJsonPath('campaigns.0.last_robot_check_at', null);
});