forked from fa/breadcrumb-the-shire
143 lines
4.8 KiB
PHP
143 lines
4.8 KiB
PHP
<?php
|
|
|
|
namespace MintyPHP\Tests\Architecture;
|
|
|
|
final class ModuleManifestContractTest extends AbstractModuleStructureContractTestCase
|
|
{
|
|
public function testAtLeastOneModuleExists(): void
|
|
{
|
|
self::assertNotEmpty(self::$modules, 'Expected at least one module under modules/');
|
|
}
|
|
|
|
public function testEveryModuleHasManifest(): void
|
|
{
|
|
$modulesDir = dirname(__DIR__, 2) . '/modules';
|
|
if (!is_dir($modulesDir)) {
|
|
self::markTestSkipped('No modules/ directory');
|
|
}
|
|
|
|
$entries = scandir($modulesDir) ?: [];
|
|
foreach ($entries as $entry) {
|
|
if ($entry === '.' || $entry === '..' || !is_dir($modulesDir . '/' . $entry)) {
|
|
continue;
|
|
}
|
|
self::assertFileExists(
|
|
$modulesDir . '/' . $entry . '/module.php',
|
|
"Module directory '{$entry}' is missing module.php manifest"
|
|
);
|
|
}
|
|
}
|
|
|
|
public function testManifestIdMatchesDirectoryName(): void
|
|
{
|
|
foreach (self::$modules as $module) {
|
|
self::assertSame(
|
|
$module['id'],
|
|
$module['manifest']->id,
|
|
"Module directory '{$module['id']}' has mismatched manifest id '{$module['manifest']->id}'"
|
|
);
|
|
}
|
|
}
|
|
|
|
public function testManifestHasVersion(): void
|
|
{
|
|
foreach (self::$modules as $module) {
|
|
self::assertMatchesRegularExpression(
|
|
'/^\d+\.\d+\.\d+$/',
|
|
$module['manifest']->version,
|
|
"Module '{$module['id']}' must have a semver version (got '{$module['manifest']->version}')"
|
|
);
|
|
}
|
|
}
|
|
|
|
public function testManifestHasAllRequiredKeys(): void
|
|
{
|
|
$requiredKeys = [
|
|
'id', 'version', 'enabled_by_default', 'load_order',
|
|
'routes', 'public_paths', 'container_registrars',
|
|
'ui_slots', 'search_resources', 'asset_groups',
|
|
'scheduler_jobs', 'layout_context_providers',
|
|
'session_providers', 'permissions',
|
|
];
|
|
|
|
foreach (self::$modules as $module) {
|
|
foreach ($requiredKeys as $key) {
|
|
self::assertArrayHasKey(
|
|
$key,
|
|
$module['raw'],
|
|
"Module '{$module['id']}' manifest is missing required key '{$key}'"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function testRequiresFieldContainsOnlyValidModuleIds(): void
|
|
{
|
|
foreach (self::$modules as $module) {
|
|
foreach ($module['manifest']->requires as $index => $requiredId) {
|
|
self::assertIsString($requiredId);
|
|
self::assertNotEmpty(
|
|
trim($requiredId),
|
|
"Module '{$module['id']}' requires[{$index}] must be a non-empty string"
|
|
);
|
|
self::assertMatchesRegularExpression(
|
|
'/^[a-z][a-z0-9_-]*$/',
|
|
$requiredId,
|
|
"Module '{$module['id']}' requires[{$index}] has invalid module id format: '{$requiredId}'"
|
|
);
|
|
}
|
|
}
|
|
|
|
self::addToAssertionCount(1);
|
|
}
|
|
|
|
public function testModuleHasLibDirectory(): void
|
|
{
|
|
foreach (self::$modules as $module) {
|
|
self::assertDirectoryExists(
|
|
$module['path'] . '/lib',
|
|
"Module '{$module['id']}' must have a lib/ directory for PHP classes"
|
|
);
|
|
}
|
|
}
|
|
|
|
public function testRoutesHavePathAndTarget(): void
|
|
{
|
|
foreach (self::$modules as $module) {
|
|
foreach ($module['manifest']->routes as $idx => $route) {
|
|
self::assertArrayHasKey(
|
|
'path',
|
|
$route,
|
|
"Module '{$module['id']}' route[{$idx}] is missing 'path'"
|
|
);
|
|
self::assertArrayHasKey(
|
|
'target',
|
|
$route,
|
|
"Module '{$module['id']}' route[{$idx}] is missing 'target'"
|
|
);
|
|
self::assertNotEmpty(
|
|
trim((string) $route['path']),
|
|
"Module '{$module['id']}' route[{$idx}] has empty 'path'"
|
|
);
|
|
self::assertNotEmpty(
|
|
trim((string) $route['target']),
|
|
"Module '{$module['id']}' route[{$idx}] has empty 'target'"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function testModulesWithRoutesHavePagesDirectory(): void
|
|
{
|
|
foreach (self::$modules as $module) {
|
|
if (empty($module['manifest']->routes)) {
|
|
continue;
|
|
}
|
|
self::assertDirectoryExists(
|
|
$module['path'] . '/pages',
|
|
"Module '{$module['id']}' declares routes but has no pages/ directory"
|
|
);
|
|
}
|
|
}
|
|
}
|