337f6b9897
Дата старта (решение владельца 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>
145 lines
6.7 KiB
PHP
145 lines
6.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\ClientTg\Campaign;
|
|
use App\Models\Tenant;
|
|
use App\Models\User;
|
|
use App\Services\ClientTg\RobotResult;
|
|
use App\Services\ClientTg\TelegramModerationVerdictApplier;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
/**
|
|
* Дата старта рекламы: клиент должен видеть, С КАКОГО ДНЯ пойдут показы.
|
|
*
|
|
* 🔴 Откуда взялось. Портал не передаёт кабинету МТС ни одной даты — их ставит сам
|
|
* кабинет своими умолчаниями. Живьём 06.08.2026 (кампания 2237821) старт оказался
|
|
* ЗАВТРАШНИМ, а экран обещал клиенту показы «7 дней», подразумевая сегодня. Клиент
|
|
* ждёт рекламу сегодня, её нет, и объяснить это нечем.
|
|
*
|
|
* Решение владельца — самое дешёвое: дат не выбирать, а ПОКАЗАТЬ ту, что поставил
|
|
* кабинет. Робот читает строку кампании в списке раз в час (`read-status`) — дата
|
|
* стоит в её начале и приезжает вместе с вердиктом, без единого лишнего захода.
|
|
*
|
|
* 🔑 Главный случай — именно «ещё на модерации»: дата нужна клиенту РАНЬШЕ вердикта.
|
|
* Применение вердикта на «moderating» выходит рано и кампанию не трогает, поэтому
|
|
* сохранение даты обязано стоять ДО этого выхода — иначе поле не заполнится никогда.
|
|
*/
|
|
uses(RefreshDatabase::class);
|
|
|
|
function kampaniyaSDatoy(int $tenantId): Campaign
|
|
{
|
|
return Campaign::query()->create([
|
|
'tenant_id' => $tenantId,
|
|
'status' => Campaign::STATUS_MODERATING,
|
|
'ad_text' => 'Приходите к нам за услугой',
|
|
'ad_link' => 'https://example.test/promo',
|
|
'ord_category' => 'Размещение рекламы',
|
|
'budget_cap_rub' => '500.00',
|
|
'audience_kind' => Campaign::AUDIENCE_LIST,
|
|
'planned_count' => 2,
|
|
'estimated_cost_rub' => '315.00',
|
|
'mts_campaign_id' => '2237821',
|
|
'created_by' => 1,
|
|
]);
|
|
}
|
|
|
|
beforeEach(function () {
|
|
config()->set('client_tg.sandbox', true);
|
|
$this->tenant = Tenant::factory()->create(['balance_rub' => '1000.00']);
|
|
DB::statement('SET LOCAL app.current_tenant_id = '.$this->tenant->id);
|
|
$this->campaign = kampaniyaSDatoy($this->tenant->id);
|
|
});
|
|
|
|
it('дата старта сохраняется, ПОКА кампания ещё на модерации', function () {
|
|
app(TelegramModerationVerdictApplier::class)->apply(
|
|
$this->tenant->id,
|
|
$this->campaign->id,
|
|
RobotResult::fromRobotJson([
|
|
'ok' => true,
|
|
'moderationStatus' => 'moderating',
|
|
'startDate' => '2026-08-07',
|
|
]),
|
|
true,
|
|
);
|
|
|
|
$fresh = Campaign::find($this->campaign->id);
|
|
expect($fresh->starts_on?->toDateString())->toBe('2026-08-07');
|
|
// Вердикта нет — статус трогать не смеем.
|
|
expect($fresh->status)->toBe(Campaign::STATUS_MODERATING);
|
|
});
|
|
|
|
it('дата сохраняется и вместе с одобрением', function () {
|
|
app(TelegramModerationVerdictApplier::class)->apply(
|
|
$this->tenant->id,
|
|
$this->campaign->id,
|
|
RobotResult::fromRobotJson([
|
|
'ok' => true,
|
|
'moderationStatus' => 'approved',
|
|
'startDate' => '2026-08-07',
|
|
]),
|
|
true,
|
|
);
|
|
|
|
$fresh = Campaign::find($this->campaign->id);
|
|
expect($fresh->starts_on?->toDateString())->toBe('2026-08-07');
|
|
expect($fresh->status)->toBe(Campaign::STATUS_LAUNCHED);
|
|
});
|
|
|
|
// 🪤 Робот дату читает «как получится»: кабинет мог показать строку без даты. Записать
|
|
// в этом случае пустоту значило бы стереть уже известную клиенту дату и превратить
|
|
// экран в молчащий — хуже, чем было.
|
|
it('робот не прислал дату — прежнюю не затираем', function () {
|
|
$this->campaign->starts_on = '2026-08-07';
|
|
$this->campaign->save();
|
|
|
|
app(TelegramModerationVerdictApplier::class)->apply(
|
|
$this->tenant->id,
|
|
$this->campaign->id,
|
|
RobotResult::fromRobotJson(['ok' => true, 'moderationStatus' => 'moderating']),
|
|
true,
|
|
);
|
|
|
|
expect(Campaign::find($this->campaign->id)->starts_on?->toDateString())->toBe('2026-08-07');
|
|
});
|
|
|
|
// 🪤 Мусор вместо даты отсекается САМИМ разбором отчёта, а не тем, что ниже по течению
|
|
// его проглотит защита. Первая версия этого теста смотрела на колонку в базе и была
|
|
// ЛОЖНЫМ сторожем: вырезание проверки её не покрасило — мусор до базы доезжал, база
|
|
// ругалась, а ошибку глотал try/catch применения вердикта. Спрашиваем прямо у разбора.
|
|
it('мусор вместо даты отсекается разбором отчёта робота', function (mixed $syryo) {
|
|
expect(RobotResult::fromRobotJson([
|
|
'ok' => true,
|
|
'moderationStatus' => 'moderating',
|
|
'startDate' => $syryo,
|
|
])->startDate)->toBeNull();
|
|
})->with([
|
|
'слово вместо даты' => 'завтра',
|
|
'наш формат наизнанку' => '07.08.2026',
|
|
'дня такого не бывает' => '2026-02-31',
|
|
'месяца такого не бывает' => '2026-13-01',
|
|
'хвост после даты' => '2026-08-07 09:00',
|
|
'не строка вовсе' => 20260807,
|
|
]);
|
|
|
|
it('правильная дата разбор проходит', function () {
|
|
expect(RobotResult::fromRobotJson([
|
|
'ok' => true,
|
|
'moderationStatus' => 'moderating',
|
|
'startDate' => '2026-08-07',
|
|
])->startDate)->toBe('2026-08-07');
|
|
});
|
|
|
|
it('клиент видит дату старта в списке своих кампаний', function () {
|
|
$this->campaign->starts_on = '2026-08-07';
|
|
$this->campaign->save();
|
|
|
|
$user = User::factory()->create(['tenant_id' => $this->tenant->id]);
|
|
|
|
$this->actingAs($user)
|
|
->getJson('/api/telegram/campaigns')
|
|
->assertOk()
|
|
->assertJsonPath('campaigns.0.starts_on', '2026-08-07');
|
|
});
|