create(); return Campaign::query()->create([ 'tenant_id' => $tenant->id, 'status' => $status, 'ad_text' => 'Тест модерации', 'ord_category' => 'Размещение рекламы', 'budget_cap_rub' => '1000.00', 'audience_kind' => Campaign::AUDIENCE_DEALS, 'planned_count' => 0, 'estimated_cost_rub' => '0.00', 'created_by' => 1, ]); } /** Queued-кампания-список с фейковыми номерами для прогона джоба. */ function moderatingQueuedCampaign(int $tenantId): Campaign { $campaign = Campaign::query()->create([ 'tenant_id' => $tenantId, 'status' => Campaign::STATUS_QUEUED, 'ad_text' => 'Приходите к нам', 'ad_link' => 'https://example.test/promo', 'ord_category' => 'Размещение рекламы', 'budget_cap_rub' => '1500.00', 'audience_kind' => Campaign::AUDIENCE_LIST, 'planned_count' => 2, 'estimated_cost_rub' => '0.00', 'created_by' => 1, ]); foreach (['79990000001', '79990000002'] as $phone) { CampaignPhone::query()->create([ 'tenant_id' => $tenantId, 'campaign_id' => $campaign->id, 'phone' => $phone, ]); } return $campaign; } function moderatingMockRobot(RobotResult $result): void { $mock = Mockery::mock(TelegramRobotRunner::class); $mock->shouldReceive('run')->andReturn($result); app()->instance(TelegramRobotRunner::class, $mock); } it('константа STATUS_MODERATING существует и равна moderating', function () { expect(Campaign::STATUS_MODERATING)->toBe('moderating'); }); it('из running допускает moderating', function () { $c = moderatingCampaign(Campaign::STATUS_RUNNING); expect($c->canTransitionTo(Campaign::STATUS_MODERATING))->toBeTrue(); $c->transitionTo(Campaign::STATUS_MODERATING); expect($c->status)->toBe(Campaign::STATUS_MODERATING); expect(Campaign::find($c->id)->status)->toBe(Campaign::STATUS_MODERATING); }); it('из moderating допускает launched и rejected', function (string $to) { $c = moderatingCampaign(Campaign::STATUS_MODERATING); $c->transitionTo($to); expect($c->status)->toBe($to); })->with([ Campaign::STATUS_LAUNCHED, Campaign::STATUS_REJECTED, ]); it('rejected → queued разрешён (пересдача)', function () { $c = moderatingCampaign(Campaign::STATUS_REJECTED); expect($c->canTransitionTo(Campaign::STATUS_QUEUED))->toBeTrue(); $c->transitionTo(Campaign::STATUS_QUEUED); expect($c->status)->toBe(Campaign::STATUS_QUEUED); }); it('moderating не пускает назад в running', function () { $c = moderatingCampaign(Campaign::STATUS_MODERATING); expect(fn () => $c->transitionTo(Campaign::STATUS_RUNNING)) ->toThrow(DomainException::class); expect($c->status)->toBe(Campaign::STATUS_MODERATING); }); it('живой finalize (launched=true) → moderating, а НЕ launched', function () { config()->set('client_tg.sandbox', false); $tenant = Tenant::factory()->create(); $campaign = moderatingQueuedCampaign($tenant->id); moderatingMockRobot(new RobotResult(ok: true, matched: 437, launched: true, campaignId: '2231140')); RunTelegramCampaignJob::dispatchSync($campaign->id, $tenant->id); $fresh = Campaign::find($campaign->id); expect($fresh->status)->toBe(Campaign::STATUS_MODERATING) ->and($fresh->matched_count)->toBe(437) ->and($fresh->mts_campaign_id)->toBe('2231140'); }); it('песочница (draft) остаётся draft_ready, не moderating', function () { config()->set('client_tg.sandbox', true); $tenant = Tenant::factory()->create(); $campaign = moderatingQueuedCampaign($tenant->id); moderatingMockRobot(new RobotResult(ok: true, matched: 400, launched: false, campaignId: 'draft')); RunTelegramCampaignJob::dispatchSync($campaign->id, $tenant->id); expect(Campaign::find($campaign->id)->status)->toBe(Campaign::STATUS_DRAFT_READY); });