- 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>
50 lines
2.1 KiB
PHP
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([]);
|
|
});
|