2026-08-12 15:45:29 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
2026-08-13 11:25:08 +02:00
|
|
|
namespace Tests\Architecture;
|
|
|
|
|
|
2026-08-13 12:58:29 +02:00
|
|
|
/**
|
|
|
|
|
* @param array<int, string> $directories
|
|
|
|
|
* @return array<int, string>
|
|
|
|
|
*/
|
|
|
|
|
function getTestFiles(array $directories): array
|
|
|
|
|
{
|
|
|
|
|
$files = [];
|
|
|
|
|
foreach ($directories as $dir) {
|
2026-08-17 09:44:10 +02:00
|
|
|
$path = dirname(__DIR__, 2) . '/' . ltrim($dir, '/');
|
2026-08-13 12:58:29 +02:00
|
|
|
if (! is_dir($path)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$dirIterator = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
|
|
|
|
|
$iterator = new \RecursiveIteratorIterator($dirIterator);
|
|
|
|
|
/** @var \SplFileInfo $file */
|
|
|
|
|
foreach ($iterator as $file) {
|
|
|
|
|
if ($file->isFile() && str_ends_with($file->getFilename(), 'Test.php')) {
|
|
|
|
|
$files[] = $file->getPathname();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $files;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-12 15:45:29 +02:00
|
|
|
describe('Coding Standards & Project Rules', function (): void {
|
|
|
|
|
arch()->preset()->php();
|
2026-08-12 16:56:13 +02:00
|
|
|
// Le helper assert natif est interdit en faveur de Webmozart\Assert
|
|
|
|
|
arch()->preset()->security();
|
|
|
|
|
|
|
|
|
|
arch('assert is not used in src (use Webmozart\Assert instead)')
|
|
|
|
|
->expect('assert')
|
|
|
|
|
->not->toBeUsed();
|
2026-08-12 15:45:29 +02:00
|
|
|
|
|
|
|
|
arch('config() helper is not used in src (use Config facade instead)')
|
|
|
|
|
->expect('config')
|
|
|
|
|
->not->toBeUsed();
|
|
|
|
|
|
|
|
|
|
arch('env() helper is not used in src (use Config facade instead)')
|
|
|
|
|
->expect('env')
|
|
|
|
|
->not->toBeUsed();
|
|
|
|
|
|
|
|
|
|
arch('debug functions are not used in src')
|
|
|
|
|
->expect(['dd', 'dump', 'var_dump', 'die', 'print_r', 'ray'])
|
|
|
|
|
->not->toBeUsed();
|
|
|
|
|
|
|
|
|
|
arch('shell execution functions are not used in src')
|
|
|
|
|
->expect(['exec', 'shell_exec', 'system', 'passthru', 'proc_open'])
|
|
|
|
|
->not->toBeUsed();
|
|
|
|
|
|
|
|
|
|
arch('eval function is not used in src')
|
|
|
|
|
->expect('eval')
|
|
|
|
|
->not->toBeUsed();
|
|
|
|
|
|
|
|
|
|
arch('insecure hashing functions are not used in src')
|
|
|
|
|
->expect(['md5', 'sha1'])
|
|
|
|
|
->not->toBeUsed();
|
|
|
|
|
|
|
|
|
|
arch('sleep functions are not used in src')
|
|
|
|
|
->expect(['sleep', 'usleep'])
|
|
|
|
|
->not->toBeUsed();
|
|
|
|
|
|
2026-08-21 15:45:32 +02:00
|
|
|
arch('mutable DateTime is not used in src (use DateTimeImmutable or DateTimeInterface)')
|
|
|
|
|
->expect('DateTime')
|
|
|
|
|
->not->toBeUsed();
|
|
|
|
|
|
2026-08-12 15:45:29 +02:00
|
|
|
arch('all Domain source files use strict types')
|
|
|
|
|
->expect('Domain')
|
|
|
|
|
->toUseStrictTypes();
|
|
|
|
|
|
|
|
|
|
arch('all Application source files use strict types')
|
|
|
|
|
->expect('Application')
|
|
|
|
|
->toUseStrictTypes();
|
|
|
|
|
|
|
|
|
|
arch('all Presentation source files use strict types')
|
|
|
|
|
->expect('Presentation')
|
|
|
|
|
->toUseStrictTypes();
|
|
|
|
|
|
|
|
|
|
arch('all Infra source files use strict types')
|
|
|
|
|
->expect('Infra')
|
|
|
|
|
->toUseStrictTypes();
|
|
|
|
|
|
|
|
|
|
arch('all Shared source files use strict types')
|
|
|
|
|
->expect('Shared')
|
|
|
|
|
->toUseStrictTypes();
|
2026-08-13 11:25:08 +02:00
|
|
|
|
|
|
|
|
arch('all test files belong to Tests namespace and use strict types')
|
|
|
|
|
->expect('Tests')
|
|
|
|
|
->toUseStrictTypes();
|
|
|
|
|
|
2026-08-14 10:36:26 +02:00
|
|
|
it('ensures every database seeder has a corresponding integration test', function (): void {
|
2026-08-17 09:44:10 +02:00
|
|
|
$seederFiles = glob(dirname(__DIR__, 2) . '/database/seeders/*Seeder.php') ?: [];
|
2026-08-14 10:36:26 +02:00
|
|
|
foreach ($seederFiles as $file) {
|
|
|
|
|
$basename = basename($file, '.php');
|
2026-08-17 09:44:10 +02:00
|
|
|
$testFile = dirname(__DIR__, 2) . "/tests/Integration/Database/Seeders/{$basename}Test.php";
|
2026-08-14 10:36:26 +02:00
|
|
|
expect(file_exists($testFile))->toBeTrue(
|
|
|
|
|
"Missing integration test for seeder [{$basename}]. Expected at [{$testFile}]."
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-08-13 11:25:08 +02:00
|
|
|
it('ensures every test file in tests directory has a namespace', function (): void {
|
2026-08-13 12:58:29 +02:00
|
|
|
foreach (getTestFiles(['tests']) as $file) {
|
2026-08-13 11:25:08 +02:00
|
|
|
$content = (string) file_get_contents($file);
|
|
|
|
|
expect($content)->toMatch(
|
|
|
|
|
'/^\s*namespace\s+Tests/m',
|
|
|
|
|
"File [{$file}] is missing a namespace Tests declaration."
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
2026-08-13 12:02:43 +02:00
|
|
|
|
2026-08-21 15:01:36 +02:00
|
|
|
it('ensures native classes in global namespace are not imported with use statements in src', function (): void {
|
|
|
|
|
$srcDir = dirname(__DIR__, 2) . '/src';
|
|
|
|
|
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($srcDir, \RecursiveDirectoryIterator::SKIP_DOTS));
|
|
|
|
|
|
|
|
|
|
/** @var \SplFileInfo $file */
|
|
|
|
|
foreach ($iterator as $file) {
|
|
|
|
|
if ($file->isFile() && $file->getExtension() === 'php') {
|
|
|
|
|
$content = (string) file_get_contents($file->getPathname());
|
|
|
|
|
if (preg_match('/^use\s+([A-Z][a-zA-Z0-9_]*)\s*;/m', $content, $matches)) {
|
|
|
|
|
$className = $matches[1];
|
|
|
|
|
if (class_exists('\\' . $className) || interface_exists('\\' . $className)) {
|
|
|
|
|
expect(true)->toBeFalse(
|
|
|
|
|
sprintf('File [%s] imports global native class [%s] with a use statement. Use [\\%s] directly instead.', $file->getPathname(), $className, $className)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-08-13 12:02:43 +02:00
|
|
|
}
|
2026-08-21 15:01:36 +02:00
|
|
|
|
|
|
|
|
expect(true)->toBeTrue();
|
2026-08-13 12:02:43 +02:00
|
|
|
});
|
2026-08-12 15:45:29 +02:00
|
|
|
});
|