parser = new EnvFileParser(); }); describe('parseString()', function () { it('parses basic key=value pairs', function () { $content = <<parser->parseString($content); expect($result)->toBe([ 'KEY1' => 'value1', 'KEY2' => 'value2', 'KEY3' => 'value3', ]); }); it('handles empty lines', function () { $content = <<parser->parseString($content); expect($result)->toBe([ 'KEY1' => 'value1', 'KEY2' => 'value2', 'KEY3' => 'value3', ]); }); it('skips comment lines', function () { $content = <<parser->parseString($content); expect($result)->toBe([ 'KEY1' => 'value1', 'KEY2' => 'value2', ]); }); it('removes double quotes from values', function () { $content = <<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 = <<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 = <<parser->parseString($content); expect($result['BOOL1'])->toBeTrue(); expect($result['BOOL2'])->toBeTrue(); expect($result['BOOL3'])->toBeTrue(); }); it('casts "false" to boolean false', function () { $content = <<parser->parseString($content); expect($result['BOOL1'])->toBeFalse(); expect($result['BOOL2'])->toBeFalse(); expect($result['BOOL3'])->toBeFalse(); }); it('casts "null" to null', function () { $content = <<parser->parseString($content); expect($result['NULL1'])->toBeNull(); expect($result['NULL2'])->toBeNull(); expect($result['NULL3'])->toBeNull(); }); it('casts numeric strings to integers', function () { $content = <<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 = <<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 = <<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 = <<parser->parseString($content); expect($result)->toBe([ 'KEY1' => 'value1', 'KEY2' => 'value2', 'KEY3' => 'value3', ]); }); it('handles empty values', function () { $content = <<parser->parseString($content); expect($result['EMPTY1'])->toBe(''); expect($result['EMPTY2'])->toBe(''); expect($result['EMPTY3'])->toBe(''); }); it('handles values with equals signs', function () { $content = <<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 = <<parser->parseString($content); expect($result)->toBe([ 'KEY1' => 'value1', 'KEY2' => 'value2', ]); }); it('handles mixed content with all features', function () { $content = <<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 = <<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 = <<parser->parseString($content); expect($result)->toBe([ 'VAR_1' => 'value1', 'VAR_2_TEST' => 'value2', 'VAR123' => 'value3', ]); }); it('handles special characters in values', function () { $content = <<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 = <<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 = <<parser->parseString($content); expect($result['KEY'])->toBe('value3'); }); }); });