mivita/tests/Feature/PaymentDashboard/PaymentIncidentCrudTest.php
2026-04-14 18:07:45 +02:00

298 lines
12 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\Http\Controllers\Admin\PaymentDashboardController;
use App\Http\Requests\PaymentIncident\AddIncidentActivityRequest;
use App\Http\Requests\PaymentIncident\StorePaymentIncidentRequest;
use App\Http\Requests\PaymentIncident\UpdateIncidentStatusRequest;
use App\Models\IncidentActivity;
use App\Models\PaymentIncident;
use App\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
uses(RefreshDatabase::class);
function makeAdmin(): User
{
return User::forceCreate([
'email' => 'admin-crud-'.uniqid().'@test.com',
'password' => Hash::make('secret'),
'admin' => 2,
'lang' => 'de',
]);
}
// ─── Model / Scopes ───────────────────────────────────────────────────────────
it('scopeOpen gibt nur offene, laufende und wartende Incidents zurück', function () {
PaymentIncident::create(['title' => 'Offen', 'provider' => 'payone', 'type' => 'other', 'severity' => 'low', 'status' => 'open', 'detected_at' => now()]);
PaymentIncident::create(['title' => 'In Bearbeitung', 'provider' => 'payone', 'type' => 'other', 'severity' => 'low', 'status' => 'in_progress', 'detected_at' => now()]);
PaymentIncident::create(['title' => 'Wartet', 'provider' => 'payone', 'type' => 'other', 'severity' => 'low', 'status' => 'waiting_provider', 'detected_at' => now()]);
PaymentIncident::create(['title' => 'Gelöst', 'provider' => 'payone', 'type' => 'other', 'severity' => 'low', 'status' => 'resolved', 'detected_at' => now()]);
$open = PaymentIncident::open()->get();
expect($open)->toHaveCount(3);
expect($open->pluck('title')->toArray())->not->toContain('Gelöst');
});
it('scopePayone filtert nach PAYONE', function () {
PaymentIncident::create(['title' => 'PAYONE Incident', 'provider' => 'payone', 'type' => 'other', 'severity' => 'low', 'detected_at' => now()]);
PaymentIncident::create(['title' => 'Stripe Incident', 'provider' => 'stripe', 'type' => 'other', 'severity' => 'low', 'detected_at' => now()]);
expect(PaymentIncident::payone()->count())->toBe(1);
expect(PaymentIncident::payone()->first()->title)->toBe('PAYONE Incident');
});
it('scopeLastDays filtert nach Zeitraum', function () {
PaymentIncident::create(['title' => 'Alt', 'provider' => 'payone', 'type' => 'other', 'severity' => 'low', 'detected_at' => now()->subDays(10)]);
PaymentIncident::create(['title' => 'Neu', 'provider' => 'payone', 'type' => 'other', 'severity' => 'low', 'detected_at' => now()]);
expect(PaymentIncident::lastDays(7)->count())->toBe(1);
expect(PaymentIncident::lastDays(7)->first()->title)->toBe('Neu');
});
it('Accessor severity_color gibt Bootstrap-Klasse zurück', function () {
$incident = new PaymentIncident(['severity' => 'critical']);
expect($incident->severity_color)->toBe('danger');
$incident = new PaymentIncident(['severity' => 'high']);
expect($incident->severity_color)->toBe('warning');
$incident = new PaymentIncident(['severity' => 'low']);
expect($incident->severity_color)->toBe('success');
});
it('Accessor duration berechnet Dauer korrekt', function () {
$incident = new PaymentIncident([
'detected_at' => now()->subMinutes(90),
'resolved_at' => null,
]);
expect($incident->duration)->toContain('h');
});
// ─── Controller: Incident anlegen ─────────────────────────────────────────────
it('legt einen neuen Incident an und erstellt automatisch eine erste Aktivität', function () {
$admin = makeAdmin();
$this->actingAs($admin);
$request = StorePaymentIncidentRequest::create('/admin/payment-dashboard', 'POST', [
'title' => 'PAYONE IPN ausgefallen',
'provider' => 'payone',
'type' => 'ipn_error',
'severity' => 'high',
'detected_at' => now()->format('Y-m-d H:i:s'),
'affected_orders' => 5,
'affected_revenue' => '450.00',
]);
$request->setContainer(app());
$request->validateResolved();
try {
(new PaymentDashboardController)->store($request);
} catch (\Symfony\Component\Routing\Exception\RouteNotFoundException $e) {
// Domain-spezifische Routen sind im Test-Kontext nicht registriert erwartet.
}
$incident = PaymentIncident::where('title', 'PAYONE IPN ausgefallen')->first();
expect($incident)->not->toBeNull();
expect($incident->provider)->toBe('payone');
expect($incident->status)->toBe('open');
expect($incident->affected_orders)->toBe(5);
expect(IncidentActivity::where('incident_id', $incident->id)->count())->toBe(1);
expect(IncidentActivity::where('incident_id', $incident->id)->first()->type)->toBe('note');
});
// ─── FormRequest Validierung ──────────────────────────────────────────────────
it('validiert Pflichtfelder beim Anlegen eines Incidents', function () {
$rules = (new StorePaymentIncidentRequest)->rules();
$validator = Validator::make([], $rules);
expect($validator->fails())->toBeTrue();
expect($validator->errors()->has('title'))->toBeTrue();
expect($validator->errors()->has('provider'))->toBeTrue();
expect($validator->errors()->has('type'))->toBeTrue();
expect($validator->errors()->has('severity'))->toBeTrue();
expect($validator->errors()->has('detected_at'))->toBeTrue();
});
it('validiert ungültige Enum-Werte in StorePaymentIncidentRequest', function () {
$rules = (new StorePaymentIncidentRequest)->rules();
$validator = Validator::make([
'title' => 'Test',
'provider' => 'bitcoin',
'type' => 'unknown',
'severity' => 'extreme',
'detected_at' => now()->toDateTimeString(),
], $rules);
expect($validator->fails())->toBeTrue();
expect($validator->errors()->has('provider'))->toBeTrue();
expect($validator->errors()->has('type'))->toBeTrue();
expect($validator->errors()->has('severity'))->toBeTrue();
});
it('validiert Pflichtfelder in AddIncidentActivityRequest', function () {
$rules = (new AddIncidentActivityRequest)->rules();
$validator = Validator::make([], $rules);
expect($validator->fails())->toBeTrue();
expect($validator->errors()->has('type'))->toBeTrue();
expect($validator->errors()->has('title'))->toBeTrue();
});
// ─── Controller: Status ändern ────────────────────────────────────────────────
it('ändert den Status eines Incidents und erstellt eine Aktivität', function () {
$admin = makeAdmin();
$this->actingAs($admin);
$incident = PaymentIncident::create([
'title' => 'Status-Test',
'provider' => 'payone',
'type' => 'outage',
'severity' => 'critical',
'detected_at' => now(),
]);
$request = UpdateIncidentStatusRequest::create('/admin/payment-dashboard/'.$incident->id.'/status', 'PATCH', [
'status' => 'resolved',
]);
$request->setContainer(app());
$request->validateResolved();
$controller = new PaymentDashboardController;
$controller->updateStatus($request, $incident);
$incident->refresh();
expect($incident->status)->toBe('resolved');
expect($incident->resolved_at)->not->toBeNull();
$statusActivity = IncidentActivity::where('incident_id', $incident->id)
->where('type', 'status_change')
->first();
expect($statusActivity)->not->toBeNull();
expect($statusActivity->title)->toContain('Gelöst');
});
it('setzt resolved_at nicht wenn Status in_progress ist', function () {
$admin = makeAdmin();
$this->actingAs($admin);
$incident = PaymentIncident::create([
'title' => 'Resolved At Test',
'provider' => 'payone',
'type' => 'other',
'severity' => 'low',
'detected_at' => now(),
]);
$request = UpdateIncidentStatusRequest::create('', 'PATCH', ['status' => 'in_progress']);
$request->setContainer(app());
$request->validateResolved();
(new PaymentDashboardController)->updateStatus($request, $incident);
expect($incident->fresh()->resolved_at)->toBeNull();
});
// ─── Controller: Aktivität hinzufügen ────────────────────────────────────────
it('fügt eine Aktivität zu einem Incident hinzu', function () {
$admin = makeAdmin();
$this->actingAs($admin);
$incident = PaymentIncident::create([
'title' => 'Aktivitäts-Test',
'provider' => 'payone',
'type' => 'payment_failure',
'severity' => 'medium',
'detected_at' => now(),
]);
$request = AddIncidentActivityRequest::create('', 'POST', [
'type' => 'email',
'title' => 'Mail an PAYONE gesendet',
'content' => 'Ticket eröffnet, Antwort abwarten.',
]);
$request->setContainer(app());
$request->validateResolved();
(new PaymentDashboardController)->addActivity($request, $incident);
$activity = IncidentActivity::where('incident_id', $incident->id)
->where('type', 'email')
->first();
expect($activity)->not->toBeNull();
expect($activity->title)->toBe('Mail an PAYONE gesendet');
expect($activity->author)->toBe($admin->name ?? 'System');
});
it('setzt Incident automatisch auf in_progress wenn Aktivität bei offenem Incident hinzugefügt wird', function () {
$admin = makeAdmin();
$this->actingAs($admin);
$incident = PaymentIncident::create([
'title' => 'Auto-Status-Test',
'provider' => 'payone',
'type' => 'other',
'severity' => 'low',
'status' => 'open',
'detected_at' => now(),
]);
$request = AddIncidentActivityRequest::create('', 'POST', ['type' => 'note', 'title' => 'Erste Notiz']);
$request->setContainer(app());
$request->validateResolved();
(new PaymentDashboardController)->addActivity($request, $incident);
expect($incident->fresh()->status)->toBe('in_progress');
});
it('validiert Pflichtfelder in UpdateIncidentStatusRequest', function () {
$rules = (new UpdateIncidentStatusRequest)->rules();
$validator = Validator::make([], $rules);
expect($validator->fails())->toBeTrue();
expect($validator->errors()->has('status'))->toBeTrue();
});
it('validiert ungültigen Status in UpdateIncidentStatusRequest', function () {
$rules = (new UpdateIncidentStatusRequest)->rules();
$validator = Validator::make(['status' => 'deleted'], $rules);
expect($validator->fails())->toBeTrue();
expect($validator->errors()->has('status'))->toBeTrue();
});
// ─── Cache Invalidierung ──────────────────────────────────────────────────────
it('invalidiert den Cache nach Incident-Anlage', function () {
$admin = makeAdmin();
$this->actingAs($admin);
Cache::put('open_incident_count', 99, 60);
$request = StorePaymentIncidentRequest::create('', 'POST', [
'title' => 'Cache-Test',
'provider' => 'payone',
'type' => 'other',
'severity' => 'low',
'detected_at' => now()->format('Y-m-d H:i:s'),
]);
$request->setContainer(app());
$request->validateResolved();
try {
(new PaymentDashboardController)->store($request);
} catch (\Symfony\Component\Routing\Exception\RouteNotFoundException $e) {
// Domain-spezifische Routen sind im Test-Kontext nicht registriert erwartet.
}
expect(Cache::has('open_incident_count'))->toBeFalse();
});