fix: don't show health status for exited containers (#7317)

This commit is contained in:
Andras Bacsai
2025-11-24 10:29:57 +01:00
committed by GitHub
11 changed files with 65 additions and 48 deletions
@@ -136,7 +136,7 @@ it('ensures excluded status format is consistent across all paths', function ()
->toContain("return 'degraded:excluded';")
->toContain("return 'paused:excluded';")
->toContain("return 'starting:excluded';")
->toContain("return 'exited:excluded';")
->toContain("return 'exited';")
->toContain('return "$status:excluded";'); // For running:healthy:excluded, running:unhealthy:excluded, etc.
});
@@ -179,7 +179,7 @@ it('ensures calculateExcludedStatus uses ContainerStatusAggregator', function ()
->toContain("return 'degraded:excluded';")
->toContain("return 'paused:excluded';")
->toContain("return 'starting:excluded';")
->toContain("return 'exited:excluded';")
->toContain("return 'exited';")
->toContain('return "$status:excluded";'); // For running:healthy:excluded
});
@@ -199,7 +199,7 @@ it('ensures calculateExcludedStatusFromStrings uses ContainerStatusAggregator',
->toContain("return 'degraded:excluded';")
->toContain("return 'paused:excluded';")
->toContain("return 'starting:excluded';")
->toContain("return 'exited:excluded';")
->toContain("return 'exited';")
->toContain('return "$status:excluded";'); // For running:healthy:excluded
});
+14 -14
View File
@@ -8,10 +8,10 @@ beforeEach(function () {
});
describe('aggregateFromStrings', function () {
test('returns exited:unhealthy for empty collection', function () {
test('returns exited for empty collection', function () {
$result = $this->aggregator->aggregateFromStrings(collect());
expect($result)->toBe('exited:unhealthy');
expect($result)->toBe('exited');
});
test('returns running:healthy for single healthy running container', function () {
@@ -78,12 +78,12 @@ describe('aggregateFromStrings', function () {
expect($result)->toBe('degraded:unhealthy');
});
test('returns exited:unhealthy for exited containers without restart count', function () {
test('returns exited for exited containers without restart count', function () {
$statuses = collect(['exited']);
$result = $this->aggregator->aggregateFromStrings($statuses, maxRestartCount: 0);
expect($result)->toBe('exited:unhealthy');
expect($result)->toBe('exited');
});
test('returns degraded:unhealthy for dead container', function () {
@@ -200,10 +200,10 @@ describe('aggregateFromStrings', function () {
});
describe('aggregateFromContainers', function () {
test('returns exited:unhealthy for empty collection', function () {
test('returns exited for empty collection', function () {
$result = $this->aggregator->aggregateFromContainers(collect());
expect($result)->toBe('exited:unhealthy');
expect($result)->toBe('exited');
});
test('returns running:healthy for single healthy running container', function () {
@@ -299,7 +299,7 @@ describe('aggregateFromContainers', function () {
expect($result)->toBe('degraded:unhealthy');
});
test('returns exited:unhealthy for exited containers without restart count', function () {
test('returns exited for exited containers without restart count', function () {
$containers = collect([
(object) [
'State' => (object) [
@@ -310,7 +310,7 @@ describe('aggregateFromContainers', function () {
$result = $this->aggregator->aggregateFromContainers($containers, maxRestartCount: 0);
expect($result)->toBe('exited:unhealthy');
expect($result)->toBe('exited');
});
test('returns degraded:unhealthy for dead container', function () {
@@ -473,8 +473,8 @@ describe('maxRestartCount validation', function () {
// With negative value, should be treated as 0 (no restarts)
$result = $this->aggregator->aggregateFromStrings($statuses, maxRestartCount: -5);
// Should return exited:unhealthy (not degraded) since corrected to 0
expect($result)->toBe('exited:unhealthy');
// Should return exited (not degraded) since corrected to 0
expect($result)->toBe('exited');
});
test('negative maxRestartCount is corrected to 0 in aggregateFromContainers', function () {
@@ -493,8 +493,8 @@ describe('maxRestartCount validation', function () {
// With negative value, should be treated as 0 (no restarts)
$result = $this->aggregator->aggregateFromContainers($containers, maxRestartCount: -10);
// Should return exited:unhealthy (not degraded) since corrected to 0
expect($result)->toBe('exited:unhealthy');
// Should return exited (not degraded) since corrected to 0
expect($result)->toBe('exited');
});
test('zero maxRestartCount works correctly', function () {
@@ -503,7 +503,7 @@ describe('maxRestartCount validation', function () {
$result = $this->aggregator->aggregateFromStrings($statuses, maxRestartCount: 0);
// Zero is valid default - no crash loop detection
expect($result)->toBe('exited:unhealthy');
expect($result)->toBe('exited');
});
test('positive maxRestartCount works correctly', function () {
@@ -535,6 +535,6 @@ describe('maxRestartCount validation', function () {
// Call without specifying maxRestartCount (should default to 0)
$result = $this->aggregator->aggregateFromStrings($statuses);
expect($result)->toBe('exited:unhealthy');
expect($result)->toBe('exited');
});
});
+4 -7
View File
@@ -28,7 +28,7 @@ it('ensures ComplexStatusCheck returns excluded status when all containers exclu
->toContain('$aggregator->aggregateFromContainers($excludedOnly)')
->toContain("return 'degraded:excluded';")
->toContain("return 'paused:excluded';")
->toContain("return 'exited:excluded';")
->toContain("return 'exited';")
->toContain('return "$status:excluded";'); // For running:healthy:excluded
});
@@ -47,7 +47,7 @@ it('ensures Service model returns unknown:unknown:excluded when no containers ex
$serviceModelFile = file_get_contents(__DIR__.'/../../app/Models/Service.php');
// Check that when a service has no applications or databases at all,
// the Service model returns 'unknown:unknown:excluded' instead of 'exited:unhealthy:excluded'
// the Service model returns 'unknown:unknown:excluded' instead of 'exited'
// This prevents misleading status display when containers don't exist
expect($serviceModelFile)
->toContain('// If no status was calculated at all (no containers exist), return unknown')
@@ -85,12 +85,9 @@ it('ensures exclude_from_hc flag is properly checked in GetContainersStatus', fu
it('ensures UI displays excluded status correctly in status component', function () {
$servicesStatusFile = file_get_contents(__DIR__.'/../../resources/views/components/status/services.blade.php');
// Verify that the status component transforms :excluded suffix to (excluded) for better display
// Verify that the status component uses formatContainerStatus helper to display status
expect($servicesStatusFile)
->toContain('$isExcluded = str($complexStatus)->endsWith(\':excluded\');')
->toContain('$parts = explode(\':\', $complexStatus);')
->toContain('// Has health status: running:unhealthy:excluded → Running (unhealthy, excluded)')
->toContain('// No health status: exited:excluded → Exited (excluded)');
->toContain('formatContainerStatus($complexStatus)');
});
it('ensures UI handles excluded status in service heading buttons', function () {
+8 -8
View File
@@ -73,7 +73,7 @@ describe('Service Excluded Status Calculation', function () {
$service->shouldReceive('isStarting')->andReturn(false);
$app1 = makeResource('running:healthy', excludeFromStatus: false);
$app2 = makeResource('exited:unhealthy', excludeFromStatus: true);
$app2 = makeResource('exited', excludeFromStatus: true);
$service->shouldReceive('getAttribute')->with('applications')->andReturn(collect([$app1, $app2]));
$service->shouldReceive('getAttribute')->with('databases')->andReturn(collect());
@@ -87,7 +87,7 @@ describe('Service Excluded Status Calculation', function () {
$service->shouldReceive('isStarting')->andReturn(false);
$app1 = makeResource('running:healthy', excludeFromStatus: false);
$app2 = makeResource('exited:unhealthy', excludeFromStatus: false);
$app2 = makeResource('exited', excludeFromStatus: false);
$service->shouldReceive('getAttribute')->with('applications')->andReturn(collect([$app1, $app2]));
$service->shouldReceive('getAttribute')->with('databases')->andReturn(collect());
@@ -224,7 +224,7 @@ describe('Service Excluded Status Calculation', function () {
$service->shouldReceive('isStarting')->andReturn(false);
$app1 = makeResource('running:healthy', excludeFromStatus: false);
$app2 = makeResource('exited:unhealthy:excluded', excludeFromStatus: false);
$app2 = makeResource('exited:excluded', excludeFromStatus: false);
$service->shouldReceive('getAttribute')->with('applications')->andReturn(collect([$app1, $app2]));
$service->shouldReceive('getAttribute')->with('databases')->andReturn(collect());
@@ -245,7 +245,7 @@ describe('Service Excluded Status Calculation', function () {
expect($service->status)->toBe('running:healthy:excluded');
});
it('returns exited:unhealthy:excluded when excluded containers have no valid status', function () {
it('returns exited when excluded containers have no valid status', function () {
$service = Mockery::mock(Service::class)->makePartial();
$service->shouldReceive('isStarting')->andReturn(false);
@@ -254,7 +254,7 @@ describe('Service Excluded Status Calculation', function () {
$service->shouldReceive('getAttribute')->with('applications')->andReturn(collect([$app1]));
$service->shouldReceive('getAttribute')->with('databases')->andReturn(collect());
expect($service->status)->toBe('exited:unhealthy:excluded');
expect($service->status)->toBe('exited');
});
it('handles all excluded containers with degraded state', function () {
@@ -262,7 +262,7 @@ describe('Service Excluded Status Calculation', function () {
$service->shouldReceive('isStarting')->andReturn(false);
$app1 = makeResource('running:healthy', excludeFromStatus: true);
$app2 = makeResource('exited:unhealthy', excludeFromStatus: true);
$app2 = makeResource('exited', excludeFromStatus: true);
$service->shouldReceive('getAttribute')->with('applications')->andReturn(collect([$app1, $app2]));
$service->shouldReceive('getAttribute')->with('databases')->andReturn(collect());
@@ -286,12 +286,12 @@ describe('Service Excluded Status Calculation', function () {
$service = Mockery::mock(Service::class)->makePartial();
$service->shouldReceive('isStarting')->andReturn(false);
$app1 = makeResource('exited:unhealthy', excludeFromStatus: false);
$app1 = makeResource('exited', excludeFromStatus: false);
$service->shouldReceive('getAttribute')->with('applications')->andReturn(collect([$app1]));
$service->shouldReceive('getAttribute')->with('databases')->andReturn(collect());
expect($service->status)->toBe('exited:unhealthy');
expect($service->status)->toBe('exited');
});
it('prefers running over starting status', function () {