set('monitoring.health.token', 'geheimes-monitor-token'); config()->set('monitoring.health.disk_min_free_percent', 0); config()->set('queue.default', 'database'); }); function healthHeaders(): array { return ['Authorization' => 'Bearer geheimes-monitor-token']; } test('the endpoint does not exist without a configured token or with a wrong one', function () { app(HealthProbe::class)->recordHeartbeat(); $this->get('/health', healthHeaders())->assertOk(); $this->get('/health', ['Authorization' => 'Bearer falsch'])->assertNotFound(); $this->get('/health')->assertNotFound(); config()->set('monitoring.health.token', null); $this->get('/health', healthHeaders())->assertNotFound(); }); test('a healthy system answers 200 with every check', function () { config()->set('app.version', 'v1.1.0'); $this->artisan('monitoring:heartbeat')->assertSuccessful(); $this->get('/health?token=geheimes-monitor-token') ->assertOk() ->assertHeader('Cache-Control', 'no-store, private') ->assertJsonPath('status', 'ok') ->assertJsonPath('version', 'v1.1.0') ->assertJsonPath('checks.database.status', 'ok') ->assertJsonPath('checks.cache.status', 'ok') ->assertJsonPath('checks.queue.status', 'ok') ->assertJsonPath('checks.scheduler.status', 'ok') ->assertJsonStructure(['checks' => ['disk' => ['status', 'detail']]]); }); test('a missing or stale heartbeat makes the endpoint fail', function () { $this->get('/health', healthHeaders()) ->assertStatus(503) ->assertJsonPath('checks.scheduler.status', 'critical'); app(HealthProbe::class)->recordHeartbeat(); $this->travel(4)->minutes(); $this->get('/health', healthHeaders())->assertOk()->assertJsonPath('checks.scheduler.status', 'warn'); $this->travel(7)->minutes(); $this->get('/health', healthHeaders())->assertStatus(503)->assertJsonPath('checks.scheduler.status', 'critical'); }); test('a queue backlog older than five minutes makes the endpoint fail', function () { app(HealthProbe::class)->recordHeartbeat(); DB::table('jobs')->insert([ 'queue' => 'default', 'payload' => '{}', 'attempts' => 0, 'reserved_at' => null, 'available_at' => now()->subMinutes(6)->getTimestamp(), 'created_at' => now()->subMinutes(6)->getTimestamp(), ]); $this->get('/health', healthHeaders()) ->assertStatus(503) ->assertJsonPath('checks.queue.status', 'critical'); }); test('the endpoint answers on every brand domain and skips basic auth', function (string $domainKey) { config()->set('auth.basic.enabled', true); config()->set('auth.basic.user', 'staging'); config()->set('auth.basic.password', 'staging'); app(HealthProbe::class)->recordHeartbeat(); $this->get('http://'.config($domainKey).'/health', healthHeaders()) ->assertOk() ->assertJsonPath('status', 'ok'); })->with(['domains.domain_portal', 'domains.domain_presseecho', 'domains.domain_businessportal']); test('the admin system health shows scheduler and disk', function () { $checks = collect(app(AdminSystemHealth::class)->snapshot()['checks'])->keyBy('key'); expect($checks->get('scheduler')['status'])->toBe('critical') ->and($checks->get('scheduler')['detail'])->toContain('Noch kein Herzschlag') ->and($checks)->toHaveKey('disk'); app(HealthProbe::class)->recordHeartbeat(); $checks = collect(app(AdminSystemHealth::class)->snapshot()['checks'])->keyBy('key'); expect($checks->get('scheduler')['status'])->toBe('ok'); });