108 lines
4.0 KiB
PHP
108 lines
4.0 KiB
PHP
<?php
|
|
|
|
use App\Models\Instance;
|
|
use App\Models\MonitoringTarget;
|
|
use App\Provisioning\Jobs\SyncMonitoringStatus;
|
|
use App\Services\Monitoring\MonitoringClient;
|
|
|
|
/**
|
|
* Monitoring verdicts have to be measured, not remembered.
|
|
*
|
|
* The status column was written once — 'up', at provisioning — and nothing ever
|
|
* updated it. The console's notices and the public status page both read it, so
|
|
* an outage would have been reported as healthy indefinitely, on a page whose
|
|
* whole purpose is to be believed.
|
|
*/
|
|
|
|
function target(string $externalId, array $attributes = []): MonitoringTarget
|
|
{
|
|
$instance = Instance::factory()->create(['status' => 'active']);
|
|
|
|
return MonitoringTarget::query()->create(array_merge([
|
|
'instance_id' => $instance->id,
|
|
'external_id' => $externalId,
|
|
'url' => 'https://example.test',
|
|
'status' => 'unknown',
|
|
], $attributes));
|
|
}
|
|
|
|
it('records what monitoring actually reports', function () {
|
|
$up = target('mon-up');
|
|
$down = target('mon-down');
|
|
|
|
$this->mock(MonitoringClient::class, function ($mock) {
|
|
$mock->shouldReceive('health')->with('mon-up')->andReturnTrue();
|
|
$mock->shouldReceive('health')->with('mon-down')->andReturnFalse();
|
|
});
|
|
|
|
(new SyncMonitoringStatus)->handle(app(MonitoringClient::class));
|
|
|
|
expect($up->refresh()->status)->toBe('up')
|
|
->and($up->checked_at)->not->toBeNull()
|
|
->and($down->refresh()->status)->toBe('down');
|
|
});
|
|
|
|
it('does not blame the instance when the monitoring service is the one that is down', function () {
|
|
// "The monitor did not answer" says nothing about the instance. Writing
|
|
// 'down' would raise an alarm about the wrong system; writing 'up' would
|
|
// hide a real one.
|
|
$target = target('mon-x', ['status' => 'up', 'checked_at' => now()->subMinutes(3)]);
|
|
$wasCheckedAt = $target->checked_at;
|
|
|
|
$this->mock(MonitoringClient::class, function ($mock) {
|
|
$mock->shouldReceive('health')->andThrow(new RuntimeException('connection refused'));
|
|
});
|
|
|
|
(new SyncMonitoringStatus)->handle(app(MonitoringClient::class));
|
|
|
|
expect($target->refresh()->status)->toBe('up')
|
|
// Not advanced — so the verdict is allowed to go stale and stop counting.
|
|
->and($target->checked_at->timestamp)->toBe($wasCheckedAt->timestamp);
|
|
});
|
|
|
|
it('does not treat a never-checked target as healthy on the public page', function () {
|
|
target('mon-fresh');
|
|
|
|
$this->get('/status')
|
|
->assertOk()
|
|
->assertSee(__('status.state.unknown'))
|
|
->assertDontSee(__('status.overall.operational'));
|
|
});
|
|
|
|
it('stops trusting a verdict nobody has refreshed', function () {
|
|
target('mon-old', ['status' => 'up', 'checked_at' => now()->subHours(2)]);
|
|
|
|
$this->get('/status')
|
|
->assertOk()
|
|
->assertSee(__('status.state.unknown'));
|
|
});
|
|
|
|
it('records nothing when the monitor cannot answer at all', function () {
|
|
// The boolean contract folded "no monitoring configured" into true and
|
|
// "monitor unreachable" into false. Recording either as a measurement
|
|
// publishes a fleet-wide all-clear nobody took, or a fleet-wide outage that
|
|
// is really one broken monitor.
|
|
$target = target('mon-quiet', ['status' => 'up', 'checked_at' => now()->subMinutes(4)]);
|
|
$wasCheckedAt = $target->checked_at;
|
|
|
|
$this->mock(MonitoringClient::class, function ($mock) {
|
|
$mock->shouldReceive('health')->andReturnNull();
|
|
});
|
|
|
|
(new SyncMonitoringStatus)->handle(app(MonitoringClient::class));
|
|
|
|
expect($target->refresh()->status)->toBe('up')
|
|
->and($target->checked_at->timestamp)->toBe($wasCheckedAt->timestamp);
|
|
});
|
|
|
|
it('keeps the acceptance check passing when monitoring is not set up', function () {
|
|
// isHealthy() still folds "cannot tell" into a pass — deliberately, and
|
|
// only there: a delivery must not fail because monitoring is unconfigured.
|
|
config()->set('services.monitoring.url', '');
|
|
|
|
$client = new App\Services\Monitoring\HttpMonitoringClient;
|
|
|
|
expect($client->health('anything'))->toBeNull()
|
|
->and($client->isHealthy('anything'))->toBeTrue();
|
|
});
|