This commit introduces advanced environment variable handling capabilities including: - Nested environment variable resolution with circular dependency detection - Extraction of hardcoded environment variables from docker-compose.yml - New ShowHardcoded Livewire component for displaying detected variables - Enhanced UI for better environment variable management The changes improve the user experience by automatically detecting and displaying environment variables that are hardcoded in docker-compose files, allowing users to override them if needed. The nested variable resolution ensures complex variable dependencies are properly handled. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
207 lines
7 KiB
PHP
207 lines
7 KiB
PHP
<?php
|
|
|
|
use function PHPUnit\Framework\assertNotNull;
|
|
use function PHPUnit\Framework\assertNull;
|
|
|
|
test('extractBalancedBraceContent extracts content from simple variable', function () {
|
|
$result = extractBalancedBraceContent('${VAR}', 0);
|
|
|
|
assertNotNull($result);
|
|
expect($result['content'])->toBe('VAR')
|
|
->and($result['start'])->toBe(1)
|
|
->and($result['end'])->toBe(5);
|
|
});
|
|
|
|
test('extractBalancedBraceContent handles nested braces', function () {
|
|
$result = extractBalancedBraceContent('${API_URL:-${SERVICE_URL_YOLO}/api}', 0);
|
|
|
|
assertNotNull($result);
|
|
expect($result['content'])->toBe('API_URL:-${SERVICE_URL_YOLO}/api')
|
|
->and($result['start'])->toBe(1)
|
|
->and($result['end'])->toBe(34); // Position of closing }
|
|
});
|
|
|
|
test('extractBalancedBraceContent handles triple nesting', function () {
|
|
$result = extractBalancedBraceContent('${A:-${B:-${C}}}', 0);
|
|
|
|
assertNotNull($result);
|
|
expect($result['content'])->toBe('A:-${B:-${C}}');
|
|
});
|
|
|
|
test('extractBalancedBraceContent returns null for unbalanced braces', function () {
|
|
$result = extractBalancedBraceContent('${VAR', 0);
|
|
|
|
assertNull($result);
|
|
});
|
|
|
|
test('extractBalancedBraceContent returns null when no braces', function () {
|
|
$result = extractBalancedBraceContent('VAR', 0);
|
|
|
|
assertNull($result);
|
|
});
|
|
|
|
test('extractBalancedBraceContent handles startPos parameter', function () {
|
|
$result = extractBalancedBraceContent('foo ${VAR} bar', 4);
|
|
|
|
assertNotNull($result);
|
|
expect($result['content'])->toBe('VAR')
|
|
->and($result['start'])->toBe(5)
|
|
->and($result['end'])->toBe(9);
|
|
});
|
|
|
|
test('splitOnOperatorOutsideNested splits on :- operator', function () {
|
|
$split = splitOnOperatorOutsideNested('API_URL:-default_value');
|
|
|
|
assertNotNull($split);
|
|
expect($split['variable'])->toBe('API_URL')
|
|
->and($split['operator'])->toBe(':-')
|
|
->and($split['default'])->toBe('default_value');
|
|
});
|
|
|
|
test('splitOnOperatorOutsideNested handles nested defaults', function () {
|
|
$split = splitOnOperatorOutsideNested('API_URL:-${SERVICE_URL_YOLO}/api');
|
|
|
|
assertNotNull($split);
|
|
expect($split['variable'])->toBe('API_URL')
|
|
->and($split['operator'])->toBe(':-')
|
|
->and($split['default'])->toBe('${SERVICE_URL_YOLO}/api');
|
|
});
|
|
|
|
test('splitOnOperatorOutsideNested handles dash operator', function () {
|
|
$split = splitOnOperatorOutsideNested('VAR-default');
|
|
|
|
assertNotNull($split);
|
|
expect($split['variable'])->toBe('VAR')
|
|
->and($split['operator'])->toBe('-')
|
|
->and($split['default'])->toBe('default');
|
|
});
|
|
|
|
test('splitOnOperatorOutsideNested handles colon question operator', function () {
|
|
$split = splitOnOperatorOutsideNested('VAR:?error message');
|
|
|
|
assertNotNull($split);
|
|
expect($split['variable'])->toBe('VAR')
|
|
->and($split['operator'])->toBe(':?')
|
|
->and($split['default'])->toBe('error message');
|
|
});
|
|
|
|
test('splitOnOperatorOutsideNested handles question operator', function () {
|
|
$split = splitOnOperatorOutsideNested('VAR?error');
|
|
|
|
assertNotNull($split);
|
|
expect($split['variable'])->toBe('VAR')
|
|
->and($split['operator'])->toBe('?')
|
|
->and($split['default'])->toBe('error');
|
|
});
|
|
|
|
test('splitOnOperatorOutsideNested returns null for simple variable', function () {
|
|
$split = splitOnOperatorOutsideNested('SIMPLE_VAR');
|
|
|
|
assertNull($split);
|
|
});
|
|
|
|
test('splitOnOperatorOutsideNested ignores operators inside nested braces', function () {
|
|
$split = splitOnOperatorOutsideNested('A:-${B:-default}');
|
|
|
|
assertNotNull($split);
|
|
// Should split on first :- (outside nested braces), not the one inside ${B:-default}
|
|
expect($split['variable'])->toBe('A')
|
|
->and($split['operator'])->toBe(':-')
|
|
->and($split['default'])->toBe('${B:-default}');
|
|
});
|
|
|
|
test('replaceVariables handles simple variable', function () {
|
|
$result = replaceVariables('${VAR}');
|
|
|
|
expect($result->value())->toBe('VAR');
|
|
});
|
|
|
|
test('replaceVariables handles nested expressions', function () {
|
|
$result = replaceVariables('${API_URL:-${SERVICE_URL_YOLO}/api}');
|
|
|
|
expect($result->value())->toBe('API_URL:-${SERVICE_URL_YOLO}/api');
|
|
});
|
|
|
|
test('replaceVariables handles variable with default', function () {
|
|
$result = replaceVariables('${API_URL:-http://localhost}');
|
|
|
|
expect($result->value())->toBe('API_URL:-http://localhost');
|
|
});
|
|
|
|
test('replaceVariables returns unchanged for non-variable string', function () {
|
|
$result = replaceVariables('not_a_variable');
|
|
|
|
expect($result->value())->toBe('not_a_variable');
|
|
});
|
|
|
|
test('replaceVariables handles triple nesting', function () {
|
|
$result = replaceVariables('${A:-${B:-${C}}}');
|
|
|
|
expect($result->value())->toBe('A:-${B:-${C}}');
|
|
});
|
|
|
|
test('replaceVariables fallback works for malformed input', function () {
|
|
// When braces are unbalanced, it falls back to old behavior
|
|
$result = replaceVariables('${VAR');
|
|
|
|
// Old behavior would extract everything before first }
|
|
// But since there's no }, it will extract 'VAR' (removing ${)
|
|
expect($result->value())->toContain('VAR');
|
|
});
|
|
|
|
test('extractBalancedBraceContent handles complex nested expression', function () {
|
|
$result = extractBalancedBraceContent('${API:-${SERVICE_URL}/api/v${VERSION:-1}}', 0);
|
|
|
|
assertNotNull($result);
|
|
expect($result['content'])->toBe('API:-${SERVICE_URL}/api/v${VERSION:-1}');
|
|
});
|
|
|
|
test('splitOnOperatorOutsideNested handles complex nested expression', function () {
|
|
$split = splitOnOperatorOutsideNested('API:-${SERVICE_URL}/api/v${VERSION:-1}');
|
|
|
|
assertNotNull($split);
|
|
expect($split['variable'])->toBe('API')
|
|
->and($split['operator'])->toBe(':-')
|
|
->and($split['default'])->toBe('${SERVICE_URL}/api/v${VERSION:-1}');
|
|
});
|
|
|
|
test('extractBalancedBraceContent finds second variable in string', function () {
|
|
$str = '${VAR1} and ${VAR2}';
|
|
|
|
// First variable
|
|
$result1 = extractBalancedBraceContent($str, 0);
|
|
assertNotNull($result1);
|
|
expect($result1['content'])->toBe('VAR1');
|
|
|
|
// Second variable
|
|
$result2 = extractBalancedBraceContent($str, $result1['end'] + 1);
|
|
assertNotNull($result2);
|
|
expect($result2['content'])->toBe('VAR2');
|
|
});
|
|
|
|
test('replaceVariables handles empty default value', function () {
|
|
$result = replaceVariables('${VAR:-}');
|
|
|
|
expect($result->value())->toBe('VAR:-');
|
|
});
|
|
|
|
test('splitOnOperatorOutsideNested handles empty default value', function () {
|
|
$split = splitOnOperatorOutsideNested('VAR:-');
|
|
|
|
assertNotNull($split);
|
|
expect($split['variable'])->toBe('VAR')
|
|
->and($split['operator'])->toBe(':-')
|
|
->and($split['default'])->toBe('');
|
|
});
|
|
|
|
test('replaceVariables handles brace format without dollar sign', function () {
|
|
// This format is used by the regex capture group in magic variable detection
|
|
$result = replaceVariables('{SERVICE_URL_YOLO}');
|
|
expect($result->value())->toBe('SERVICE_URL_YOLO');
|
|
});
|
|
|
|
test('replaceVariables handles truncated brace format', function () {
|
|
// When regex captures {VAR from a larger expression, no closing brace
|
|
$result = replaceVariables('{API_URL');
|
|
expect($result->value())->toBe('API_URL');
|
|
});
|