presseportale/tests/Feature/ScheduleConfigurationTest.php
Kevin Adametz c82f279fef press-releases/jobs: Scheduler respektiert manuelle Prüfung und kommt an Blockaden vorbei, Newsletter-Versand per Schalter gesperrt, kurze Scheduler-Sperren (Umsetzungsaudit 29.09.: K-01, K-02, K-05, K-06, K-09, E9, OPT-8, OPT-9; 02.10.: O06)
- K-01: publish-scheduled filtert manual_review_required_at; publish() prüft
  für ki/scheduler unter der Sperre (ManualReviewRequiredException)
- K-02: Kontingent- und Bildsperre sind fachliche Ausgänge (Exit 0, einmaliger
  Verlaufseintrag); KI-Job ohne Retry bei Bildsperre
- O06: Kandidaten per Cursor (scheduled_at, id), Limit zählt bearbeitete
  Meldungen, Blockaden nur ein Scan-Budget
- K-05: KI-Job lädt vor dem Routing neu, überholter Status ohne Retry; backoff
- K-06: Admin-Aktionen mit Permission-Prüfung und Fehlermeldung statt 500
- K-09: withoutOverlapping() mit kurzer Sperrdauer, purge-events 03:50,
  neuer Wochenlauf press-release-images:sweep-orphan-originals (So 04:45)
- E9: NEWSLETTER_SENDING_ENABLED (Standard aus) sperrt Start, Resume und Job
- OPT-9: ExpireGrandfathering prüft die Queue-Voraussetzung aus E18

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 13:09:00 +00:00

50 lines
2.1 KiB
PHP

<?php
use Illuminate\Console\Scheduling\Event;
use Illuminate\Console\Scheduling\Schedule;
/**
* Scheduler-Konfiguration (Umsetzungsaudit 29.09.2026, K-09): Die Sperre von
* withoutOverlapping() darf einen abgestürzten Lauf nicht einen ganzen Tag
* lang überleben, und keine zwei nächtlichen Aufgaben teilen sich die Minute.
*/
function scheduledEvents(): array
{
return collect(app(Schedule::class)->events())
->filter(fn (Event $event): bool => ! str_contains((string) $event->command, 'inspire'))
->values()
->all();
}
function scheduledName(Event $event): string
{
return preg_replace('/^.*artisan[\'"]?\s+/', '', (string) $event->command) ?: (string) $event->description;
}
test('every scheduled task releases its overlap lock well before a day', function () {
$events = scheduledEvents();
expect($events)->not->toBeEmpty();
foreach ($events as $event) {
expect($event->withoutOverlapping)->toBeTrue(scheduledName($event).' ohne withoutOverlapping')
->and($event->expiresAt)->toBeLessThan(1440, scheduledName($event).' hält die Sperre 24 h');
}
});
test('the frequent tasks unlock within their interval window', function () {
$expires = collect(scheduledEvents())->mapWithKeys(fn (Event $event): array => [scheduledName($event) => $event->expiresAt]);
expect($expires->first(fn ($minutes, string $name): bool => str_starts_with($name, 'press-releases:publish-scheduled')))->toBe(10)
->and($expires->first(fn ($minutes, string $name): bool => str_starts_with($name, 'press-releases:aggregate-views')))->toBe(15);
});
test('no two daily tasks start in the same minute', function () {
$daily = collect(scheduledEvents())
->filter(fn (Event $event): bool => preg_match('/^\d+ \d+ \* \* \*$/', $event->expression) === 1)
->groupBy(fn (Event $event): string => $event->expression)
->filter(fn ($group): bool => $group->count() > 1)
->map(fn ($group) => $group->map(fn (Event $event): string => scheduledName($event))->all());
expect($daily->all())->toBe([]);
});