tempFile = sys_get_temp_dir() . '/cfgloader_test_' . uniqid() . '.json'; } protected function tearDown(): void { if (file_exists($this->tempFile)) { unlink($this->tempFile); } } private function writeJson(array $data): void { file_put_contents($this->tempFile, json_encode($data)); } /** * Minimal valid configuration that passes all validation checks. */ private function validConfig(): array { return [ 'metadata' => [ 'extractionRules' => [ ['name' => 'account_iban', 'lineNumber' => 1, 'regex' => '(.+)'], ], ], 'csvStructure' => [ 'headerLine' => 1, 'inputDelimiter' => ';', 'encoding' => 'UTF-8', ], 'columnTransformations' => [ ['sourceColumn' => 'A', 'outputColumn' => 'B', 'type' => 'map'], ], ]; } // ------------------------------------------------------------------------- // Happy path // ------------------------------------------------------------------------- public function testLoadValidConfigReturnsArray(): void { $this->writeJson($this->validConfig()); $loader = new ConfigurationLoader($this->tempFile); $config = $loader->load(); $this->assertIsArray($config); $this->assertArrayHasKey('metadata', $config); $this->assertArrayHasKey('csvStructure', $config); $this->assertArrayHasKey('columnTransformations', $config); } public function testGetAllReturnsFullConfig(): void { $this->writeJson($this->validConfig()); $loader = new ConfigurationLoader($this->tempFile); $loader->load(); $all = $loader->getAll(); $this->assertSame(1, $all['csvStructure']['headerLine']); } // ------------------------------------------------------------------------- // get() dot-notation accessor // ------------------------------------------------------------------------- public function testGetDotNotationReturnsValue(): void { $this->writeJson($this->validConfig()); $loader = new ConfigurationLoader($this->tempFile); $loader->load(); $this->assertSame(1, $loader->get('csvStructure.headerLine')); $this->assertSame(';', $loader->get('csvStructure.inputDelimiter')); } public function testGetNonExistentKeyReturnsNull(): void { $this->writeJson($this->validConfig()); $loader = new ConfigurationLoader($this->tempFile); $loader->load(); $this->assertNull($loader->get('nonexistent.key')); } public function testGetNonExistentKeyReturnsDefault(): void { $this->writeJson($this->validConfig()); $loader = new ConfigurationLoader($this->tempFile); $loader->load(); $this->assertSame('fallback', $loader->get('nonexistent', 'fallback')); } // ------------------------------------------------------------------------- // File not found / bad extension // ------------------------------------------------------------------------- public function testFileNotFoundThrows(): void { $this->expectException(\RuntimeException::class); $loader = new ConfigurationLoader('/nonexistent/path/config.json'); $loader->load(); } public function testNonJsonExtensionThrows(): void { $yamlPath = sys_get_temp_dir() . '/test_config_' . uniqid() . '.yaml'; file_put_contents($yamlPath, 'key: value'); try { $loader = new ConfigurationLoader($yamlPath); $this->expectException(\RuntimeException::class); $loader->load(); } finally { if (file_exists($yamlPath)) { unlink($yamlPath); } } } public function testInvalidJsonThrows(): void { file_put_contents($this->tempFile, '{invalid json content}'); $this->expectException(\RuntimeException::class); (new ConfigurationLoader($this->tempFile))->load(); } // ------------------------------------------------------------------------- // Required top-level sections // ------------------------------------------------------------------------- public function testMissingMetadataSectionThrows(): void { $config = $this->validConfig(); unset($config['metadata']); $this->writeJson($config); $this->expectException(\RuntimeException::class); (new ConfigurationLoader($this->tempFile))->load(); } public function testMissingExtractionRulesThrows(): void { $config = $this->validConfig(); unset($config['metadata']['extractionRules']); $this->writeJson($config); $this->expectException(\RuntimeException::class); (new ConfigurationLoader($this->tempFile))->load(); } public function testMissingCsvStructureSectionThrows(): void { $config = $this->validConfig(); unset($config['csvStructure']); $this->writeJson($config); $this->expectException(\RuntimeException::class); (new ConfigurationLoader($this->tempFile))->load(); } public function testMissingHeaderLineThrows(): void { $config = $this->validConfig(); unset($config['csvStructure']['headerLine']); $this->writeJson($config); $this->expectException(\RuntimeException::class); (new ConfigurationLoader($this->tempFile))->load(); } public function testMissingColumnTransformationsThrows(): void { $config = $this->validConfig(); unset($config['columnTransformations']); $this->writeJson($config); $this->expectException(\RuntimeException::class); (new ConfigurationLoader($this->tempFile))->load(); } // ------------------------------------------------------------------------- // csvStructure value validation // ------------------------------------------------------------------------- public function testInvalidEncodingThrows(): void { $config = $this->validConfig(); $config['csvStructure']['encoding'] = 'LATIN1'; $this->writeJson($config); $this->expectException(\Exception::class); (new ConfigurationLoader($this->tempFile))->load(); } public function testSupportedEncodingsPass(): void { foreach (['UTF-8', 'ISO-8859-1', 'CP1252'] as $encoding) { $config = $this->validConfig(); $config['csvStructure']['encoding'] = $encoding; $this->writeJson($config); $loader = new ConfigurationLoader($this->tempFile); $result = $loader->load(); $this->assertSame($encoding, $result['csvStructure']['encoding']); } } // ------------------------------------------------------------------------- // directories validation // ------------------------------------------------------------------------- public function testDirectoriesAllKeysPass(): void { $config = $this->validConfig(); $config['directories'] = [ 'source' => '/tmp/source', 'output' => '/tmp/output', 'archive' => '/tmp/archive', 'error' => '/tmp/error', ]; $this->writeJson($config); $result = (new ConfigurationLoader($this->tempFile))->load(); $this->assertArrayHasKey('directories', $result); } public function testDirectoriesMissingSubkeyThrows(): void { $config = $this->validConfig(); $config['directories'] = [ 'source' => '/tmp/source', 'output' => '/tmp/output', // 'archive' and 'error' intentionally missing ]; $this->writeJson($config); $this->expectException(\RuntimeException::class); (new ConfigurationLoader($this->tempFile))->load(); } // ------------------------------------------------------------------------- // Absent optional 'directories' key does not trigger validation // ------------------------------------------------------------------------- public function testAbsentDirectoriesKeyDoesNotThrow(): void { $config = $this->validConfig(); $this->assertArrayNotHasKey('directories', $config); $this->writeJson($config); $result = (new ConfigurationLoader($this->tempFile))->load(); $this->assertArrayNotHasKey('directories', $result); } }