Http::response('', 200), ]); $this->artisan('payment:check-uptime')->assertSuccessful(); $log = ProviderUptimeLog::where('provider', 'payone')->latest()->first(); expect($log)->not->toBeNull(); expect($log->is_up)->toBeTrue(); expect($log->error_message)->toBeNull(); }); it('schreibt einen negativen ProviderUptimeLog und legt Incident an wenn PAYONE nicht erreichbar ist', function () { Http::fake([ 'api.pay1.de/*' => Http::response('Server Error', 503), ]); $this->artisan('payment:check-uptime')->assertSuccessful(); $log = ProviderUptimeLog::where('provider', 'payone')->latest()->first(); expect($log)->not->toBeNull(); expect($log->is_up)->toBeFalse(); $incident = PaymentIncident::where('provider', 'payone')->where('type', 'outage')->first(); expect($incident)->not->toBeNull(); expect($incident->severity)->toBe('critical'); expect($incident->status)->toBe('open'); }); it('erstellt keine doppelten Outage-Incidents bei mehrfachem Ausfall in derselben Stunde', function () { Http::fake([ 'api.pay1.de/*' => Http::response('', 503), ]); $this->artisan('payment:check-uptime'); $this->artisan('payment:check-uptime'); expect(PaymentIncident::where('provider', 'payone')->where('type', 'outage')->count())->toBe(1); }); it('löst offene Outage-Incidents automatisch auf wenn PAYONE wieder erreichbar ist', function () { PaymentIncident::create([ 'title' => 'Automatischer Ausfall', 'provider' => 'payone', 'type' => 'outage', 'severity' => 'critical', 'status' => 'open', 'detected_at' => now()->subMinutes(10), ]); Http::fake([ 'api.pay1.de/*' => Http::response('', 200), ]); $this->artisan('payment:check-uptime')->assertSuccessful(); $incident = PaymentIncident::where('provider', 'payone')->where('type', 'outage')->first(); expect($incident->fresh()->status)->toBe('resolved'); expect($incident->fresh()->resolved_at)->not->toBeNull(); $activity = IncidentActivity::where('incident_id', $incident->id) ->where('type', 'status_change') ->first(); expect($activity)->not->toBeNull(); }); it('verarbeitet Verbindungsfehler als Ausfall', function () { Http::fake([ 'api.pay1.de/*' => fn () => throw new \Exception('Connection refused'), ]); $this->artisan('payment:check-uptime')->assertSuccessful(); $log = ProviderUptimeLog::where('provider', 'payone')->latest()->first(); expect($log->is_up)->toBeFalse(); expect($log->error_message)->toContain('Connection refused'); }); // ─── Mailable ──────────────────────────────────────────────────────────────── it('sendet PaymentIncidentAlert-Mail wenn critical Incident angelegt wird', function () { Mail::fake(); $incident = PaymentIncident::create([ 'title' => 'Kritischer Test-Incident', 'provider' => 'payone', 'type' => 'outage', 'severity' => 'critical', 'detected_at' => now(), ]); Mail::to(config('app.exception_mail'))->queue(new PaymentIncidentAlert($incident)); Mail::assertQueued(PaymentIncidentAlert::class, function (PaymentIncidentAlert $mail) use ($incident) { return $mail->incident->id === $incident->id; }); });