Fix-Runde: Markierung im selben Versuch wie der Versand, zwei Randpruefungen
$instance->update(['export_reminded_at' => now()]) lag ausserhalb des try/catch, das nur den Mailversand absicherte. Ein Fehlschlag dieses einen Schreibzugriffs haette die Marke leer gelassen (morgen eine zweite Mail fuer dieselbe Instanz) UND den ganzen Lauf abgebrochen, statt nur diese eine Instanz zu ueberspringen. Jetzt beides in derselben Absicherung. Dazu zwei Pruefungen, die der Rand "Ende schon vorbei" bislang nicht hatte: keine Erinnerung mehr, wenn die Frist schon abgelaufen ist (kein akademischer Fall - EndInstanceService laesst einen DNS-Fehler bewusst durch, eine Instanz kann also real tagelang mit abgelaufener Frist stehenbleiben), und die obere Einschlussgrenze bei genau sieben Tagen, damit beide Raender der "hoechstens sieben Tage"-Regel durch je eine eigene Pruefung belegt sind. Ausserdem: die Selbst-Herunterladen-Adresse selbst wird jetzt im Rendertest geprueft, nicht nur Datum und Sprachschluessel.claude/nice-moser-521659
parent
6f01097558
commit
5eef03d267
|
|
@ -63,8 +63,15 @@ class RemindEndingServices extends Command
|
|||
$customer = $instance->customer;
|
||||
|
||||
if ($customer === null || blank($customer->email)) {
|
||||
// Laut, aber ohne export_reminded_at zu setzen: eine Mail, die
|
||||
// nie eine Adresse hatte, ist keine, die verschickt wurde.
|
||||
// $customer === null ist über die Anwendung praktisch
|
||||
// unerreichbar: instances.customer_id ist ein
|
||||
// Pflicht-Fremdschlüssel mit Kaskade, und customers.email ist
|
||||
// NOT NULL. Trotzdem abgefangen statt vorausgesetzt — eine
|
||||
// Instanz ohne ansprechbaren Kunden soll den ganzen Lauf
|
||||
// nicht mitreißen, welcher Zustand sie auch dorthin gebracht
|
||||
// hätte. Laut, aber ohne export_reminded_at zu setzen: eine
|
||||
// Mail, die nie eine Adresse hatte, ist keine, die verschickt
|
||||
// wurde.
|
||||
Log::warning('Instanz nähert sich dem Laufzeitende, aber kein erreichbarer Kunde dahinter.', [
|
||||
'instance' => $instance->uuid,
|
||||
]);
|
||||
|
|
@ -85,8 +92,18 @@ class RemindEndingServices extends Command
|
|||
try {
|
||||
$mail = new ServiceEndingSoonMail($instance);
|
||||
Mail::mailer($mail->mailer)->to($customer->email)->queue($mail);
|
||||
|
||||
// In DERSELBEN Absicherung wie der Versand, nicht danach:
|
||||
// hinge ausgerechnet dieser Schreibzugriff (Datenbank kurz
|
||||
// weg), bliebe die Marke leer, obwohl die Mail schon
|
||||
// eingereiht ist — derselbe Kunde bekäme morgen eine zweite.
|
||||
// Und ein ungefangener Fehler hier würde den ganzen Lauf
|
||||
// abbrechen, statt nur diese eine Instanz zu überspringen —
|
||||
// die restlichen Instanzen des Tages kämen erst morgen an
|
||||
// die Reihe, ein Siebtel der sieben Tage Vorlauf verloren.
|
||||
$instance->update(['export_reminded_at' => now()]);
|
||||
} catch (Throwable $e) {
|
||||
Log::warning('Die Erinnerung vor dem Laufzeitende ließ sich nicht einreihen.', [
|
||||
Log::warning('Die Erinnerung vor dem Laufzeitende ließ sich nicht einreihen oder nicht vermerken.', [
|
||||
'instance' => $instance->uuid,
|
||||
'exception' => $e->getMessage(),
|
||||
]);
|
||||
|
|
@ -94,9 +111,6 @@ class RemindEndingServices extends Command
|
|||
continue;
|
||||
}
|
||||
|
||||
// Hält fest, dass erinnert wurde — ohne diese Spalte schickte der
|
||||
// tägliche Lauf dieselbe Mail an jedem der restlichen Tage erneut.
|
||||
$instance->update(['export_reminded_at' => now()]);
|
||||
$reminded++;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use App\Mail\ServiceEndingSoonMail;
|
||||
use App\Models\Instance;
|
||||
use App\Support\ProvisioningSettings;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
it('erinnert sieben Tage vor dem Ende', function () {
|
||||
|
|
@ -17,6 +18,21 @@ it('erinnert sieben Tage vor dem Ende', function () {
|
|||
expect($instance->fresh()->export_reminded_at)->not->toBeNull();
|
||||
});
|
||||
|
||||
it('erinnert auch, wenn das Ende genau in sieben Tagen liegt', function () {
|
||||
// Die obere Einschlussgrenze, gegenüber dem unteren Rand unten: die
|
||||
// Auswahl ist "höchstens sieben Tage", also gehört der siebte Tag noch
|
||||
// dazu, nicht erst der sechste.
|
||||
Mail::fake();
|
||||
Instance::factory()->create([
|
||||
'status' => 'cancellation_scheduled',
|
||||
'service_ends_at' => now()->addDays(7),
|
||||
]);
|
||||
|
||||
$this->artisan('clupilot:remind-ending-services')->assertSuccessful();
|
||||
|
||||
Mail::assertQueued(ServiceEndingSoonMail::class);
|
||||
});
|
||||
|
||||
it('erinnert nicht zweimal', function () {
|
||||
// Ein täglicher Zeitplan würde die Erinnerung sonst an jedem der
|
||||
// restlichen Tage erneut schicken. Eine Erinnerung im Sechserpack liest
|
||||
|
|
@ -45,6 +61,23 @@ it('erinnert nicht, wenn das Ende noch weit weg ist', function () {
|
|||
Mail::assertNothingQueued();
|
||||
});
|
||||
|
||||
it('erinnert nicht mehr, wenn das Ende schon vorbei ist', function () {
|
||||
// Kein akademischer Rand: EndInstanceService lässt einen DNS-Fehler
|
||||
// bewusst durch, eine gekündigte Instanz mit abgelaufener Frist kann also
|
||||
// real tagelang stehenbleiben, bis der stündliche Lauf durchkommt. Genau
|
||||
// dann darf keine Erinnerung mehr ausgehen — die Tür ist zu, und eine
|
||||
// Aufforderung zum Selbst-Herunterladen wäre eine Aufforderung ins Leere.
|
||||
Mail::fake();
|
||||
Instance::factory()->create([
|
||||
'status' => 'cancellation_scheduled',
|
||||
'service_ends_at' => now()->subHour(),
|
||||
]);
|
||||
|
||||
$this->artisan('clupilot:remind-ending-services')->assertSuccessful();
|
||||
|
||||
Mail::assertNothingQueued();
|
||||
});
|
||||
|
||||
it('erinnert eine Instanz, die gar nicht gekündigt wurde, nicht', function () {
|
||||
Mail::fake();
|
||||
Instance::factory()->create(['status' => 'active', 'service_ends_at' => now()->addDays(6)]);
|
||||
|
|
@ -80,7 +113,10 @@ it('trägt in der Mail das Datum und den Weg zum Selbst-Herunterladen', function
|
|||
$text = (new ServiceEndingSoonMail($instance))->render();
|
||||
|
||||
// Kein roher Schlüssel, und die zwei Dinge, um die es geht: ab wann ist
|
||||
// zu, und wie komme ich vorher an meine Sachen.
|
||||
// zu, und wie komme ich vorher an meine Sachen — die Adresse selbst muss
|
||||
// also wirklich im Text stehen, nicht nur der Knopf, der theoretisch
|
||||
// hinführen könnte.
|
||||
expect($text)->not->toContain('service_ending.')
|
||||
->and($text)->toContain($instance->service_ends_at->local()->isoFormat('LL'));
|
||||
->and($text)->toContain($instance->service_ends_at->local()->isoFormat('LL'))
|
||||
->and($text)->toContain('https://'.$instance->address(ProvisioningSettings::dnsZone()));
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue