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'); });