ki/quota: Hash-Bindung atomar bis in den Publish-Lock, roter Re-Check lehnt ab, Kontingent-Altbestand (Block 7, U02–U04)
- Jobs speichern Ergebnis nur per bedingtem UPDATE auf den analysierten Hash; publish() prüft bei ki/scheduler unter der Zeilensperre die jüngste gültige Klassifikation und plant sonst eine neue Prüfung ein (StaleClassificationException) - Re-Check ohne Routing: Rot bei einer PM in Review lehnt wie die Erstprüfung ab - consumePublishSlot() zählt eine NULL-Periode wie den laufenden Monat; Backfill-Migration setzt die Periode für Altverbrauch - ApiPortalResolutionTest: gemeinsame Kategorie wegen der U10-Prüfung Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
1cd9fd11af
commit
ea68362310
11 changed files with 698 additions and 25 deletions
|
|
@ -272,3 +272,61 @@ test('the monthly reset is idempotent and leaves this month\'s consumption alone
|
|||
->and($lastMonth->fresh()->press_release_quota_period)->toBe('2026-09')
|
||||
->and($unknown->fresh()->press_release_quota_used_this_month)->toBe(0);
|
||||
});
|
||||
|
||||
test('legacy consumption without a period counts as this month and continues the counter on publish (Audit U04)', function () {
|
||||
Carbon::setTestNow('2026-09-18 10:00:00');
|
||||
|
||||
// Altbestand aus der Zeit vor der Periodenbindung: 2 von 3 verbraucht,
|
||||
// Monat unbekannt (NULL).
|
||||
$user = quotaTestSubscriber(planQuota: 3, used: 2);
|
||||
expect($user->press_release_quota_period)->toBeNull()
|
||||
->and($user->pressReleaseQuotaRemaining())->toBe(1);
|
||||
|
||||
$pr = quotaTestPressRelease($user, 'review');
|
||||
app(PressReleaseService::class)->publish($pr);
|
||||
|
||||
$fresh = $user->fresh();
|
||||
expect($fresh->press_release_quota_used_this_month)->toBe(3)
|
||||
->and($fresh->press_release_quota_period)->toBe('2026-09')
|
||||
->and($fresh->pressReleaseQuotaRemaining())->toBe(0);
|
||||
|
||||
$second = quotaTestPressRelease($user, 'review');
|
||||
expect(fn () => app(PressReleaseService::class)->publish($second))
|
||||
->toThrow(QuotaExceededException::class);
|
||||
});
|
||||
|
||||
test('the backfill migration pins legacy consumption to the migration month so the next month frees it (Audit U04)', function () {
|
||||
Carbon::setTestNow('2026-09-18 10:00:00');
|
||||
|
||||
$user = quotaTestSubscriber(planQuota: 3, used: 3);
|
||||
$untouched = User::factory()->create(['press_release_quota_used_this_month' => 0, 'press_release_quota_period' => null]);
|
||||
$pinned = User::factory()->create(['press_release_quota_used_this_month' => 1, 'press_release_quota_period' => '2026-08']);
|
||||
|
||||
expect($user->pressReleaseQuotaRemaining())->toBe(0);
|
||||
|
||||
// Heutige Lesart ohne Backfill: NULL gilt als laufender Monat – der
|
||||
// Monatswechsel gibt ohne Reset-Cron nichts frei.
|
||||
Carbon::setTestNow('2026-10-02 10:00:00');
|
||||
expect($user->fresh()->pressReleaseQuotaRemaining())->toBe(0);
|
||||
|
||||
Carbon::setTestNow('2026-09-18 10:00:00');
|
||||
$migration = require base_path('database/migrations/2026_09_18_190000_backfill_press_release_quota_period.php');
|
||||
$migration->up();
|
||||
|
||||
expect($user->fresh()->press_release_quota_period)->toBe('2026-09')
|
||||
->and($user->fresh()->press_release_quota_used_this_month)->toBe(3)
|
||||
->and($untouched->fresh()->press_release_quota_period)->toBeNull()
|
||||
->and($pinned->fresh()->press_release_quota_period)->toBe('2026-08');
|
||||
|
||||
// Nach dem Backfill erkennt der Monatswechsel den Altverbrauch als Vormonat.
|
||||
Carbon::setTestNow('2026-10-02 10:00:00');
|
||||
expect($user->fresh()->pressReleaseQuotaRemaining())->toBe(3);
|
||||
|
||||
$pr = quotaTestPressRelease($user, 'review');
|
||||
app(PressReleaseService::class)->publish($pr);
|
||||
|
||||
$fresh = $user->fresh();
|
||||
expect($fresh->press_release_quota_used_this_month)->toBe(1)
|
||||
->and($fresh->press_release_quota_period)->toBe('2026-10')
|
||||
->and($fresh->pressReleaseQuotaRemaining())->toBe(2);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue