mirror of
https://github.com/coollabsio/coolify.git
synced 2025-12-28 13:41:51 +00:00
- Added `requiredPort` property to `ServiceApplicationView` to track the required port for services. - Introduced modal confirmation for removing required ports, including methods to confirm or cancel the action. - Enhanced `Service` model with `getRequiredPort` and `requiresPort` methods to retrieve port information from service templates. - Implemented `extractPortFromUrl` method in `ServiceApplication` to extract port from FQDN URLs. - Updated frontend views to display warnings when required ports are missing from domains. - Created unit tests for service port validation and extraction logic, ensuring correct behavior for various scenarios. - Added feature tests for Livewire component handling of domain submissions with required ports.
297 lines
9.6 KiB
PHP
297 lines
9.6 KiB
PHP
<?php
|
|
|
|
use Symfony\Component\Yaml\Yaml;
|
|
|
|
/**
|
|
* Unit tests to verify that environment variables with empty strings
|
|
* in Docker Compose files are preserved as empty strings, not converted to null.
|
|
*
|
|
* This is important because empty strings and null have different semantics in Docker:
|
|
* - Empty string: Variable is set to "" (e.g., HTTP_PROXY="" means "no proxy")
|
|
* - Null: Variable is unset/removed from container environment
|
|
*
|
|
* See: https://github.com/coollabsio/coolify/issues/7126
|
|
*/
|
|
it('ensures parsers.php preserves empty strings in application parser', function () {
|
|
$parsersFile = file_get_contents(__DIR__.'/../../bootstrap/helpers/parsers.php');
|
|
|
|
// Find the applicationParser function's environment mapping logic
|
|
$hasApplicationParser = str_contains($parsersFile, 'function applicationParser(');
|
|
expect($hasApplicationParser)->toBeTrue('applicationParser function should exist');
|
|
|
|
// The code should distinguish between null and empty string
|
|
// Check for the pattern where we explicitly check for null vs empty string
|
|
$hasNullCheck = str_contains($parsersFile, 'if ($value === null)');
|
|
$hasEmptyStringCheck = str_contains($parsersFile, "} elseif (\$value === '') {");
|
|
|
|
expect($hasNullCheck)->toBeTrue('Should have explicit null check');
|
|
expect($hasEmptyStringCheck)->toBeTrue('Should have explicit empty string check');
|
|
});
|
|
|
|
it('ensures parsers.php preserves empty strings in service parser', function () {
|
|
$parsersFile = file_get_contents(__DIR__.'/../../bootstrap/helpers/parsers.php');
|
|
|
|
// Find the serviceParser function's environment mapping logic
|
|
$hasServiceParser = str_contains($parsersFile, 'function serviceParser(');
|
|
expect($hasServiceParser)->toBeTrue('serviceParser function should exist');
|
|
|
|
// The code should distinguish between null and empty string
|
|
// Same check as application parser
|
|
$hasNullCheck = str_contains($parsersFile, 'if ($value === null)');
|
|
$hasEmptyStringCheck = str_contains($parsersFile, "} elseif (\$value === '') {");
|
|
|
|
expect($hasNullCheck)->toBeTrue('Should have explicit null check');
|
|
expect($hasEmptyStringCheck)->toBeTrue('Should have explicit empty string check');
|
|
});
|
|
|
|
it('verifies YAML parsing preserves empty strings correctly', function () {
|
|
// Test that Symfony YAML parser handles empty strings as we expect
|
|
$yamlWithEmptyString = <<<'YAML'
|
|
environment:
|
|
HTTP_PROXY: ""
|
|
HTTPS_PROXY: ''
|
|
NO_PROXY: "localhost"
|
|
YAML;
|
|
|
|
$parsed = Yaml::parse($yamlWithEmptyString);
|
|
|
|
// Empty strings should remain as empty strings, not null
|
|
expect($parsed['environment']['HTTP_PROXY'])->toBe('');
|
|
expect($parsed['environment']['HTTPS_PROXY'])->toBe('');
|
|
expect($parsed['environment']['NO_PROXY'])->toBe('localhost');
|
|
});
|
|
|
|
it('verifies YAML parsing handles null values correctly', function () {
|
|
// Test that null values are preserved as null
|
|
$yamlWithNull = <<<'YAML'
|
|
environment:
|
|
HTTP_PROXY: null
|
|
HTTPS_PROXY:
|
|
NO_PROXY: "localhost"
|
|
YAML;
|
|
|
|
$parsed = Yaml::parse($yamlWithNull);
|
|
|
|
// Null should remain null
|
|
expect($parsed['environment']['HTTP_PROXY'])->toBeNull();
|
|
expect($parsed['environment']['HTTPS_PROXY'])->toBeNull();
|
|
expect($parsed['environment']['NO_PROXY'])->toBe('localhost');
|
|
});
|
|
|
|
it('verifies YAML serialization preserves empty strings', function () {
|
|
// Test that empty strings serialize back correctly
|
|
$data = [
|
|
'environment' => [
|
|
'HTTP_PROXY' => '',
|
|
'HTTPS_PROXY' => '',
|
|
'NO_PROXY' => 'localhost',
|
|
],
|
|
];
|
|
|
|
$yaml = Yaml::dump($data, 10, 2);
|
|
|
|
// Empty strings should be serialized with quotes
|
|
expect($yaml)->toContain("HTTP_PROXY: ''");
|
|
expect($yaml)->toContain("HTTPS_PROXY: ''");
|
|
expect($yaml)->toContain('NO_PROXY: localhost');
|
|
|
|
// Should NOT contain "null"
|
|
expect($yaml)->not->toContain('HTTP_PROXY: null');
|
|
});
|
|
|
|
it('verifies YAML serialization handles null values', function () {
|
|
// Test that null values serialize as null
|
|
$data = [
|
|
'environment' => [
|
|
'HTTP_PROXY' => null,
|
|
'HTTPS_PROXY' => null,
|
|
'NO_PROXY' => 'localhost',
|
|
],
|
|
];
|
|
|
|
$yaml = Yaml::dump($data, 10, 2);
|
|
|
|
// Null should be serialized as "null"
|
|
expect($yaml)->toContain('HTTP_PROXY: null');
|
|
expect($yaml)->toContain('HTTPS_PROXY: null');
|
|
expect($yaml)->toContain('NO_PROXY: localhost');
|
|
|
|
// Should NOT contain empty quotes for null values
|
|
expect($yaml)->not->toContain("HTTP_PROXY: ''");
|
|
});
|
|
|
|
it('verifies empty string round-trip through YAML', function () {
|
|
// Test full round-trip: empty string -> YAML -> parse -> serialize -> parse
|
|
$original = [
|
|
'environment' => [
|
|
'HTTP_PROXY' => '',
|
|
'NO_PROXY' => 'localhost',
|
|
],
|
|
];
|
|
|
|
// Serialize to YAML
|
|
$yaml1 = Yaml::dump($original, 10, 2);
|
|
|
|
// Parse back
|
|
$parsed1 = Yaml::parse($yaml1);
|
|
|
|
// Verify empty string is preserved
|
|
expect($parsed1['environment']['HTTP_PROXY'])->toBe('');
|
|
expect($parsed1['environment']['NO_PROXY'])->toBe('localhost');
|
|
|
|
// Serialize again
|
|
$yaml2 = Yaml::dump($parsed1, 10, 2);
|
|
|
|
// Parse again
|
|
$parsed2 = Yaml::parse($yaml2);
|
|
|
|
// Should still be empty string, not null
|
|
expect($parsed2['environment']['HTTP_PROXY'])->toBe('');
|
|
expect($parsed2['environment']['NO_PROXY'])->toBe('localhost');
|
|
|
|
// Both YAML representations should be equivalent
|
|
expect($yaml1)->toBe($yaml2);
|
|
});
|
|
|
|
it('verifies str()->isEmpty() behavior with empty strings and null', function () {
|
|
// Test Laravel's str()->isEmpty() helper behavior
|
|
|
|
// Empty string should be considered empty
|
|
expect(str('')->isEmpty())->toBeTrue();
|
|
|
|
// Null should be considered empty
|
|
expect(str(null)->isEmpty())->toBeTrue();
|
|
|
|
// String with content should not be empty
|
|
expect(str('value')->isEmpty())->toBeFalse();
|
|
|
|
// This confirms that we need additional logic to distinguish
|
|
// between empty string ('') and null, since both are "isEmpty"
|
|
});
|
|
|
|
it('verifies the distinction between empty string and null in PHP', function () {
|
|
// Document PHP's behavior for empty strings vs null
|
|
|
|
$emptyString = '';
|
|
$nullValue = null;
|
|
|
|
// They are different values
|
|
expect($emptyString === $nullValue)->toBeFalse();
|
|
|
|
// Empty string is not null
|
|
expect($emptyString === '')->toBeTrue();
|
|
expect($nullValue === null)->toBeTrue();
|
|
|
|
// isset() treats them differently
|
|
$arrayWithEmpty = ['key' => ''];
|
|
$arrayWithNull = ['key' => null];
|
|
|
|
expect(isset($arrayWithEmpty['key']))->toBeTrue();
|
|
expect(isset($arrayWithNull['key']))->toBeFalse();
|
|
});
|
|
|
|
it('verifies YAML null syntax options all produce PHP null', function () {
|
|
// Test all three ways to write null in YAML
|
|
$yamlWithNullSyntax = <<<'YAML'
|
|
environment:
|
|
VAR_NO_VALUE:
|
|
VAR_EXPLICIT_NULL: null
|
|
VAR_TILDE: ~
|
|
VAR_EMPTY_STRING: ""
|
|
YAML;
|
|
|
|
$parsed = Yaml::parse($yamlWithNullSyntax);
|
|
|
|
// All three null syntaxes should produce PHP null
|
|
expect($parsed['environment']['VAR_NO_VALUE'])->toBeNull();
|
|
expect($parsed['environment']['VAR_EXPLICIT_NULL'])->toBeNull();
|
|
expect($parsed['environment']['VAR_TILDE'])->toBeNull();
|
|
|
|
// Empty string should remain empty string
|
|
expect($parsed['environment']['VAR_EMPTY_STRING'])->toBe('');
|
|
});
|
|
|
|
it('verifies null round-trip through YAML', function () {
|
|
// Test full round-trip: null -> YAML -> parse -> serialize -> parse
|
|
$original = [
|
|
'environment' => [
|
|
'NULL_VAR' => null,
|
|
'EMPTY_VAR' => '',
|
|
'VALUE_VAR' => 'localhost',
|
|
],
|
|
];
|
|
|
|
// Serialize to YAML
|
|
$yaml1 = Yaml::dump($original, 10, 2);
|
|
|
|
// Parse back
|
|
$parsed1 = Yaml::parse($yaml1);
|
|
|
|
// Verify types are preserved
|
|
expect($parsed1['environment']['NULL_VAR'])->toBeNull();
|
|
expect($parsed1['environment']['EMPTY_VAR'])->toBe('');
|
|
expect($parsed1['environment']['VALUE_VAR'])->toBe('localhost');
|
|
|
|
// Serialize again
|
|
$yaml2 = Yaml::dump($parsed1, 10, 2);
|
|
|
|
// Parse again
|
|
$parsed2 = Yaml::parse($yaml2);
|
|
|
|
// Should still have correct types
|
|
expect($parsed2['environment']['NULL_VAR'])->toBeNull();
|
|
expect($parsed2['environment']['EMPTY_VAR'])->toBe('');
|
|
expect($parsed2['environment']['VALUE_VAR'])->toBe('localhost');
|
|
|
|
// Both YAML representations should be equivalent
|
|
expect($yaml1)->toBe($yaml2);
|
|
});
|
|
|
|
it('verifies null vs empty string behavior difference', function () {
|
|
// Document the critical difference between null and empty string
|
|
|
|
// Null in YAML
|
|
$yamlNull = "VAR: null\n";
|
|
$parsedNull = Yaml::parse($yamlNull);
|
|
expect($parsedNull['VAR'])->toBeNull();
|
|
|
|
// Empty string in YAML
|
|
$yamlEmpty = "VAR: \"\"\n";
|
|
$parsedEmpty = Yaml::parse($yamlEmpty);
|
|
expect($parsedEmpty['VAR'])->toBe('');
|
|
|
|
// They should NOT be equal
|
|
expect($parsedNull['VAR'] === $parsedEmpty['VAR'])->toBeFalse();
|
|
|
|
// Verify type differences
|
|
expect(is_null($parsedNull['VAR']))->toBeTrue();
|
|
expect(is_string($parsedEmpty['VAR']))->toBeTrue();
|
|
});
|
|
|
|
it('verifies parser logic distinguishes null from empty string', function () {
|
|
// Test the exact === comparison behavior
|
|
$nullValue = null;
|
|
$emptyString = '';
|
|
|
|
// PHP strict comparison
|
|
expect($nullValue === null)->toBeTrue();
|
|
expect($emptyString === '')->toBeTrue();
|
|
expect($nullValue === $emptyString)->toBeFalse();
|
|
|
|
// This is what the parser should use for correct behavior
|
|
if ($nullValue === null) {
|
|
$nullHandled = true;
|
|
} else {
|
|
$nullHandled = false;
|
|
}
|
|
|
|
if ($emptyString === '') {
|
|
$emptyHandled = true;
|
|
} else {
|
|
$emptyHandled = false;
|
|
}
|
|
|
|
expect($nullHandled)->toBeTrue();
|
|
expect($emptyHandled)->toBeTrue();
|
|
});
|