seed(RolesAndPermissionsSeeder::class); Mail::fake(); }); test('publish ohne scheduled_at und ohne embargo_at setzt published_at auf jetzt', function () { /** @var TestCase $this */ Carbon::setTestNow('2026-06-01 10:00:00'); $pr = PressRelease::factory()->create([ 'status' => PressReleaseStatus::Review->value, 'scheduled_at' => null, 'embargo_at' => null, 'published_at' => null, ]); app(PressReleaseService::class)->publish($pr); expect($pr->fresh()->published_at?->toDateTimeString())->toBe('2026-06-01 10:00:00'); }); test('publish mit scheduled_at in der Zukunft setzt published_at auf den Termin', function () { /** @var TestCase $this */ Carbon::setTestNow('2026-06-01 10:00:00'); $pr = PressRelease::factory()->create([ 'status' => PressReleaseStatus::Review->value, 'scheduled_at' => '2026-06-05 14:30:00', 'embargo_at' => null, 'published_at' => null, ]); app(PressReleaseService::class)->publish($pr); expect($pr->fresh()->published_at?->toDateTimeString())->toBe('2026-06-05 14:30:00'); }); test('publish mit embargo_at in der Zukunft verschiebt published_at auf das Embargo', function () { /** @var TestCase $this */ Carbon::setTestNow('2026-06-01 10:00:00'); $pr = PressRelease::factory()->create([ 'status' => PressReleaseStatus::Review->value, 'scheduled_at' => null, 'embargo_at' => '2026-06-10 08:00:00', 'published_at' => null, ]); app(PressReleaseService::class)->publish($pr); expect($pr->fresh()->published_at?->toDateTimeString())->toBe('2026-06-10 08:00:00'); }); test('publish mit scheduled_at und späterem embargo_at nimmt das Embargo', function () { /** @var TestCase $this */ Carbon::setTestNow('2026-06-01 10:00:00'); $pr = PressRelease::factory()->create([ 'status' => PressReleaseStatus::Review->value, 'scheduled_at' => '2026-06-05 14:30:00', 'embargo_at' => '2026-06-10 08:00:00', 'published_at' => null, ]); app(PressReleaseService::class)->publish($pr); expect($pr->fresh()->published_at?->toDateTimeString())->toBe('2026-06-10 08:00:00'); }); test('publish übernimmt bereits gesetztes published_at und überschreibt nicht', function () { /** @var TestCase $this */ Carbon::setTestNow('2026-06-01 10:00:00'); $pr = PressRelease::factory()->create([ 'status' => PressReleaseStatus::Review->value, 'scheduled_at' => '2026-06-05 14:30:00', 'embargo_at' => null, 'published_at' => '2025-12-01 00:00:00', ]); app(PressReleaseService::class)->publish($pr); expect($pr->fresh()->published_at?->toDateTimeString())->toBe('2025-12-01 00:00:00'); }); test('publish-Source landet als source im Status-Log', function () { /** @var TestCase $this */ $pr = PressRelease::factory()->create([ 'status' => PressReleaseStatus::Review->value, 'published_at' => null, ]); app(PressReleaseService::class)->publish($pr, source: 'scheduler'); $log = PressReleaseStatusLog::query()->latest('id')->firstOrFail(); expect($log->source)->toBe('scheduler'); expect($log->to_status)->toBe(PressReleaseStatus::Published); }); test('Command publisht fällige Review-PMs mit scheduled_at <= now', function () { /** @var TestCase $this */ Carbon::setTestNow('2026-06-01 12:00:00'); $due = PressRelease::factory()->create([ 'status' => PressReleaseStatus::Review->value, 'classification' => PressReleaseClassification::Green->value, 'scheduled_at' => '2026-06-01 11:55:00', 'published_at' => null, ]); Artisan::call(PublishScheduledPressReleases::class); $fresh = $due->fresh(); expect($fresh->status)->toBe(PressReleaseStatus::Published); expect($fresh->published_at?->toDateTimeString())->toBe('2026-06-01 11:55:00'); }); test('Command publisht fällige gelbe PMs wie grüne (Direkt-Live)', function () { /** @var TestCase $this */ Carbon::setTestNow('2026-06-01 12:00:00'); $yellow = PressRelease::factory()->create([ 'status' => PressReleaseStatus::Review->value, 'classification' => PressReleaseClassification::Yellow->value, 'scheduled_at' => '2026-06-01 11:55:00', 'published_at' => null, ]); Artisan::call(PublishScheduledPressReleases::class); expect($yellow->fresh()->status)->toBe(PressReleaseStatus::Published); }); test('Command ignoriert fällige unklassifizierte PMs (Fallback: manuelle Queue)', function () { /** @var TestCase $this */ Carbon::setTestNow('2026-06-01 12:00:00'); $unclassified = PressRelease::factory()->create([ 'status' => PressReleaseStatus::Review->value, 'classification' => null, 'scheduled_at' => '2026-06-01 11:55:00', 'published_at' => null, ]); Artisan::call(PublishScheduledPressReleases::class); expect($unclassified->fresh()->status)->toBe(PressReleaseStatus::Review); }); test('Command ignoriert PMs mit scheduled_at in der Zukunft', function () { /** @var TestCase $this */ Carbon::setTestNow('2026-06-01 12:00:00'); $future = PressRelease::factory()->create([ 'status' => PressReleaseStatus::Review->value, 'scheduled_at' => '2026-06-01 12:30:00', 'published_at' => null, ]); Artisan::call(PublishScheduledPressReleases::class); expect($future->fresh()->status)->toBe(PressReleaseStatus::Review); }); test('Command ignoriert PMs ohne scheduled_at', function () { /** @var TestCase $this */ Carbon::setTestNow('2026-06-01 12:00:00'); $manual = PressRelease::factory()->create([ 'status' => PressReleaseStatus::Review->value, 'scheduled_at' => null, ]); Artisan::call(PublishScheduledPressReleases::class); expect($manual->fresh()->status)->toBe(PressReleaseStatus::Review); }); test('Command läuft mit dry-run ohne Statusänderung', function () { /** @var TestCase $this */ Carbon::setTestNow('2026-06-01 12:00:00'); $due = PressRelease::factory()->create([ 'status' => PressReleaseStatus::Review->value, 'classification' => PressReleaseClassification::Green->value, 'scheduled_at' => '2026-06-01 11:50:00', 'published_at' => null, ]); Artisan::call(PublishScheduledPressReleases::class, ['--dry-run' => true]); expect($due->fresh()->status)->toBe(PressReleaseStatus::Review); }); test('Command publisht maximal --limit pro Lauf', function () { /** @var TestCase $this */ Carbon::setTestNow('2026-06-01 12:00:00'); PressRelease::factory()->count(3)->state([ 'status' => PressReleaseStatus::Review->value, 'classification' => PressReleaseClassification::Green->value, 'scheduled_at' => '2026-06-01 11:50:00', 'published_at' => null, ])->create(); Artisan::call(PublishScheduledPressReleases::class, ['--limit' => 2]); $publishedCount = PressRelease::withoutGlobalScopes() ->where('status', PressReleaseStatus::Published->value) ->count(); expect($publishedCount)->toBe(2); });