Skip to content

Commit 88a4405

Browse files
committed
feat(config) : ajout de tests Configurator pour la validation, la mise en cache et les cas limites
- Introduction de tests complets pour la classe Configurator, couvrant la validation avec des schémas stricts, des vérifications de type et des valeurs énumérées. - Mise en œuvre de tests pour la gestion du cache, y compris l'invalidation sélective et l'accès en profondeur. - Ajout de la gestion des cas limites pour les schémas manquants et les valeurs par défaut. - Amélioration des tests existants dans les classes Providers et Config pour une meilleure couverture et plus de clarté. refactor(config) : amélioration de la structure et de la documentation de la classe Config - Refactorisation de la classe Config pour rationaliser le chargement et la fusion des configurations. - Amélioration de la documentation des méthodes et ajout d'indications de type pour plus de clarté. - Amélioration de la gestion des erreurs et de la logique de validation pour la récupération et la configuration des paramètres. - Mise à jour des constantes pour une meilleure organisation et plus de clarté dans le fichier des constantes.
1 parent a1b5c48 commit 88a4405

File tree

7 files changed

+1001
-239
lines changed

7 files changed

+1001
-239
lines changed

spec/system/framework/Config/Config.spec.php

Lines changed: 369 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,54 @@
1212
use BlitzPHP\Config\Config;
1313
use BlitzPHP\Exceptions\ConfigException;
1414
use BlitzPHP\Spec\ReflectionHelper;
15+
use Nette\Schema\Expect;
1516
use Nette\Schema\Schema;
1617

1718
use function Kahlan\expect;
1819

1920
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);
2243
});
2344

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+
2463
describe('Initialisation', function (): void {
2564
it('La config est toujours initialisee', function (): void {
2665
$initialized = ReflectionHelper::getPrivateProperty(Config::class, 'initialized');
@@ -92,8 +131,8 @@
92131

93132
describe('Autres', function (): void {
94133
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();
97136
});
98137

99138
it('schema', function (): void {
@@ -137,4 +176,330 @@
137176
expect($this->config->get('app.negotiate_locale'))->toBeFalsy();
138177
});
139178
});
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+
});
140505
});

0 commit comments

Comments
 (0)