press-releases: Mail "wird persoenlich geprueft" beim Block, Blade-Guard fuer @php-Bloecke (Roadmap 1d, Nachtrag)

Der Autor bekommt beim Block nach dem letzten Rot (oder Gelb mit
eingeschalteter Queue) eine kurze Mail statt Stillstand: was passiert ist,
dass nichts zu tun ist, und dass das Ergebnis per Mail kommt. Laeuft ueber
notifyAuthor() wie Veroeffentlichung und Ablehnung (API-Regel, Sperrliste,
adressbezogene Abmeldung, Bounce-Referenz); Opt-out am Typ "abgelehnt".
Neue ConversionSource mail_review fuer den Link ins Konto.

BladePhpBlockGuardTest wendet das Compiler-Muster auf alle Views an und
faengt inline @php( vor einem spaeteren @endphp ab — fand den eigenen
Erklaer-Kommentar im Modal, der nur zufaellig funktionierte. Regel in
CLAUDE.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-09-16 15:05:47 +00:00
parent 35db30e019
commit 4fd1db7825
11 changed files with 278 additions and 24 deletions

View file

@ -1,8 +1,10 @@
<?php
use App\Enums\EmailNotificationType;
use App\Enums\PressReleaseClassification;
use App\Enums\PressReleaseStatus;
use App\Jobs\ClassifyPressRelease;
use App\Mail\PressReleaseHeldForReview;
use App\Mail\PressReleaseRejected;
use App\Models\PressRelease;
use App\Models\PressReleaseStatusLog;
@ -70,8 +72,41 @@ test('each release counts its automatic checks and the last red blocks instead o
->and(PressReleaseStatusLog::query()->where('press_release_id', $pr->id)->latest('id')->first()->reason)
->toContain('Automatische Prüfung erschöpft (2 von 2)');
// Keine Ablehnungsmail für den Block — der Autor sieht den Stand im Konto.
// Keine Ablehnungsmail für den Block — stattdessen „ein Mensch prüft jetzt".
Mail::assertNotQueued(PressReleaseRejected::class, fn ($mail) => str_contains((string) $mail->reason, 'erschöpft'));
Mail::assertQueued(PressReleaseHeldForReview::class, function (PressReleaseHeldForReview $mail) use ($customer, $pr) {
return $mail->hasTo($customer->email)
&& $mail->automationExhausted === true
&& $mail->pressRelease->is($pr)
&& str_contains($mail->showUrl, '/admin/me/press-releases/'.$pr->id.'?src=mail_review&pr='.$pr->id);
});
});
test('the held-for-review mail says nothing is required and respects the rejected opt-out', function () {
/** @var TestCase $this */
[$customer, $pr] = gateDraft();
$pr->update(['title' => 'Zurückgehaltene Meldung']);
$html = (new PressReleaseHeldForReview($customer, $pr, automationExhausted: true))->render();
expect($html)
->toContain('Zurückgehaltene Meldung')
->toContain('auch im 2. Durchlauf nicht')
->toContain('Sie müssen nichts tun')
->toContain('Stand der Prüfung ansehen');
$this->assertStringNotContainsString('Ablehnungsgrund', $html);
$yellow = (new PressReleaseHeldForReview($customer, $pr, automationExhausted: false))->render();
expect($yellow)->toContain('zur Sicherheit zurückgehalten');
$this->assertStringNotContainsString('Durchlauf', $yellow);
// Wer die Ablehnungsmails abbestellt hat, bekommt auch den Zwischenstand nicht.
$customer->optOutOfEmailNotification(EmailNotificationType::PressReleaseRejected);
$pr->update(['auto_check_attempts' => 2, 'status' => PressReleaseStatus::Review->value]);
redRun($pr, 'K1: Rot');
expect($pr->fresh()->isAwaitingManualReview())->toBeTrue();
Mail::assertNotQueued(PressReleaseHeldForReview::class);
});
test('after the human decision the automation stays off for this release', function () {