mirror of
https://github.com/coollabsio/coolify.git
synced 2025-12-28 05:34:50 +00:00
- Introduced tests for ContainerStatusAggregator to validate status aggregation logic across various container states. - Implemented tests to ensure serverStatus accessor correctly checks server infrastructure health without being affected by container status. - Updated ExcludeFromHealthCheckTest to verify excluded status handling in various components. - Removed obsolete PushServerUpdateJobStatusAggregationTest as its functionality is covered elsewhere. - Updated version number for sentinel to 0.0.17 in versions.json.
224 lines
12 KiB
PHP
224 lines
12 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Unit tests to verify consistent handling of all-excluded containers
|
|
* across PushServerUpdateJob, GetContainersStatus, and ComplexStatusCheck.
|
|
*
|
|
* These tests verify the fix for issue where different code paths handled
|
|
* all-excluded containers inconsistently:
|
|
* - PushServerUpdateJob (Sentinel, ~30s) previously skipped updates
|
|
* - GetContainersStatus (SSH, ~1min) previously skipped updates
|
|
* - ComplexStatusCheck (Multi-server) correctly calculated :excluded status
|
|
*
|
|
* After this fix, all three paths now calculate and return :excluded status
|
|
* consistently, preventing status drift and UI inconsistencies.
|
|
*/
|
|
it('ensures CalculatesExcludedStatus trait exists with required methods', function () {
|
|
$traitFile = file_get_contents(__DIR__.'/../../app/Traits/CalculatesExcludedStatus.php');
|
|
|
|
// Verify trait has both status calculation methods
|
|
expect($traitFile)
|
|
->toContain('trait CalculatesExcludedStatus')
|
|
->toContain('protected function calculateExcludedStatus(Collection $containers, Collection $excludedContainers): string')
|
|
->toContain('protected function calculateExcludedStatusFromStrings(Collection $containerStatuses): string')
|
|
->toContain('protected function getExcludedContainersFromDockerCompose(?string $dockerComposeRaw): Collection');
|
|
});
|
|
|
|
it('ensures ComplexStatusCheck uses CalculatesExcludedStatus trait', function () {
|
|
$complexStatusCheckFile = file_get_contents(__DIR__.'/../../app/Actions/Shared/ComplexStatusCheck.php');
|
|
|
|
// Verify trait is used
|
|
expect($complexStatusCheckFile)
|
|
->toContain('use App\Traits\CalculatesExcludedStatus;')
|
|
->toContain('use CalculatesExcludedStatus;');
|
|
|
|
// Verify it uses the trait method instead of inline code
|
|
expect($complexStatusCheckFile)
|
|
->toContain('return $this->calculateExcludedStatus($containers, $excludedContainers);');
|
|
|
|
// Verify it uses the trait helper for excluded containers
|
|
expect($complexStatusCheckFile)
|
|
->toContain('$excludedContainers = $this->getExcludedContainersFromDockerCompose($dockerComposeRaw);');
|
|
});
|
|
|
|
it('ensures PushServerUpdateJob uses CalculatesExcludedStatus trait', function () {
|
|
$pushServerUpdateJobFile = file_get_contents(__DIR__.'/../../app/Jobs/PushServerUpdateJob.php');
|
|
|
|
// Verify trait is used
|
|
expect($pushServerUpdateJobFile)
|
|
->toContain('use App\Traits\CalculatesExcludedStatus;')
|
|
->toContain('use CalculatesExcludedStatus;');
|
|
|
|
// Verify it calculates excluded status instead of skipping (old behavior: continue)
|
|
expect($pushServerUpdateJobFile)
|
|
->toContain('// If all containers are excluded, calculate status from excluded containers')
|
|
->toContain('$aggregatedStatus = $this->calculateExcludedStatusFromStrings($containerStatuses);');
|
|
|
|
// Verify it uses the trait helper for excluded containers
|
|
expect($pushServerUpdateJobFile)
|
|
->toContain('$excludedContainers = $this->getExcludedContainersFromDockerCompose($dockerComposeRaw);');
|
|
});
|
|
|
|
it('ensures PushServerUpdateJob calculates excluded status for applications', function () {
|
|
$pushServerUpdateJobFile = file_get_contents(__DIR__.'/../../app/Jobs/PushServerUpdateJob.php');
|
|
|
|
// In aggregateMultiContainerStatuses, verify the all-excluded scenario
|
|
// calculates status and updates the application
|
|
expect($pushServerUpdateJobFile)
|
|
->toContain('if ($relevantStatuses->isEmpty()) {')
|
|
->toContain('$aggregatedStatus = $this->calculateExcludedStatusFromStrings($containerStatuses);')
|
|
->toContain('if ($aggregatedStatus && $application->status !== $aggregatedStatus) {')
|
|
->toContain('$application->status = $aggregatedStatus;')
|
|
->toContain('$application->save();');
|
|
});
|
|
|
|
it('ensures PushServerUpdateJob calculates excluded status for services', function () {
|
|
$pushServerUpdateJobFile = file_get_contents(__DIR__.'/../../app/Jobs/PushServerUpdateJob.php');
|
|
|
|
// Count occurrences - should appear twice (once for applications, once for services)
|
|
$calculateExcludedCount = substr_count(
|
|
$pushServerUpdateJobFile,
|
|
'$aggregatedStatus = $this->calculateExcludedStatusFromStrings($containerStatuses);'
|
|
);
|
|
|
|
expect($calculateExcludedCount)->toBe(2, 'Should calculate excluded status for both applications and services');
|
|
});
|
|
|
|
it('ensures GetContainersStatus uses CalculatesExcludedStatus trait', function () {
|
|
$getContainersStatusFile = file_get_contents(__DIR__.'/../../app/Actions/Docker/GetContainersStatus.php');
|
|
|
|
// Verify trait is used
|
|
expect($getContainersStatusFile)
|
|
->toContain('use App\Traits\CalculatesExcludedStatus;')
|
|
->toContain('use CalculatesExcludedStatus;');
|
|
|
|
// Verify it calculates excluded status instead of returning null
|
|
expect($getContainersStatusFile)
|
|
->toContain('// If all containers are excluded, calculate status from excluded containers')
|
|
->toContain('return $this->calculateExcludedStatusFromStrings($containerStatuses);');
|
|
|
|
// Verify it uses the trait helper for excluded containers
|
|
expect($getContainersStatusFile)
|
|
->toContain('$excludedContainers = $this->getExcludedContainersFromDockerCompose($dockerComposeRaw);');
|
|
});
|
|
|
|
it('ensures GetContainersStatus calculates excluded status for applications', function () {
|
|
$getContainersStatusFile = file_get_contents(__DIR__.'/../../app/Actions/Docker/GetContainersStatus.php');
|
|
|
|
// In aggregateApplicationStatus, verify the all-excluded scenario returns status
|
|
expect($getContainersStatusFile)
|
|
->toContain('if ($relevantStatuses->isEmpty()) {')
|
|
->toContain('return $this->calculateExcludedStatusFromStrings($containerStatuses);');
|
|
});
|
|
|
|
it('ensures GetContainersStatus calculates excluded status for services', function () {
|
|
$getContainersStatusFile = file_get_contents(__DIR__.'/../../app/Actions/Docker/GetContainersStatus.php');
|
|
|
|
// In aggregateServiceContainerStatuses, verify the all-excluded scenario updates status
|
|
expect($getContainersStatusFile)
|
|
->toContain('$aggregatedStatus = $this->calculateExcludedStatusFromStrings($containerStatuses);')
|
|
->toContain('if ($aggregatedStatus) {')
|
|
->toContain('$statusFromDb = $subResource->status;')
|
|
->toContain("if (\$statusFromDb !== \$aggregatedStatus) {\n \$subResource->update(['status' => \$aggregatedStatus]);");
|
|
});
|
|
|
|
it('ensures excluded status format is consistent across all paths', function () {
|
|
$traitFile = file_get_contents(__DIR__.'/../../app/Traits/CalculatesExcludedStatus.php');
|
|
|
|
// Trait now delegates to ContainerStatusAggregator and uses appendExcludedSuffix helper
|
|
expect($traitFile)
|
|
->toContain('use App\\Services\\ContainerStatusAggregator;')
|
|
->toContain('$aggregator = new ContainerStatusAggregator;')
|
|
->toContain('private function appendExcludedSuffix(string $status): string');
|
|
|
|
// Check that appendExcludedSuffix returns consistent colon format with :excluded suffix
|
|
expect($traitFile)
|
|
->toContain("return 'degraded:excluded';")
|
|
->toContain("return 'paused:excluded';")
|
|
->toContain("return 'starting:excluded';")
|
|
->toContain("return 'exited:excluded';")
|
|
->toContain('return "$status:excluded";'); // For running:healthy:excluded, running:unhealthy:excluded, etc.
|
|
});
|
|
|
|
it('ensures all three paths check for exclude_from_hc flag consistently', function () {
|
|
// All three should use the trait helper method
|
|
$complexStatusCheckFile = file_get_contents(__DIR__.'/../../app/Actions/Shared/ComplexStatusCheck.php');
|
|
$pushServerUpdateJobFile = file_get_contents(__DIR__.'/../../app/Jobs/PushServerUpdateJob.php');
|
|
$getContainersStatusFile = file_get_contents(__DIR__.'/../../app/Actions/Docker/GetContainersStatus.php');
|
|
|
|
expect($complexStatusCheckFile)
|
|
->toContain('$excludedContainers = $this->getExcludedContainersFromDockerCompose($dockerComposeRaw);');
|
|
|
|
expect($pushServerUpdateJobFile)
|
|
->toContain('$excludedContainers = $this->getExcludedContainersFromDockerCompose($dockerComposeRaw);');
|
|
|
|
expect($getContainersStatusFile)
|
|
->toContain('$excludedContainers = $this->getExcludedContainersFromDockerCompose($dockerComposeRaw);');
|
|
|
|
// The trait method should check both exclude_from_hc and restart: no
|
|
$traitFile = file_get_contents(__DIR__.'/../../app/Traits/CalculatesExcludedStatus.php');
|
|
expect($traitFile)
|
|
->toContain('$excludeFromHc = data_get($serviceConfig, \'exclude_from_hc\', false);')
|
|
->toContain('$restartPolicy = data_get($serviceConfig, \'restart\', \'always\');')
|
|
->toContain('if ($excludeFromHc || $restartPolicy === \'no\') {');
|
|
});
|
|
|
|
it('ensures calculateExcludedStatus uses ContainerStatusAggregator', function () {
|
|
$traitFile = file_get_contents(__DIR__.'/../../app/Traits/CalculatesExcludedStatus.php');
|
|
|
|
// Check that the trait uses ContainerStatusAggregator service instead of duplicating logic
|
|
expect($traitFile)
|
|
->toContain('protected function calculateExcludedStatus(Collection $containers, Collection $excludedContainers): string')
|
|
->toContain('use App\Services\ContainerStatusAggregator;')
|
|
->toContain('$aggregator = new ContainerStatusAggregator;')
|
|
->toContain('$aggregator->aggregateFromContainers($excludedOnly)');
|
|
|
|
// Check that it has appendExcludedSuffix helper for all states
|
|
expect($traitFile)
|
|
->toContain('private function appendExcludedSuffix(string $status): string')
|
|
->toContain("return 'degraded:excluded';")
|
|
->toContain("return 'paused:excluded';")
|
|
->toContain("return 'starting:excluded';")
|
|
->toContain("return 'exited:excluded';")
|
|
->toContain('return "$status:excluded";'); // For running:healthy:excluded
|
|
});
|
|
|
|
it('ensures calculateExcludedStatusFromStrings uses ContainerStatusAggregator', function () {
|
|
$traitFile = file_get_contents(__DIR__.'/../../app/Traits/CalculatesExcludedStatus.php');
|
|
|
|
// Check that the trait uses ContainerStatusAggregator service instead of duplicating logic
|
|
expect($traitFile)
|
|
->toContain('protected function calculateExcludedStatusFromStrings(Collection $containerStatuses): string')
|
|
->toContain('use App\Services\ContainerStatusAggregator;')
|
|
->toContain('$aggregator = new ContainerStatusAggregator;')
|
|
->toContain('$aggregator->aggregateFromStrings($containerStatuses)');
|
|
|
|
// Check that it has appendExcludedSuffix helper for all states
|
|
expect($traitFile)
|
|
->toContain('private function appendExcludedSuffix(string $status): string')
|
|
->toContain("return 'degraded:excluded';")
|
|
->toContain("return 'paused:excluded';")
|
|
->toContain("return 'starting:excluded';")
|
|
->toContain("return 'exited:excluded';")
|
|
->toContain('return "$status:excluded";'); // For running:healthy:excluded
|
|
});
|
|
|
|
it('verifies no code path skips update when all containers excluded', function () {
|
|
$pushServerUpdateJobFile = file_get_contents(__DIR__.'/../../app/Jobs/PushServerUpdateJob.php');
|
|
$getContainersStatusFile = file_get_contents(__DIR__.'/../../app/Actions/Docker/GetContainersStatus.php');
|
|
|
|
// These patterns should NOT exist anymore (old behavior that caused drift)
|
|
expect($pushServerUpdateJobFile)
|
|
->not->toContain("// If all containers are excluded, don't update status");
|
|
|
|
expect($getContainersStatusFile)
|
|
->not->toContain("// If all containers are excluded, don't update status");
|
|
|
|
// Instead, both should calculate excluded status
|
|
expect($pushServerUpdateJobFile)
|
|
->toContain('// If all containers are excluded, calculate status from excluded containers');
|
|
|
|
expect($getContainersStatusFile)
|
|
->toContain('// If all containers are excluded, calculate status from excluded containers');
|
|
});
|