|
12 | 12 | use BlitzPHP\Config\Config; |
13 | 13 | use BlitzPHP\Exceptions\ConfigException; |
14 | 14 | use BlitzPHP\Spec\ReflectionHelper; |
| 15 | +use Nette\Schema\Expect; |
15 | 16 | use Nette\Schema\Schema; |
16 | 17 |
|
17 | 18 | use function Kahlan\expect; |
18 | 19 |
|
19 | 20 | describe('Config / Config', function (): void { |
20 | | - beforeEach(function(): void { |
21 | | - $this->config = service('config'); |
| 21 | + beforeAll(function(): void { |
| 22 | + // Méthode utilitaire pour nettoyer les dossiers |
| 23 | + $this->recursiveDelete = function(string $dir): void { |
| 24 | + if (!is_dir($dir)) { |
| 25 | + return; |
| 26 | + } |
| 27 | + |
| 28 | + $files = array_diff(scandir($dir), ['.', '..']); |
| 29 | + foreach ($files as $file) { |
| 30 | + $path = $dir . '/' . $file; |
| 31 | + is_dir($path) ? $this->recursiveDelete($path) : unlink($path); |
| 32 | + } |
| 33 | + |
| 34 | + rmdir($dir); |
| 35 | + }; |
| 36 | + |
| 37 | + |
| 38 | + // Créer un dossier temporaire pour les tests |
| 39 | + $this->tempDir = sys_get_temp_dir() . '/blitz-config-test'; |
| 40 | + mkdir($this->tempDir, 0777, true); |
| 41 | + mkdir($this->tempDir . '/Config', 0777, true); |
| 42 | + mkdir($this->tempDir . '/Config/schemas', 0777, true); |
22 | 43 | }); |
23 | 44 |
|
| 45 | + beforeEach(function(): void { |
| 46 | + $this->config = service('config'); |
| 47 | + $this->configurator = ReflectionHelper::getPrivateProperty($this->config, 'configurator'); |
| 48 | + }); |
| 49 | + |
| 50 | + afterAll(function(): void { |
| 51 | + // Nettoyer le dossier temporaire |
| 52 | + if (is_dir($this->tempDir)) { |
| 53 | + $this->recursiveDelete($this->tempDir); |
| 54 | + } |
| 55 | + |
| 56 | + // Réinitialiser le configurator pour les tests |
| 57 | + $loaded = ReflectionHelper::setPrivateProperty(Config::class, 'loaded', []); |
| 58 | + $originals = ReflectionHelper::setPrivateProperty(Config::class, 'originals', []); |
| 59 | + $registrars = ReflectionHelper::setPrivateProperty(Config::class, 'registrars', []); |
| 60 | + $didDiscovery = ReflectionHelper::setPrivateProperty(Config::class, 'didDiscovery', false); |
| 61 | + }); |
| 62 | + |
24 | 63 | describe('Initialisation', function (): void { |
25 | 64 | it('La config est toujours initialisee', function (): void { |
26 | 65 | $initialized = ReflectionHelper::getPrivateProperty(Config::class, 'initialized'); |
|
92 | 131 |
|
93 | 132 | describe('Autres', function (): void { |
94 | 133 | it('path', function (): void { |
95 | | - expect(Config::path('app'))->toBe(config_path('app')); |
96 | | - expect(Config::path('appl'))->toBeEmpty(); |
| 134 | + expect(Config::file('app'))->toBe(config_path('app')); |
| 135 | + expect(Config::file('appl'))->toBeEmpty(); |
97 | 136 | }); |
98 | 137 |
|
99 | 138 | it('schema', function (): void { |
|
137 | 176 | expect($this->config->get('app.negotiate_locale'))->toBeFalsy(); |
138 | 177 | }); |
139 | 178 | }); |
| 179 | + |
| 180 | + describe('Chargement avancé', function (): void { |
| 181 | + it('load avec chemin de fichier personnalisé', function (): void { |
| 182 | + // Créer un fichier de config temporaire |
| 183 | + $tempFile = $this->tempDir . '/custom.php'; |
| 184 | + file_put_contents($tempFile, '<?php return ["test" => "value"];'); |
| 185 | + |
| 186 | + $this->config->load('custom', $tempFile); |
| 187 | + |
| 188 | + expect($this->config->get('custom.test'))->toBe('value'); |
| 189 | + }); |
| 190 | + |
| 191 | + it('load avec schéma personnalisé', function (): void { |
| 192 | + $schema = Expect::structure([ |
| 193 | + 'test' => Expect::string()->required() |
| 194 | + ]); |
| 195 | + |
| 196 | + $this->config->ghost('custom', $schema); |
| 197 | + $this->config->set('custom.test', 'value'); |
| 198 | + |
| 199 | + expect($this->config->get('custom.test'))->toBe('value'); |
| 200 | + }); |
| 201 | + |
| 202 | + it('load avec tableau vide autorisé', function (): void { |
| 203 | + $this->config->ghost('emptyconfig'); |
| 204 | + expect($this->config->exists('emptyconfig'))->toBeTruthy(); |
| 205 | + |
| 206 | + // Vérifier qu'on peut setter après |
| 207 | + $this->config->set('emptyconfig.somekey', 'value'); |
| 208 | + expect($this->config->get('emptyconfig.somekey'))->toBe('value'); |
| 209 | + }); |
| 210 | + |
| 211 | + it('load sans fichier existant', function (): void { |
| 212 | + $before = ReflectionHelper::getPrivateProperty(Config::class, 'loaded'); |
| 213 | + expect($before)->not->toContainKey('nonexistent'); |
| 214 | + |
| 215 | + // Ne devrait rien charger |
| 216 | + $this->config->load('nonexistent'); |
| 217 | + |
| 218 | + $after = ReflectionHelper::getPrivateProperty(Config::class, 'loaded'); |
| 219 | + expect($after)->not->toContainKey('nonexistent'); |
| 220 | + }); |
| 221 | + |
| 222 | + it('load avec fichier déjà inclus', function (): void { |
| 223 | + // Créer un fichier qui se charge lui-même |
| 224 | + $tempFile = $this->tempDir . '/selfincluded.php'; |
| 225 | + file_put_contents($tempFile, '<?php return ["self" => "loaded"];'); |
| 226 | + |
| 227 | + // Inclure une première fois |
| 228 | + require $tempFile; |
| 229 | + |
| 230 | + // Essayer de charger via Config (ne devrait pas charger à nouveau) |
| 231 | + $this->config->load('selfincluded', $tempFile); |
| 232 | + |
| 233 | + // Le fichier ne devrait pas être marqué comme chargé |
| 234 | + $loaded = ReflectionHelper::getPrivateProperty(Config::class, 'loaded'); |
| 235 | + expect($loaded)->toContainKey('selfincluded'); |
| 236 | + expect($this->config->get('selfincluded.self'))->toThrow(); |
| 237 | + }); |
| 238 | + |
| 239 | + it('loadMultiple avec différents formats', function (): void { |
| 240 | + $tempFile1 = $this->tempDir . '/test1.php'; |
| 241 | + $tempFile2 = $this->tempDir . '/test2.php'; |
| 242 | + |
| 243 | + file_put_contents($tempFile1, '<?php return ["key1" => "value1"];'); |
| 244 | + file_put_contents($tempFile2, '<?php return ["key2" => "value2"];'); |
| 245 | + |
| 246 | + // Format 1: ['config1', 'config2'] |
| 247 | + $this->config->load(['test1', 'test2']); |
| 248 | + |
| 249 | + // Format 2: ['config1' => 'path1.php', 'config2' => 'path2.php'] |
| 250 | + $this->config->load(['test3' => $tempFile1, 'test4' => $tempFile2]); |
| 251 | + |
| 252 | + expect($this->config->exists('test1'))->toBeFalsy(); // Pas de fichier |
| 253 | + expect($this->config->exists('test3.key1'))->toBeTruthy(); |
| 254 | + expect($this->config->get('test3.key1'))->toBe('value1'); |
| 255 | + }); |
| 256 | + }); |
| 257 | + |
| 258 | + describe('Auto-détection', function (): void { |
| 259 | + it('initializeURL avec base_url auto', function (): void { |
| 260 | + // Sauvegarder la valeur originale |
| 261 | + $original = $this->config->get('app.base_url'); |
| 262 | + |
| 263 | + // Tester avec 'auto' |
| 264 | + $this->config->set('app.base_url', 'auto'); |
| 265 | + |
| 266 | + $initializeURL = ReflectionHelper::getPrivateMethodInvoker($this->config, 'initializeURL'); |
| 267 | + $initializeURL(); |
| 268 | + |
| 269 | + $newValue = $this->config->get('app.base_url'); |
| 270 | + expect($newValue)->not->toBe('auto'); |
| 271 | + expect($newValue)->not->toBeEmpty(); |
| 272 | + |
| 273 | + // Restaurer |
| 274 | + $this->config->set('app.base_url', $original); |
| 275 | + }); |
| 276 | + |
| 277 | + it('initializeEnvironment avec différentes valeurs', function (): void { |
| 278 | + // Tester différents environnements |
| 279 | + $environments = [ |
| 280 | + 'development' => 'development', |
| 281 | + 'dev' => 'development', |
| 282 | + 'production' => 'production', |
| 283 | + 'prod' => 'production', |
| 284 | + 'testing' => 'testing', |
| 285 | + 'test' => 'testing', |
| 286 | + 'auto' => is_online() ? 'production' : 'development' |
| 287 | + ]; |
| 288 | + |
| 289 | + foreach ($environments as $input => $expected) { |
| 290 | + $this->config->set('app.environment', $input); |
| 291 | + |
| 292 | + $initializeEnv = ReflectionHelper::getPrivateMethodInvoker($this->config, 'initializeEnvironment'); |
| 293 | + $initializeEnv(); |
| 294 | + |
| 295 | + expect($this->config->get('app.environment'))->toBe($expected); |
| 296 | + } |
| 297 | + |
| 298 | + // Test avec valeur invalide |
| 299 | + // $this->config->set('app.environment', 'invalid'); |
| 300 | + // expect(fn() => $this->config->get('app.environment'))->toThrow(new ConfigException()); |
| 301 | + }); |
| 302 | + |
| 303 | + it('configureErrorReporting pour chaque environnement', function (): void { |
| 304 | + $configureErrorReporting = ReflectionHelper::getPrivateMethodInvoker($this->config, 'configureErrorReporting'); |
| 305 | + |
| 306 | + // Sauvegarder les valeurs originales |
| 307 | + $displayErrors = ini_get('display_errors'); |
| 308 | + $errorReporting = error_reporting(); |
| 309 | + |
| 310 | + // Tester development |
| 311 | + $configureErrorReporting('development'); |
| 312 | + expect(ini_get('display_errors'))->toBe('1'); |
| 313 | + expect(error_reporting() & E_ALL)->toBe(E_ALL); |
| 314 | + |
| 315 | + // Tester production |
| 316 | + $configureErrorReporting('production'); |
| 317 | + expect(ini_get('display_errors'))->toBe('0'); |
| 318 | + |
| 319 | + // Tester testing |
| 320 | + $configureErrorReporting('testing'); |
| 321 | + expect(ini_get('display_errors'))->toBe('0'); |
| 322 | + |
| 323 | + // Restaurer |
| 324 | + ini_set('display_errors', $displayErrors); |
| 325 | + error_reporting($errorReporting); |
| 326 | + }); |
| 327 | + }); |
| 328 | + |
| 329 | + describe('Gestion des erreurs', function (): void { |
| 330 | + it('Chargement avec schéma invalide', function (): void { |
| 331 | + // Créer un fichier de schéma invalide |
| 332 | + $invalidSchema = $this->tempDir . '/schemas/invalid.config.php'; |
| 333 | + if (! is_dir($dir = dirname($invalidSchema))) { |
| 334 | + mkdir($dir, 0777, true); |
| 335 | + } |
| 336 | + file_put_contents($invalidSchema, '<?php return "not-a-schema";'); |
| 337 | + |
| 338 | + // Configurer le locator pour trouver ce schéma |
| 339 | + allow(service('locator'))->toReceive('search')->with('Config/schemas/invalid')->andReturn([$invalidSchema]); |
| 340 | + |
| 341 | + // Doit retourner null pour un schéma invalide |
| 342 | + expect(Config::schema('invalid'))->toBeNull(); |
| 343 | + }); |
| 344 | + |
| 345 | + it('Fichier de config invalide (ne retourne pas un tableau)', function (): void { |
| 346 | + $tempFile = $this->tempDir . '/invalid.php'; |
| 347 | + if (! is_dir($dir = dirname($tempFile))) { |
| 348 | + mkdir($dir, 0777, true); |
| 349 | + } |
| 350 | + file_put_contents($tempFile, '<?php return "not-an-array";'); |
| 351 | + |
| 352 | + $this->config->load('invalid', $tempFile); |
| 353 | + |
| 354 | + // Ne devrait pas charger car pas un tableau |
| 355 | + $loaded = ReflectionHelper::getPrivateProperty(Config::class, 'loaded'); |
| 356 | + expect($loaded)->not->toContainKey('invalid'); |
| 357 | + }); |
| 358 | + }); |
| 359 | + |
| 360 | + describe('Edge cases', function (): void { |
| 361 | + it('Accès à une clé avec get sans default sur config inexistante', function (): void { |
| 362 | + expect(fn() => $this->config->get('nonexistent.config.key')) |
| 363 | + ->toThrow(new ConfigException()); |
| 364 | + }); |
| 365 | + |
| 366 | + it('reset avec clé spécifique', function (): void { |
| 367 | + // Modifier plusieurs valeurs |
| 368 | + $this->config->set('app.environment', 'production'); |
| 369 | + $this->config->set('app.locale', 'fr'); |
| 370 | + |
| 371 | + // Reset seulement environment |
| 372 | + $this->config->reset('app.environment'); |
| 373 | + |
| 374 | + expect($this->config->get('app.environment'))->toBe('testing'); // Valeur par défaut |
| 375 | + expect($this->config->get('app.locale'))->toBe('fr'); // Doit rester modifié |
| 376 | + }); |
| 377 | + |
| 378 | + it('reset avec null (tout reset)', function (): void { |
| 379 | + // Modifier |
| 380 | + $this->config->set('app.environment', 'production'); |
| 381 | + $this->config->load('publisher'); |
| 382 | + $this->config->set('publisher.restrictions', [WEBROOT => '*']); |
| 383 | + |
| 384 | + // Tout reset |
| 385 | + $this->config->reset(); |
| 386 | + |
| 387 | + expect($this->config->get('app.environment'))->toBe('testing'); |
| 388 | + expect($this->config->get('publisher.restrictions'))->toContainKeys([ROOTPATH, WEBROOT]); |
| 389 | + }); |
| 390 | + |
| 391 | + it('Méthodes chainées avec ghost', function (): void { |
| 392 | + $result = $this->config->ghost('chainable') |
| 393 | + ->set('chainable.key1', 'value1') |
| 394 | + ->set('chainable.key2', 'value2'); |
| 395 | + |
| 396 | + expect($result)->toBe($this->config); |
| 397 | + expect($this->config->get('chainable.key1'))->toBe('value1'); |
| 398 | + expect($this->config->get('chainable.key2'))->toBe('value2'); |
| 399 | + }); |
| 400 | + }); |
| 401 | + |
| 402 | + describe('Performance et cache', function (): void { |
| 403 | + it('Cache est utilisé pour les appels répétés', function (): void { |
| 404 | + // Premier appel |
| 405 | + $start1 = microtime(true); |
| 406 | + $value1 = $this->config->get('app.environment'); |
| 407 | + $time1 = microtime(true) - $start1; |
| 408 | + |
| 409 | + // Deuxième appel (devrait être plus rapide avec cache) |
| 410 | + $start2 = microtime(true); |
| 411 | + $value2 = $this->config->get('app.environment'); |
| 412 | + $time2 = microtime(true) - $start2; |
| 413 | + |
| 414 | + expect($value1)->toBe($value2); |
| 415 | + // Note: Sur certains systèmes, la différence peut être minime |
| 416 | + // mais en principe $time2 devrait être <= $time1 |
| 417 | + expect($time2)->toBeLessThan($time1); |
| 418 | + }); |
| 419 | + |
| 420 | + it('Cache est invalidé après set', function (): void { |
| 421 | + // Premier get |
| 422 | + $value1 = $this->config->get('app.environment'); |
| 423 | + |
| 424 | + // Modification |
| 425 | + $this->config->set('app.environment', 'production'); |
| 426 | + |
| 427 | + // Deuxième get (doit être différent) |
| 428 | + $value2 = $this->config->get('app.environment'); |
| 429 | + |
| 430 | + expect($value1)->not->toBe($value2); |
| 431 | + expect($value2)->toBe('production'); |
| 432 | + |
| 433 | + $this->config->set('app.environment', $value1); |
| 434 | + }); |
| 435 | + }); |
| 436 | + |
| 437 | + describe('Registrars', function (): void { |
| 438 | + it('Découverte des registrars', function (): void { |
| 439 | + // Créer un fichier Registrar de test |
| 440 | + $registrarFile = config_path('Registrar.php'); |
| 441 | + $registrarContent = <<<'PHP' |
| 442 | +<?php |
| 443 | +
|
| 444 | +namespace App\Config; |
| 445 | +
|
| 446 | +class Registrar |
| 447 | +{ |
| 448 | + public static function TestConfig(): array |
| 449 | + { |
| 450 | + return [ |
| 451 | + 'key1' => 'from-registrar', |
| 452 | + 'key2' => ['nested' => 'value'] |
| 453 | + ]; |
| 454 | + } |
| 455 | +
|
| 456 | + public static function AnotherConfig(): array |
| 457 | + { |
| 458 | + return ['another' => 'value']; |
| 459 | + } |
| 460 | +
|
| 461 | + // Méthode qui ne retourne pas un tableau (doit être ignorée) |
| 462 | + public static function InvalidMethod(): string |
| 463 | + { |
| 464 | + return 'not-an-array'; |
| 465 | + } |
| 466 | +
|
| 467 | + // Méthode privée (doit être ignorée) |
| 468 | + private static function PrivateMethod(): array |
| 469 | + { |
| 470 | + return ['private' => 'ignored']; |
| 471 | + } |
| 472 | +} |
| 473 | +PHP; |
| 474 | + |
| 475 | + if (! is_dir($dir = dirname($registrarFile))) { |
| 476 | + mkdir($dir, 0777, true); |
| 477 | + } |
| 478 | + file_put_contents($registrarFile, $registrarContent); |
| 479 | + |
| 480 | + // Reset la decouverte pour forcer le chargeur du registrar a s'executer de nouveau |
| 481 | + ReflectionHelper::setPrivateProperty(Config::class, 'didDiscovery', false); |
| 482 | + ReflectionHelper::setPrivateProperty(Config::class, 'discovering', false); |
| 483 | + |
| 484 | + // Appeler loadRegistrar via reflection |
| 485 | + $loadRegistrar = ReflectionHelper::getPrivateMethodInvoker($this->config, 'loadRegistrar'); |
| 486 | + $loadRegistrar('test'); |
| 487 | + |
| 488 | + $registrars = ReflectionHelper::getPrivateProperty(Config::class, 'registrars'); |
| 489 | + |
| 490 | + expect($registrars)->toContainKeys(['TestConfig', 'AnotherConfig']); |
| 491 | + expect($registrars)->not->toContainKeys(['InvalidMethod', 'PrivateMethod']); |
| 492 | + expect($registrars['TestConfig']['key1'])->toBe('from-registrar'); |
| 493 | + |
| 494 | + unlink($registrarFile); |
| 495 | + }); |
| 496 | + |
| 497 | + it('Double découverte des registrars', function (): void { |
| 498 | + // Configurer pour être en train de découvrir |
| 499 | + ReflectionHelper::setPrivateProperty(Config::class, 'discovering', true); |
| 500 | + |
| 501 | + // Doit lancer une exception si on essaie de redécouvrir |
| 502 | + expect(fn() => $this->config->__construct())->toThrow(); |
| 503 | + }); |
| 504 | + }); |
140 | 505 | }); |
0 commit comments