presseportale/tests/Feature/ReviewCheckServiceTest.php
Kevin Adametz 23cbd0bfe2 performance: Caching, deferred Tracking & externe Timeouts (H7, M12, M13)
- Navigations-Chrome (CategoryNavigation), Suche- und Newsroom-Seitendaten
  300s gecacht; gemeinsamer WebPageCache-Helper (eine TTL/Key-Konvention).
- Cache-Poisoning über ?page= geschlossen (Key an Paginator ausgerichtet,
  kein withQueryString im Cache); Newsrooms cachet nur den ungefilterten
  Fall und die ersten Seiten (kein unbegrenzter Key-Raum).
- Detailseiten schreiben Hits/View-Events deferred nach der Response
  (M12); OpenAI-/Markt-/USt-ID-Aufrufe mit Connect-Timeouts und
  begrenzten Retries; Admin-Memo-Caches konsistent invalidiert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 13:49:47 +00:00

121 lines
4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
use App\Enums\ReviewCheckSource;
use App\Exceptions\InsufficientCreditsException;
use App\Exceptions\ReviewLimitException;
use App\Models\User;
use App\Services\Billing\CreditWalletService;
use App\Services\PressRelease\ReviewCheckService;
beforeEach(function (): void {
$this->service = app(ReviewCheckService::class);
$this->wallet = app(CreditWalletService::class);
});
test('an Einzel user has four free checks per month', function () {
$user = User::factory()->create();
expect($this->service->freeRemaining($user))->toBe(4);
expect($this->service->nextCheckCosts($user))->toBeFalse();
});
test('free checks are consumed from the monthly quota first', function () {
$user = User::factory()->create();
$check = $this->service->recordCheck($user);
expect($check->source)->toBe(ReviewCheckSource::Free);
expect($check->charged_credits)->toBe(0);
expect($this->service->freeRemaining($user))->toBe(3);
expect($this->service->usedThisMonth($user))->toBe(1);
});
test('once the free quota is empty the overflow draws one credit per check', function () {
$user = User::factory()->create();
$this->wallet->credit($user, 5);
// Vier freie Prüfungen aufbrauchen.
for ($i = 0; $i < 4; $i++) {
$this->service->recordCheck($user);
}
expect($this->service->freeRemaining($user))->toBe(0);
expect($this->service->nextCheckCosts($user))->toBeTrue();
$overflow = $this->service->recordCheck($user);
expect($overflow->source)->toBe(ReviewCheckSource::Credit);
expect($overflow->charged_credits)->toBe(1);
expect($this->wallet->balance($user))->toBe(4);
});
test('stale user instances cannot consume the same final free review check', function () {
$user = User::factory()->create();
$this->wallet->credit($user, 5);
for ($i = 0; $i < 3; $i++) {
$this->service->recordCheck($user);
}
$firstRequestUser = $user->fresh();
$secondRequestUser = $user->fresh();
$lastFreeCheck = $this->service->recordCheck($firstRequestUser);
$overflowCheck = $this->service->recordCheck($secondRequestUser);
expect($lastFreeCheck->source)->toBe(ReviewCheckSource::Free)
->and($overflowCheck->source)->toBe(ReviewCheckSource::Credit)
->and($this->wallet->balance($user))->toBe(4);
});
test('overflow without enough credits throws and records nothing', function () {
$user = User::factory()->create();
for ($i = 0; $i < 4; $i++) {
$this->service->recordCheck($user);
}
expect(fn () => $this->service->recordCheck($user))
->toThrow(InsufficientCreditsException::class);
expect($this->service->usedThisMonth($user))->toBe(4); // kein Overflow-Eintrag
});
test('the daily limit is a hard brake that credits cannot bypass', function () {
$user = User::factory()->create();
$this->wallet->credit($user, 100);
// 10 Prüfungen heute (Tageslimit) – 4 frei, 6 per Credit.
for ($i = 0; $i < 10; $i++) {
$this->service->recordCheck($user);
}
expect($this->service->dailyLimitReached($user))->toBeTrue();
expect($this->service->canCheck($user))->toBeFalse();
expect(fn () => $this->service->recordCheck($user))
->toThrow(ReviewLimitException::class);
});
test('a higher tier check pool resets with the calendar month', function () {
$user = User::factory()->create();
// Letzten Monat verbrauchte Prüfungen zählen nicht ins aktuelle Kontingent.
ReviewCheckFactoryHelper($user, 3, now()->subMonth());
expect($this->service->usedThisMonth($user))->toBe(0);
expect($this->service->freeRemaining($user))->toBe(4);
});
/**
* Legt Prüfungen mit explizitem Datum an (umgeht den Service, der immer
* „jetzt" bucht).
*/
function ReviewCheckFactoryHelper(User $user, int $count, $at): void
{
for ($i = 0; $i < $count; $i++) {
$check = $user->reviewChecks()->create([
'source' => ReviewCheckSource::Free,
'charged_credits' => 0,
]);
$check->forceFill(['created_at' => $at, 'updated_at' => $at])->save();
}
}