Extract findPatternMatchesIn*Files() to ProjectFileAssertionSupport trait for reuse. Add contract tests for: - GR-SEC-005: Encryption centralized in Crypto.php - GR-SEC-006: File storage in storage/, not web/ - GR-SEC-002: No PII in error_log() calls - GR-CORE-012: POST-Redirect-GET pattern enforcement - GR-UI-015: i18n key completeness de ↔ en Brings automated guard coverage from 24 to 29 test files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
142 lines
5.9 KiB
PHP
142 lines
5.9 KiB
PHP
<?php
|
|
|
|
namespace MintyPHP\Tests\Architecture;
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
class CoreStarterkitContractTest extends TestCase
|
|
{
|
|
use ProjectFileAssertionSupport;
|
|
|
|
public function testPagesDoNotInstantiateServicesRepositoriesOrGatewaysDirectly(): void
|
|
{
|
|
$violations = $this->findPatternMatchesInPhpFiles('pages', '/\bnew\s+[\\\\A-Za-z0-9_]+(?:Service|Gateway|Repository)\s*\(/');
|
|
$this->assertSame([], $violations, "Direct service/repository/gateway instantiation found in pages/:\n" . implode("\n", $violations));
|
|
}
|
|
|
|
public function testPagesDoNotUseDatabaseFacadeDirectly(): void
|
|
{
|
|
$violations = $this->findPatternMatchesInPhpFiles('pages', '/\bDB::/');
|
|
$this->assertSame([], $violations, "Direct DB facade usage found in pages/:\n" . implode("\n", $violations));
|
|
}
|
|
|
|
public function testAdminPagesDoNotUseFactoryCreateChains(): void
|
|
{
|
|
$violations = $this->findPatternMatchesInPhpFiles('pages/admin', '/app\([^\n]*Factory::class\)->create/');
|
|
$this->assertSame([], $violations, "Factory create-chain usage found in pages/admin/:\n" . implode("\n", $violations));
|
|
}
|
|
|
|
public function testAdminPagesDoNotReferenceFactoryClasses(): void
|
|
{
|
|
$violations = $this->findPatternMatchesInPhpFiles('pages/admin', '/Factory::class/');
|
|
$this->assertSame([], $violations, "Factory class usage found in pages/admin/:\n" . implode("\n", $violations));
|
|
}
|
|
|
|
public function testPagesUseRequestInputInsteadOfSuperglobals(): void
|
|
{
|
|
$patterns = [
|
|
'/\$_POST/' => '$_POST',
|
|
'/\$_GET/' => '$_GET',
|
|
'/\$_FILES/' => '$_FILES',
|
|
'/REQUEST_METHOD/' => 'REQUEST_METHOD',
|
|
'/\$_SESSION/' => '$_SESSION',
|
|
'/\$_SERVER/' => '$_SERVER',
|
|
'/\$_COOKIE/' => '$_COOKIE',
|
|
];
|
|
|
|
foreach ($patterns as $pattern => $label) {
|
|
$violations = $this->findPatternMatchesInPhpFiles('pages', $pattern);
|
|
$this->assertSame([], $violations, "Superglobal {$label} usage found in pages/:\n" . implode("\n", $violations));
|
|
}
|
|
}
|
|
|
|
public function testServicesDoNotUseSuperglobals(): void
|
|
{
|
|
$patterns = [
|
|
'/\$_POST/' => '$_POST',
|
|
'/\$_GET/' => '$_GET',
|
|
'/\$_FILES/' => '$_FILES',
|
|
'/\$_SERVER/' => '$_SERVER',
|
|
'/\$_COOKIE/' => '$_COOKIE',
|
|
'/\$_SESSION/' => '$_SESSION',
|
|
'/\$_REQUEST/' => '$_REQUEST',
|
|
];
|
|
|
|
foreach ($patterns as $pattern => $label) {
|
|
$violations = $this->findPatternMatchesInPhpFiles('lib/Service', $pattern);
|
|
$this->assertSame([], $violations, "Superglobal {$label} usage found in lib/Service/:\n" . implode("\n", $violations));
|
|
}
|
|
}
|
|
|
|
public function testApiPagesDoNotReadJsonBodyDirectly(): void
|
|
{
|
|
$violations = $this->findPatternMatchesInPhpFiles('pages/api/v1', '/ApiResponse::readJsonBody\s*\(/');
|
|
$this->assertSame([], $violations, "ApiResponse::readJsonBody usage found in pages/api/v1/:\n" . implode("\n", $violations));
|
|
}
|
|
|
|
public function testTemplatesDoNotUseLegacyFilterToggleMarkup(): void
|
|
{
|
|
$pattern = '/data-(?:toolbar-toggle|filter-overflow|filter-toggle)/';
|
|
$violations = array_merge(
|
|
$this->findPatternMatchesInFiles('templates', $pattern, ['phtml']),
|
|
$this->findPatternMatchesInFiles('pages', $pattern, ['phtml'])
|
|
);
|
|
sort($violations);
|
|
|
|
$this->assertSame([], $violations, "Legacy filter toggle markup found in templates/pages:\n" . implode("\n", $violations));
|
|
}
|
|
|
|
public function testPagesAndTemplatesDoNotUseInlineModuleScripts(): void
|
|
{
|
|
$pattern = '/<script\b(?=[^>]*\btype=["\']module["\'])(?![^>]*\bsrc=)[^>]*>/i';
|
|
$violations = array_merge(
|
|
$this->findPatternMatchesInFiles('templates', $pattern, ['phtml']),
|
|
$this->findPatternMatchesInFiles('pages', $pattern, ['phtml'])
|
|
);
|
|
sort($violations);
|
|
|
|
$this->assertSame([], $violations, "Inline module scripts found in templates/pages:\n" . implode("\n", $violations));
|
|
}
|
|
|
|
public function testPagesAndTemplatesUseAssetVersionForJsUrls(): void
|
|
{
|
|
$pattern = '/asset\(\s*[\'"][^\'"]+\.js/i';
|
|
$violations = array_merge(
|
|
$this->findPatternMatchesInFiles('templates', $pattern, ['phtml']),
|
|
$this->findPatternMatchesInFiles('pages', $pattern, ['phtml'])
|
|
);
|
|
sort($violations);
|
|
|
|
$this->assertSame([], $violations, "asset(...) JS URLs found in templates/pages:\n" . implode("\n", $violations));
|
|
}
|
|
|
|
public function testNoLegacySettingsGatewayOrServiceReferencesExist(): void
|
|
{
|
|
$patterns = [
|
|
'/\bSettingService::class\b/' => 'SettingService::class',
|
|
'/\bSettingGateway::class\b/' => 'SettingGateway::class',
|
|
'/\bnew\s+SettingService\s*\(/' => 'new SettingService(...)',
|
|
'/\bnew\s+SettingGateway\s*\(/' => 'new SettingGateway(...)',
|
|
'/use\s+[^;]*\\\\SettingService;/' => 'use ...\\SettingService',
|
|
'/use\s+[^;]*\\\\SettingGateway;/' => 'use ...\\SettingGateway',
|
|
];
|
|
|
|
foreach ($patterns as $pattern => $label) {
|
|
$violations = array_merge(
|
|
$this->findPatternMatchesInPhpFiles('lib', $pattern),
|
|
$this->findPatternMatchesInPhpFiles('pages', $pattern),
|
|
$this->findPatternMatchesInPhpFiles('tests', $pattern),
|
|
);
|
|
$violations = array_values(array_filter(
|
|
$violations,
|
|
static fn (string $path): bool => $path !== 'tests/Architecture/CoreStarterkitContractTest.php'
|
|
));
|
|
$violations = array_values(array_unique($violations));
|
|
sort($violations);
|
|
|
|
$this->assertSame([], $violations, "Legacy settings reference {$label} found:\n" . implode("\n", $violations));
|
|
}
|
|
}
|
|
|
|
}
|