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, ]); } it('разрешает канонический путь draft → queued → running → draft_ready', function () { $c = tgCampaign(Campaign::STATUS_DRAFT); $c->transitionTo(Campaign::STATUS_QUEUED); expect($c->status)->toBe(Campaign::STATUS_QUEUED); $c->transitionTo(Campaign::STATUS_RUNNING); expect($c->status)->toBe(Campaign::STATUS_RUNNING); $c->transitionTo(Campaign::STATUS_DRAFT_READY); expect($c->status)->toBe(Campaign::STATUS_DRAFT_READY); // Переход персистится в БД, а не только в объекте. expect(Campaign::find($c->id)->status)->toBe(Campaign::STATUS_DRAFT_READY); }); it('из running допускает launched / failed / rejected', function (string $to) { $c = tgCampaign(Campaign::STATUS_RUNNING); $c->transitionTo($to); expect($c->status)->toBe($to); })->with([ Campaign::STATUS_LAUNCHED, Campaign::STATUS_FAILED, Campaign::STATUS_REJECTED, ]); it('запрещает перескок draft → running (минуя queued) и не меняет статус', function () { $c = tgCampaign(Campaign::STATUS_DRAFT); expect(fn () => $c->transitionTo(Campaign::STATUS_RUNNING)) ->toThrow(DomainException::class); expect($c->status)->toBe(Campaign::STATUS_DRAFT); expect(Campaign::find($c->id)->status)->toBe(Campaign::STATUS_DRAFT); }); it('запрещает уход из терминального статуса', function () { $c = tgCampaign(Campaign::STATUS_LAUNCHED); expect(fn () => $c->transitionTo(Campaign::STATUS_RUNNING)) ->toThrow(DomainException::class); }); it('canTransitionTo сообщает допустимость без броска', function () { $c = tgCampaign(Campaign::STATUS_QUEUED); expect($c->canTransitionTo(Campaign::STATUS_RUNNING))->toBeTrue() ->and($c->canTransitionTo(Campaign::STATUS_DRAFT_READY))->toBeFalse(); }); it('константа STATUS_CANCELLED существует и равна cancelled', function () { expect(Campaign::STATUS_CANCELLED)->toBe('cancelled'); }); it('разрешает отмену из draft, переход персистится в БД', function () { $c = tgCampaign(Campaign::STATUS_DRAFT); expect($c->canTransitionTo(Campaign::STATUS_CANCELLED))->toBeTrue(); $c->transitionTo(Campaign::STATUS_CANCELLED); expect($c->status)->toBe(Campaign::STATUS_CANCELLED); expect(Campaign::find($c->id)->status)->toBe(Campaign::STATUS_CANCELLED); }); it('разрешает отмену из queued', function () { $c = tgCampaign(Campaign::STATUS_QUEUED); expect($c->canTransitionTo(Campaign::STATUS_CANCELLED))->toBeTrue(); $c->transitionTo(Campaign::STATUS_CANCELLED); expect($c->status)->toBe(Campaign::STATUS_CANCELLED); }); it('запрещает отмену из running', function () { $c = tgCampaign(Campaign::STATUS_RUNNING); expect($c->canTransitionTo(Campaign::STATUS_CANCELLED))->toBeFalse(); expect(fn () => $c->transitionTo(Campaign::STATUS_CANCELLED)) ->toThrow(DomainException::class); expect($c->status)->toBe(Campaign::STATUS_RUNNING); }); it('cancelled — терминальный статус', function () { $c = tgCampaign(Campaign::STATUS_CANCELLED); expect($c->canTransitionTo(Campaign::STATUS_QUEUED))->toBeFalse(); expect(fn () => $c->transitionTo(Campaign::STATUS_QUEUED)) ->toThrow(DomainException::class); expect($c->status)->toBe(Campaign::STATUS_CANCELLED); });