1
0

test: add architecture contract tests for layering, sessions, and asset versioning

New contracts enforce repository layer isolation, no HTML in services,
no DB calls in views, module session key prefixes, and asset versioning.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-22 14:15:41 +01:00
parent 555199b217
commit be8bf496cb
5 changed files with 428 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
<?php
namespace MintyPHP\Tests\Architecture;
/**
* Module code must prefix session keys with "module.<id>." to prevent
* collisions between modules and with core session state.
*/
final class ModuleSessionKeyPrefixContractTest extends AbstractModuleStructureContractTestCase
{
/**
* Pattern matches direct $_SESSION access with a string key that does NOT
* start with "module.".
*/
private const SESSION_ACCESS_PATTERN = '/\$_SESSION\s*\[\s*[\'"](?!module\.)/';
public function testModulePhpFilesUseSessionPrefix(): void
{
$modulesDir = realpath(__DIR__ . '/../../modules');
self::assertNotFalse($modulesDir);
$violations = [];
foreach (self::$modules as $module) {
$moduleId = $module['id'];
foreach (['lib', 'pages'] as $subdir) {
$dir = $module['path'] . '/' . $subdir;
if (!is_dir($dir)) {
continue;
}
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS),
\RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($iterator as $file) {
/** @var \SplFileInfo $file */
$ext = $file->getExtension();
if ($ext !== 'php' && $ext !== 'phtml') {
continue;
}
$content = file_get_contents($file->getPathname());
if ($content === false) {
continue;
}
if (preg_match(self::SESSION_ACCESS_PATTERN, $content)) {
$relativePath = 'modules/' . $moduleId . '/' . $subdir . '/' . str_replace($dir . '/', '', $file->getPathname());
$violations[] = $relativePath . " (module '{$moduleId}') — session keys must start with 'module.{$moduleId}.'";
}
}
}
}
self::assertSame(
[],
$violations,
"Module session keys must be prefixed with 'module.<id>.':\n" . implode("\n", $violations)
);
}
}