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,94 @@
<?php
namespace MintyPHP\Tests\Architecture;
use PHPUnit\Framework\TestCase;
/**
* Service layer (Service, Gateway, Policy) must not contain HTML output.
* HTML rendering belongs in views (.phtml) and templates only.
*
* Excluded: PDF services that legitimately build HTML for Dompdf rendering.
*/
class ServiceLayerNoHtmlContractTest extends TestCase
{
use ProjectFileAssertionSupport;
/** @var list<string> */
private const EXCLUDED_FILES = [
'lib/Service/User/UserAccessPdfService.php',
];
/**
* Matches opening HTML tags that indicate rendering logic.
* Ignores comments and string literals containing tag descriptions.
*/
private const HTML_TAG_PATTERN = '/<(?:div|span|table|form|input|button|select|textarea|label|ul|ol|li|h[1-6]|thead|tbody|tr|td|th|section|article|nav|header|footer|main|aside)\b/';
public function testServicesContainNoHtmlTags(): void
{
$violations = $this->findHtmlViolations('lib/Service');
$violations = array_merge($violations, $this->findHtmlViolations('lib/Http'));
self::assertSame(
[],
$violations,
"Service layer must not contain HTML tags. Violations found:\n" . implode("\n", $violations)
);
}
public function testModuleServicesContainNoHtmlTags(): void
{
$root = $this->projectRootPath();
$modulesDir = $root . '/modules';
if (!is_dir($modulesDir)) {
self::markTestSkipped('modules/ directory missing.');
}
$violations = [];
foreach (scandir($modulesDir) ?: [] as $entry) {
if ($entry === '.' || $entry === '..' || !is_dir($modulesDir . '/' . $entry)) {
continue;
}
$libRel = 'modules/' . $entry . '/lib';
if (is_dir($root . '/' . $libRel)) {
$violations = array_merge($violations, $this->findHtmlViolations($libRel));
}
}
self::assertSame(
[],
$violations,
"Module service layer must not contain HTML tags. Violations found:\n" . implode("\n", $violations)
);
}
/**
* @return list<string>
*/
private function findHtmlViolations(string $relativeDirectory): array
{
$root = $this->projectRootPath();
$basePath = $root . '/' . $relativeDirectory;
if (!is_dir($basePath)) {
return [];
}
$hits = $this->findPatternMatchesInPhpFiles($relativeDirectory, self::HTML_TAG_PATTERN);
return array_values(array_filter(
$hits,
static function (string $path): bool {
// Exclude files that legitimately build HTML (PDF rendering)
foreach (self::EXCLUDED_FILES as $excluded) {
if ($path === $excluded) {
return false;
}
}
// Only flag Service/Gateway/Policy/Factory files, not generic helpers
return (bool) preg_match('/(Service|Gateway|Policy|Factory)\.php$/', $path);
}
));
}
}