Files
portal/app/tests/Feature
Дмитрий a736ff087d feat(телеграм-реклама): Этап 3.3 — уборщик зависших кампаний + таймаут
Этап 3 «Жизненный цикл и модерация», задача 3.3. Робот в браузере может
оборваться на полпути (воркер убит, сеть отвалилась) — кампания навсегда
виснет в `running`, а замороженные деньги залипают. Уборщик добивает статус,
но КОНСЕРВАТИВНО с деньгами.

- SweepStuckTelegramCampaignsJob: раз в 10 минут ищет `running` старше 15 минут
  (штатный робот ≤ 420с). Развилка по факту создания черновика в кабинете:
    · нет mts_campaign_id (черновик не создан) → кампания заведомо не ушла →
      `failed` + возврат брони кошелька;
    · есть mts_campaign_id (черновик создан) → могла уйти на модерацию МТС →
      деньги вслепую НЕ возвращаем, `needs_review` до ручной сверки (задача 3.4).
  Кросс-тенант через pgsql_supplier (BYPASSRLS), правки под SET LOCAL
  app.current_tenant_id на дефолтном соединении; release гейтится на !sandbox,
  ключ совпадает с freeze. Повторная проверка status===running под lockForUpdate
  (гонка с finalize). Зеркалит связку ChargeTgNameFeeJob + RunTelegramCampaignJob.
- Campaign.php: константа STATUS_NEEDS_REVIEW; переходы running→needs_review
  (терминальный) и queued→failed (для failed() джоба при раннем сбое). Миграция
  не нужна — на колонке status нет CHECK, машина статусов в модели.
- RunTelegramCampaignJob: public int $timeout = 420 (робот ~300с + навигации);
  failed() теперь умеет queued→failed (переход добавлен в модель).
- console.php: расписание уборщика everyTenMinutes с heartbeat-трекингом.

TDD. Приёмка (моя область, чистый прогон): SweepStuckTest 7/7 (в т.ч. деньги —
с черновиком не тронуты, без черновика возвращены) + StatusMachine/Moderating/
ExternalId/RunCampaignJob/LaunchIdempotency/RobotResult/RobotRunner 49/49.
phpstan 0, deptrac 0, pint чисто.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 09:44:09 +03:00
..