497 lines
13 KiB
PHP
497 lines
13 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\Config\EnvFileParser;
|
|
use App\Framework\Filesystem\ValueObjects\FilePath;
|
|
|
|
describe('EnvFileParser', function () {
|
|
beforeEach(function () {
|
|
$this->parser = new EnvFileParser();
|
|
});
|
|
|
|
describe('parseString()', function () {
|
|
it('parses basic key=value pairs', function () {
|
|
$content = <<<ENV
|
|
KEY1=value1
|
|
KEY2=value2
|
|
KEY3=value3
|
|
ENV;
|
|
|
|
$result = $this->parser->parseString($content);
|
|
|
|
expect($result)->toBe([
|
|
'KEY1' => 'value1',
|
|
'KEY2' => 'value2',
|
|
'KEY3' => 'value3',
|
|
]);
|
|
});
|
|
|
|
it('handles empty lines', function () {
|
|
$content = <<<ENV
|
|
KEY1=value1
|
|
|
|
KEY2=value2
|
|
|
|
|
|
KEY3=value3
|
|
ENV;
|
|
|
|
$result = $this->parser->parseString($content);
|
|
|
|
expect($result)->toBe([
|
|
'KEY1' => 'value1',
|
|
'KEY2' => 'value2',
|
|
'KEY3' => 'value3',
|
|
]);
|
|
});
|
|
|
|
it('skips comment lines', function () {
|
|
$content = <<<ENV
|
|
# This is a comment
|
|
KEY1=value1
|
|
# Another comment
|
|
KEY2=value2
|
|
ENV;
|
|
|
|
$result = $this->parser->parseString($content);
|
|
|
|
expect($result)->toBe([
|
|
'KEY1' => 'value1',
|
|
'KEY2' => 'value2',
|
|
]);
|
|
});
|
|
|
|
it('removes double quotes from values', function () {
|
|
$content = <<<ENV
|
|
KEY1="quoted value"
|
|
KEY2="value with spaces"
|
|
KEY3="123"
|
|
ENV;
|
|
|
|
$result = $this->parser->parseString($content);
|
|
|
|
expect($result)->toBe([
|
|
'KEY1' => 'quoted value',
|
|
'KEY2' => 'value with spaces',
|
|
'KEY3' => 123, // Numeric string gets cast to int
|
|
]);
|
|
});
|
|
|
|
it('removes single quotes from values', function () {
|
|
$content = <<<ENV
|
|
KEY1='single quoted'
|
|
KEY2='value with spaces'
|
|
KEY3='true'
|
|
ENV;
|
|
|
|
$result = $this->parser->parseString($content);
|
|
|
|
expect($result)->toBe([
|
|
'KEY1' => 'single quoted',
|
|
'KEY2' => 'value with spaces',
|
|
'KEY3' => true, // 'true' gets cast to boolean
|
|
]);
|
|
});
|
|
|
|
it('casts "true" to boolean true', function () {
|
|
$content = <<<ENV
|
|
BOOL1=true
|
|
BOOL2=True
|
|
BOOL3=TRUE
|
|
ENV;
|
|
|
|
$result = $this->parser->parseString($content);
|
|
|
|
expect($result['BOOL1'])->toBeTrue();
|
|
expect($result['BOOL2'])->toBeTrue();
|
|
expect($result['BOOL3'])->toBeTrue();
|
|
});
|
|
|
|
it('casts "false" to boolean false', function () {
|
|
$content = <<<ENV
|
|
BOOL1=false
|
|
BOOL2=False
|
|
BOOL3=FALSE
|
|
ENV;
|
|
|
|
$result = $this->parser->parseString($content);
|
|
|
|
expect($result['BOOL1'])->toBeFalse();
|
|
expect($result['BOOL2'])->toBeFalse();
|
|
expect($result['BOOL3'])->toBeFalse();
|
|
});
|
|
|
|
it('casts "null" to null', function () {
|
|
$content = <<<ENV
|
|
NULL1=null
|
|
NULL2=Null
|
|
NULL3=NULL
|
|
ENV;
|
|
|
|
$result = $this->parser->parseString($content);
|
|
|
|
expect($result['NULL1'])->toBeNull();
|
|
expect($result['NULL2'])->toBeNull();
|
|
expect($result['NULL3'])->toBeNull();
|
|
});
|
|
|
|
it('casts numeric strings to integers', function () {
|
|
$content = <<<ENV
|
|
INT1=0
|
|
INT2=123
|
|
INT3=456
|
|
INT4=-789
|
|
ENV;
|
|
|
|
$result = $this->parser->parseString($content);
|
|
|
|
expect($result['INT1'])->toBe(0);
|
|
expect($result['INT2'])->toBe(123);
|
|
expect($result['INT3'])->toBe(456);
|
|
expect($result['INT4'])->toBe(-789);
|
|
});
|
|
|
|
it('casts numeric strings with decimals to floats', function () {
|
|
$content = <<<ENV
|
|
FLOAT1=0.0
|
|
FLOAT2=123.45
|
|
FLOAT3=-67.89
|
|
ENV;
|
|
|
|
$result = $this->parser->parseString($content);
|
|
|
|
expect($result['FLOAT1'])->toBe(0.0);
|
|
expect($result['FLOAT2'])->toBe(123.45);
|
|
expect($result['FLOAT3'])->toBe(-67.89);
|
|
});
|
|
|
|
it('keeps non-numeric strings as strings', function () {
|
|
$content = <<<ENV
|
|
STR1=hello
|
|
STR2=world123
|
|
STR3=abc-def
|
|
ENV;
|
|
|
|
$result = $this->parser->parseString($content);
|
|
|
|
expect($result['STR1'])->toBe('hello');
|
|
expect($result['STR2'])->toBe('world123');
|
|
expect($result['STR3'])->toBe('abc-def');
|
|
});
|
|
|
|
it('trims whitespace around keys and values', function () {
|
|
$content = <<<ENV
|
|
KEY1 = value1
|
|
KEY2= value2
|
|
KEY3=value3
|
|
ENV;
|
|
|
|
$result = $this->parser->parseString($content);
|
|
|
|
expect($result)->toBe([
|
|
'KEY1' => 'value1',
|
|
'KEY2' => 'value2',
|
|
'KEY3' => 'value3',
|
|
]);
|
|
});
|
|
|
|
it('handles empty values', function () {
|
|
$content = <<<ENV
|
|
EMPTY1=
|
|
EMPTY2=""
|
|
EMPTY3=''
|
|
ENV;
|
|
|
|
$result = $this->parser->parseString($content);
|
|
|
|
expect($result['EMPTY1'])->toBe('');
|
|
expect($result['EMPTY2'])->toBe('');
|
|
expect($result['EMPTY3'])->toBe('');
|
|
});
|
|
|
|
it('handles values with equals signs', function () {
|
|
$content = <<<ENV
|
|
URL=https://example.com?param=value
|
|
EQUATION=a=b+c
|
|
ENV;
|
|
|
|
$result = $this->parser->parseString($content);
|
|
|
|
expect($result['URL'])->toBe('https://example.com?param=value');
|
|
expect($result['EQUATION'])->toBe('a=b+c');
|
|
});
|
|
|
|
it('skips lines without equals sign', function () {
|
|
$content = <<<ENV
|
|
KEY1=value1
|
|
INVALID_LINE_WITHOUT_EQUALS
|
|
KEY2=value2
|
|
ENV;
|
|
|
|
$result = $this->parser->parseString($content);
|
|
|
|
expect($result)->toBe([
|
|
'KEY1' => 'value1',
|
|
'KEY2' => 'value2',
|
|
]);
|
|
});
|
|
|
|
it('handles mixed content with all features', function () {
|
|
$content = <<<ENV
|
|
# Database configuration
|
|
DB_HOST=localhost
|
|
DB_PORT=3306
|
|
DB_NAME="my_database"
|
|
DB_USER='root'
|
|
DB_PASS=
|
|
|
|
# Application settings
|
|
APP_ENV=production
|
|
APP_DEBUG=false
|
|
APP_URL=https://example.com
|
|
|
|
# Feature flags
|
|
FEATURE_ENABLED=true
|
|
CACHE_TTL=3600
|
|
API_TIMEOUT=30.5
|
|
ENV;
|
|
|
|
$result = $this->parser->parseString($content);
|
|
|
|
expect($result)->toBe([
|
|
'DB_HOST' => 'localhost',
|
|
'DB_PORT' => 3306,
|
|
'DB_NAME' => 'my_database',
|
|
'DB_USER' => 'root',
|
|
'DB_PASS' => '',
|
|
'APP_ENV' => 'production',
|
|
'APP_DEBUG' => false,
|
|
'APP_URL' => 'https://example.com',
|
|
'FEATURE_ENABLED' => true,
|
|
'CACHE_TTL' => 3600,
|
|
'API_TIMEOUT' => 30.5,
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('removeQuotes()', function () {
|
|
it('removes double quotes', function () {
|
|
$result = $this->parser->removeQuotes('"quoted value"');
|
|
expect($result)->toBe('quoted value');
|
|
});
|
|
|
|
it('removes single quotes', function () {
|
|
$result = $this->parser->removeQuotes("'quoted value'");
|
|
expect($result)->toBe('quoted value');
|
|
});
|
|
|
|
it('keeps unquoted values unchanged', function () {
|
|
$result = $this->parser->removeQuotes('unquoted');
|
|
expect($result)->toBe('unquoted');
|
|
});
|
|
|
|
it('keeps mismatched quotes unchanged', function () {
|
|
$result1 = $this->parser->removeQuotes('"mismatched\'');
|
|
$result2 = $this->parser->removeQuotes('\'mismatched"');
|
|
|
|
expect($result1)->toBe('"mismatched\'');
|
|
expect($result2)->toBe('\'mismatched"');
|
|
});
|
|
|
|
it('keeps empty quoted strings as empty', function () {
|
|
$result1 = $this->parser->removeQuotes('""');
|
|
$result2 = $this->parser->removeQuotes("''");
|
|
|
|
expect($result1)->toBe('');
|
|
expect($result2)->toBe('');
|
|
});
|
|
|
|
it('keeps single quote character unchanged', function () {
|
|
$result = $this->parser->removeQuotes('"');
|
|
expect($result)->toBe('"');
|
|
});
|
|
});
|
|
|
|
describe('parse() with FilePath', function () {
|
|
beforeEach(function () {
|
|
// Create temporary test directory
|
|
$this->testDir = '/home/michael/dev/michaelschiemer/tests/tmp';
|
|
if (!is_dir($this->testDir)) {
|
|
mkdir($this->testDir, 0777, true);
|
|
}
|
|
});
|
|
|
|
afterEach(function () {
|
|
// Clean up test files
|
|
if (isset($this->testFile) && file_exists($this->testFile)) {
|
|
unlink($this->testFile);
|
|
}
|
|
});
|
|
|
|
it('parses file with FilePath object', function () {
|
|
$this->testFile = $this->testDir . '/test.env';
|
|
file_put_contents($this->testFile, "KEY1=value1\nKEY2=value2");
|
|
|
|
$filePath = FilePath::create($this->testFile);
|
|
$result = $this->parser->parse($filePath);
|
|
|
|
expect($result)->toBe([
|
|
'KEY1' => 'value1',
|
|
'KEY2' => 'value2',
|
|
]);
|
|
});
|
|
|
|
it('parses file with string path', function () {
|
|
$this->testFile = $this->testDir . '/test.env';
|
|
file_put_contents($this->testFile, "KEY1=value1\nKEY2=value2");
|
|
|
|
$result = $this->parser->parse($this->testFile);
|
|
|
|
expect($result)->toBe([
|
|
'KEY1' => 'value1',
|
|
'KEY2' => 'value2',
|
|
]);
|
|
});
|
|
|
|
it('returns empty array for non-existent file', function () {
|
|
$result = $this->parser->parse($this->testDir . '/non-existent.env');
|
|
|
|
expect($result)->toBe([]);
|
|
});
|
|
|
|
it('returns empty array for unreadable file', function () {
|
|
$this->testFile = $this->testDir . '/unreadable.env';
|
|
file_put_contents($this->testFile, "KEY=value");
|
|
chmod($this->testFile, 0000); // Make unreadable
|
|
|
|
$result = $this->parser->parse($this->testFile);
|
|
|
|
expect($result)->toBe([]);
|
|
|
|
// Restore permissions for cleanup
|
|
chmod($this->testFile, 0644);
|
|
});
|
|
|
|
it('parses real .env file format', function () {
|
|
$this->testFile = $this->testDir . '/.env.test';
|
|
$content = <<<ENV
|
|
# Environment Configuration
|
|
APP_NAME="My Application"
|
|
APP_ENV=production
|
|
APP_DEBUG=false
|
|
|
|
# Database
|
|
DB_CONNECTION=mysql
|
|
DB_HOST=127.0.0.1
|
|
DB_PORT=3306
|
|
DB_DATABASE=homestead
|
|
DB_USERNAME=homestead
|
|
DB_PASSWORD=secret
|
|
|
|
# Redis
|
|
REDIS_HOST=127.0.0.1
|
|
REDIS_PASSWORD=null
|
|
REDIS_PORT=6379
|
|
ENV;
|
|
file_put_contents($this->testFile, $content);
|
|
|
|
$result = $this->parser->parse($this->testFile);
|
|
|
|
expect($result)->toBe([
|
|
'APP_NAME' => 'My Application',
|
|
'APP_ENV' => 'production',
|
|
'APP_DEBUG' => false,
|
|
'DB_CONNECTION' => 'mysql',
|
|
'DB_HOST' => '127.0.0.1',
|
|
'DB_PORT' => 3306,
|
|
'DB_DATABASE' => 'homestead',
|
|
'DB_USERNAME' => 'homestead',
|
|
'DB_PASSWORD' => 'secret',
|
|
'REDIS_HOST' => '127.0.0.1',
|
|
'REDIS_PASSWORD' => null,
|
|
'REDIS_PORT' => 6379,
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('edge cases', function () {
|
|
it('handles Windows line endings (CRLF)', function () {
|
|
$content = "KEY1=value1\r\nKEY2=value2\r\n";
|
|
$result = $this->parser->parseString($content);
|
|
|
|
expect($result)->toBe([
|
|
'KEY1' => 'value1',
|
|
'KEY2' => 'value2',
|
|
]);
|
|
});
|
|
|
|
it('handles mixed line endings', function () {
|
|
$content = "KEY1=value1\nKEY2=value2\r\nKEY3=value3\r";
|
|
$result = $this->parser->parseString($content);
|
|
|
|
expect($result)->toBe([
|
|
'KEY1' => 'value1',
|
|
'KEY2' => 'value2',
|
|
'KEY3' => 'value3',
|
|
]);
|
|
});
|
|
|
|
it('handles keys with underscores and numbers', function () {
|
|
$content = <<<ENV
|
|
VAR_1=value1
|
|
VAR_2_TEST=value2
|
|
VAR123=value3
|
|
ENV;
|
|
|
|
$result = $this->parser->parseString($content);
|
|
|
|
expect($result)->toBe([
|
|
'VAR_1' => 'value1',
|
|
'VAR_2_TEST' => 'value2',
|
|
'VAR123' => 'value3',
|
|
]);
|
|
});
|
|
|
|
it('handles special characters in values', function () {
|
|
$content = <<<ENV
|
|
SPECIAL1=value!@#$%
|
|
SPECIAL2=value^&*()
|
|
SPECIAL3=value[]{}
|
|
ENV;
|
|
|
|
$result = $this->parser->parseString($content);
|
|
|
|
expect($result['SPECIAL1'])->toBe('value!@#$%');
|
|
expect($result['SPECIAL2'])->toBe('value^&*()');
|
|
expect($result['SPECIAL3'])->toBe('value[]{}');
|
|
});
|
|
|
|
it('handles URL values correctly', function () {
|
|
$content = <<<ENV
|
|
URL1=http://localhost:8080
|
|
URL2=https://example.com/path?param=value&other=123
|
|
URL3=mysql://user:pass@localhost:3306/database
|
|
ENV;
|
|
|
|
$result = $this->parser->parseString($content);
|
|
|
|
expect($result['URL1'])->toBe('http://localhost:8080');
|
|
expect($result['URL2'])->toBe('https://example.com/path?param=value&other=123');
|
|
expect($result['URL3'])->toBe('mysql://user:pass@localhost:3306/database');
|
|
});
|
|
|
|
it('overrides duplicate keys with last value', function () {
|
|
$content = <<<ENV
|
|
KEY=value1
|
|
KEY=value2
|
|
KEY=value3
|
|
ENV;
|
|
|
|
$result = $this->parser->parseString($content);
|
|
|
|
expect($result['KEY'])->toBe('value3');
|
|
});
|
|
});
|
|
});
|