feat(modules): harden module platform with event constants, validation checks, and CLI improvements

- Add ModuleEvents constants class with all 8 event strings; update dispatch call sites
- ValidateCommand: check AuthorizationPolicy container registration (check 20)
- ValidateCommand: warn on unknown event_listeners keys (check 21)
- make:module --simple flag for minimal manifests
- module:deactivate reverse-dependency warning
- 12 new PHPUnit tests covering all improvements

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-14 08:23:04 +02:00
parent 143d887a5c
commit b1a907b79c
10 changed files with 604 additions and 24 deletions

View File

@@ -0,0 +1,69 @@
<?php
namespace MintyPHP\Tests\App\Module;
use MintyPHP\App\Module\ModuleEvents;
use PHPUnit\Framework\TestCase;
final class ModuleEventsTest extends TestCase
{
public function testAllConstantsAreReturnedByAll(): void
{
$reflection = new \ReflectionClass(ModuleEvents::class);
$constants = $reflection->getConstants();
self::assertNotEmpty($constants, 'ModuleEvents should define at least one constant');
self::assertSame(
array_values($constants),
ModuleEvents::all(),
'ModuleEvents::all() must return exactly the values of all class constants'
);
}
public function testConstantValuesMatchExpectedEventStrings(): void
{
self::assertSame('user.login', ModuleEvents::USER_LOGIN);
self::assertSame('user.logout', ModuleEvents::USER_LOGOUT);
self::assertSame('user.created', ModuleEvents::USER_CREATED);
self::assertSame('user.deleted', ModuleEvents::USER_DELETED);
self::assertSame('user.activated', ModuleEvents::USER_ACTIVATED);
self::assertSame('user.deactivated', ModuleEvents::USER_DEACTIVATED);
self::assertSame('user.assignment_changed', ModuleEvents::USER_ASSIGNMENT_CHANGED);
self::assertSame('scheduler.job_failed', ModuleEvents::SCHEDULER_JOB_FAILED);
}
public function testAllReturnsUniqueValues(): void
{
$all = ModuleEvents::all();
self::assertSame($all, array_unique($all), 'ModuleEvents::all() must not contain duplicates');
}
public function testClassIsNotInstantiable(): void
{
$reflection = new \ReflectionClass(ModuleEvents::class);
self::assertFalse($reflection->isInstantiable(), 'ModuleEvents should not be instantiable');
}
public function testDispatchCallSitesUseConstants(): void
{
$root = dirname(__DIR__, 3);
$files = [
$root . '/core/Service/Auth/AuthService.php',
$root . '/core/Service/User/UserAccountService.php',
$root . '/core/Service/Scheduler/SchedulerRunService.php',
];
foreach ($files as $file) {
$content = file_get_contents($file);
self::assertIsString($content, "Could not read {$file}");
// Ensure no raw string dispatch calls remain (except in ternaries which use constants)
preg_match_all('/->dispatch\(\s*[\'"]([^"\']+)[\'"]\s*,/', $content, $matches);
self::assertSame(
[],
$matches[1],
basename($file) . ' still uses raw string event names: ' . implode(', ', $matches[1])
);
}
}
}