seed(RolesAndPermissionsSeeder::class); // In der Testumgebung ist die Queue `sync`; für die Queue-Metriken den // Produktions-Treiber (database) verwenden, der die jobs-Tabellen liest. config()->set('queue.default', 'database'); }); test('editor cannot open the system health report', function () { /** @var TestCase $this */ $editor = User::factory()->create(['is_active' => true]); $editor->assignRole('editor'); $this->actingAs($editor); $this->get('https://pressekonto.test/admin/reports/system-health')->assertForbidden(); }); test('admin can open the system health report', function () { /** @var TestCase $this */ $admin = User::factory()->create(['is_active' => true]); $admin->assignRole('admin'); $this->actingAs($admin); $this->get('https://pressekonto.test/admin/reports/system-health') ->assertSuccessful() ->assertSee('Betriebsüberwachung') ->assertSee('Queue-Worker'); }); test('the system health report shows the deployed version', function () { /** @var TestCase $this */ config()->set('app.version', 'v1.1.0'); $admin = User::factory()->create(['is_active' => true]); $admin->assignRole('admin'); $this->actingAs($admin); $this->get('https://pressekonto.test/admin/reports/system-health') ->assertSuccessful() ->assertSee('Version: v1.1.0'); }); test('snapshot reports an ok queue when there are no waiting jobs', function () { /** @var TestCase $this */ $snapshot = app(AdminSystemHealth::class)->snapshot(); expect($snapshot['queue']['pending'])->toBe(0) ->and($snapshot['queue']['worker_running'])->toBeTrue() ->and($snapshot['queue']['status'])->toBe('ok'); }); test('snapshot flags a stalled worker when a job has waited too long', function () { /** @var TestCase $this */ DB::table('jobs')->insert([ 'queue' => 'default', 'payload' => json_encode(['displayName' => 'App\\Jobs\\Example']), 'attempts' => 0, 'reserved_at' => null, 'available_at' => now()->subMinutes(10)->getTimestamp(), 'created_at' => now()->subMinutes(10)->getTimestamp(), ]); $snapshot = app(AdminSystemHealth::class)->snapshot(); expect($snapshot['queue']['status'])->toBe('critical') ->and($snapshot['queue']['worker_running'])->toBeFalse() ->and($snapshot['overall'])->toBe('critical'); }); test('snapshot surfaces recent failed jobs', function () { /** @var TestCase $this */ DB::table('failed_jobs')->insert([ 'uuid' => (string) Str::uuid(), 'connection' => 'database', 'queue' => 'default', 'payload' => json_encode(['displayName' => 'App\\Jobs\\Broken']), 'exception' => "RuntimeException: boom\nStack trace...", 'failed_at' => now(), ]); $snapshot = app(AdminSystemHealth::class)->snapshot(); expect($snapshot['queue']['failed_recent'])->toBe(1) ->and($snapshot['queue']['recent_failures'][0]['job'])->toBe('App\\Jobs\\Broken') ->and($snapshot['queue']['recent_failures'][0]['error'])->toContain('boom') ->and($snapshot['queue']['status'])->toBe('warn'); }); test('snapshot flags missing permissions as critical', function () { /** @var TestCase $this */ Permission::query()->where('name', 'billing:manage')->delete(); app(PermissionRegistrar::class)->forgetCachedPermissions(); $checks = collect(app(AdminSystemHealth::class)->snapshot()['checks']) ->keyBy('key'); expect($checks['permissions']['status'])->toBe('critical') ->and($checks['permissions']['detail'])->toContain('billing:manage'); }); test('snapshot flags pending media privatization', function () { /** @var TestCase $this */ $pr = PressRelease::factory()->forPortal(Portal::Presseecho)->create(); PressReleaseImage::query()->create([ 'press_release_id' => $pr->id, 'disk' => 'public', 'path' => 'press-releases/'.$pr->id.'/images/legacy.jpg', ]); $checks = collect(app(AdminSystemHealth::class)->snapshot()['checks'])->keyBy('key'); expect($checks['media_privatization']['status'])->toBe('warn') ->and($checks['media_privatization']['detail'])->toContain('1'); }); test('snapshot flags a missing imagick extension', function (bool $precheckEnabled, string $status) { config()->set('images.precheck.enabled', $precheckEnabled); app()->bind(AdminSystemHealth::class, fn () => new class extends AdminSystemHealth { protected function imagickAvailable(): bool { return false; } }); $checks = collect(app(AdminSystemHealth::class)->snapshot()['checks'])->keyBy('key'); expect($checks['imagick']['status'])->toBe($status) ->and($checks['imagick']['detail'])->toContain('php8.4-imagick'); })->with([ 'precheck off' => [false, 'warn'], 'precheck on' => [true, 'critical'], ]); test('snapshot reports imagick as ok when it is loaded', function () { $checks = collect(app(AdminSystemHealth::class)->snapshot()['checks'])->keyBy('key'); expect($checks['imagick']['status'])->toBe(class_exists(Imagick::class) ? 'ok' : 'warn'); }); test('snapshot warns in production when the turnstile keys are missing', function () { app()->instance('env', 'production'); $checks = collect(app(AdminSystemHealth::class)->snapshot()['checks'])->keyBy('key'); expect($checks['turnstile']['status'])->toBe('warn') ->and($checks['turnstile']['detail'])->toContain('TURNSTILE_SITE_KEY'); config()->set('services.turnstile.site_key', '0x4AAAAAAAsitekey'); config()->set('services.turnstile.secret_key', '0x4AAAAAAAsecret'); $checks = collect(app(AdminSystemHealth::class)->snapshot()['checks'])->keyBy('key'); expect($checks['turnstile']['status'])->toBe('ok'); }); test('snapshot asks to review forms accepted without bot check', function () { $checks = collect(app(AdminSystemHealth::class)->snapshot()['checks'])->keyBy('key'); expect($checks['bot_check_unavailable']['status'])->toBe('ok'); app(ActivityLog::class)->record(ActivityType::BotCheckUnavailable, 'Kontaktanfrage KT-1 ohne Bot-Prüfung angenommen.'); $checks = collect(app(AdminSystemHealth::class)->snapshot()['checks'])->keyBy('key'); expect($checks['bot_check_unavailable']['status'])->toBe('warn') ->and($checks['bot_check_unavailable']['detail'])->toContain('1 Formular(e)'); });