feat: module architecture improvements — session keys, dependency graph, event dispatcher, deactivation hooks
Six targeted improvements to the modular monolith platform: 1. Fix AddressBook session key prefix to follow module.<id>.* convention 2. Move ADDRESS_BOOK_VIEW permission constant from core PermissionService into module 3. Add declarative JSON schema for module manifests (.agents/contracts/) 4. Add `requires` field with missing-dependency and circular-dependency detection 5. Add lightweight fire-and-forget event dispatcher (user.created/deleted/login/logout) 6. Add module deactivation hook interface and CLI script (bin/module-deactivate.php) Includes 15 new architecture/unit tests covering all new functionality. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -143,6 +143,57 @@ final class ModuleRegistryContractTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function testMissingDependencyThrowsException(): void
|
||||
{
|
||||
$fixturesDir = sys_get_temp_dir() . '/module-dep-test-' . uniqid();
|
||||
mkdir($fixturesDir . '/mod-dep', 0777, true);
|
||||
|
||||
file_put_contents($fixturesDir . '/mod-dep/module.php', '<?php return ' . var_export([
|
||||
'id' => 'mod-dep',
|
||||
'requires' => ['nonexistent-module'],
|
||||
], true) . ';');
|
||||
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->expectExceptionMessage("requires module 'nonexistent-module' which is not enabled");
|
||||
|
||||
try {
|
||||
ModuleRegistry::boot($fixturesDir, ['mod-dep']);
|
||||
} finally {
|
||||
@unlink($fixturesDir . '/mod-dep/module.php');
|
||||
@rmdir($fixturesDir . '/mod-dep');
|
||||
@rmdir($fixturesDir);
|
||||
}
|
||||
}
|
||||
|
||||
public function testCircularDependencyThrowsException(): void
|
||||
{
|
||||
$fixturesDir = sys_get_temp_dir() . '/module-circular-test-' . uniqid();
|
||||
mkdir($fixturesDir . '/mod-x', 0777, true);
|
||||
mkdir($fixturesDir . '/mod-y', 0777, true);
|
||||
|
||||
file_put_contents($fixturesDir . '/mod-x/module.php', '<?php return ' . var_export([
|
||||
'id' => 'mod-x',
|
||||
'requires' => ['mod-y'],
|
||||
], true) . ';');
|
||||
file_put_contents($fixturesDir . '/mod-y/module.php', '<?php return ' . var_export([
|
||||
'id' => 'mod-y',
|
||||
'requires' => ['mod-x'],
|
||||
], true) . ';');
|
||||
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->expectExceptionMessage('Circular module dependency');
|
||||
|
||||
try {
|
||||
ModuleRegistry::boot($fixturesDir, ['mod-x', 'mod-y']);
|
||||
} finally {
|
||||
@unlink($fixturesDir . '/mod-x/module.php');
|
||||
@unlink($fixturesDir . '/mod-y/module.php');
|
||||
@rmdir($fixturesDir . '/mod-x');
|
||||
@rmdir($fixturesDir . '/mod-y');
|
||||
@rmdir($fixturesDir);
|
||||
}
|
||||
}
|
||||
|
||||
public function testConflictDetectionIsStrict(): void
|
||||
{
|
||||
$fixturesDir = sys_get_temp_dir() . '/module-arch-test-' . uniqid();
|
||||
|
||||
Reference in New Issue
Block a user