Module IDs to clean up */ private array $cleanup = []; private \ReflectionMethod $validateModule; private ValidateCommand $command; protected function setUp(): void { $this->modulesDir = dirname(__DIR__, 2) . '/modules'; $this->command = new ValidateCommand(); $this->validateModule = new \ReflectionMethod($this->command, 'validateModule'); } protected function tearDown(): void { foreach ($this->cleanup as $id) { $this->removeDir($this->modulesDir . '/' . $id); } } public function testValidateWarnsOnUnknownEventListenerKey(): void { $id = 'ztest-unknown-event'; $this->scaffoldModule($id, <<<'PHP' 'ztest-unknown-event', 'version' => '0.1.0', 'event_listeners' => [ 'user.created' => [ ['class' => 'NonExistent\Listener', 'method' => 'handle'], ], 'totally.unknown.event' => [ ['class' => 'NonExistent\Listener', 'method' => 'handle'], ], ], ]; PHP); [$errors, $warnings] = $this->validateModule->invoke($this->command, $this->modulesDir, $id); $warningText = implode("\n", $warnings); self::assertStringContainsString('totally.unknown.event', $warningText); self::assertStringContainsString('not defined in ModuleEvents', $warningText); // user.created is a known event — should NOT trigger a warning self::assertStringNotContainsString("unknown event 'user.created'", $warningText); } public function testValidateDoesNotWarnOnKnownEventKeys(): void { $id = 'ztest-known-events'; $this->scaffoldModule($id, <<<'PHP' 'ztest-known-events', 'version' => '0.1.0', 'event_listeners' => [ 'user.created' => [ ['class' => 'NonExistent\Listener', 'method' => 'handle'], ], 'scheduler.job_failed' => [ ['class' => 'NonExistent\Listener', 'method' => 'handle'], ], ], ]; PHP); [$errors, $warnings] = $this->validateModule->invoke($this->command, $this->modulesDir, $id); $warningText = implode("\n", $warnings); self::assertStringNotContainsString('not defined in ModuleEvents', $warningText); } public function testValidateWarnsWhenPolicyNotReferencedInRegistrar(): void { $id = 'ztest-policy-missing'; $moduleDir = $this->modulesDir . '/' . $id; mkdir($moduleDir . '/lib/Module/ZtestPolicyMissing', 0777, true); $this->cleanup[] = $id; file_put_contents( $moduleDir . '/lib/Module/ZtestPolicyMissing/ZtestPolicyMissingContainerRegistrar.php', <<<'PHP' 'ztest-policy-missing', 'version' => '0.1.0', 'container_registrars' => [\MintyPHP\Module\ZtestPolicyMissing\ZtestPolicyMissingContainerRegistrar::class], 'authorization_policies' => [\MintyPHP\Module\ZtestPolicyMissing\ZtestPolicyMissingAuthorizationPolicy::class], ]; PHP); [$errors, $warnings] = $this->validateModule->invoke($this->command, $this->modulesDir, $id); $warningText = implode("\n", $warnings); self::assertStringContainsString('ZtestPolicyMissingAuthorizationPolicy', $warningText); self::assertStringContainsString('not referenced in any ContainerRegistrar', $warningText); } public function testValidateNoWarningWhenPolicyReferencedInRegistrar(): void { $id = 'ztest-policy-ok'; $moduleDir = $this->modulesDir . '/' . $id; mkdir($moduleDir . '/lib/Module/ZtestPolicyOk', 0777, true); $this->cleanup[] = $id; file_put_contents( $moduleDir . '/lib/Module/ZtestPolicyOk/ZtestPolicyOkContainerRegistrar.php', <<<'PHP' set(ZtestPolicyOkAuthorizationPolicy::class, static fn () => new ZtestPolicyOkAuthorizationPolicy()); } } PHP ); file_put_contents( $moduleDir . '/lib/Module/ZtestPolicyOk/ZtestPolicyOkAuthorizationPolicy.php', <<<'PHP' 'ztest-policy-ok', 'version' => '0.1.0', 'container_registrars' => [\MintyPHP\Module\ZtestPolicyOk\ZtestPolicyOkContainerRegistrar::class], 'authorization_policies' => [\MintyPHP\Module\ZtestPolicyOk\ZtestPolicyOkAuthorizationPolicy::class], ]; PHP); [$errors, $warnings] = $this->validateModule->invoke($this->command, $this->modulesDir, $id); self::assertSame([], $errors); $warningText = implode("\n", $warnings); self::assertStringNotContainsString('not referenced', $warningText); } private function scaffoldModule(string $id, string $manifestContent): void { $moduleDir = $this->modulesDir . '/' . $id; mkdir($moduleDir . '/lib', 0777, true); file_put_contents($moduleDir . '/module.php', $manifestContent); $this->cleanup[] = $id; } private function removeDir(string $dir): void { if (!is_dir($dir)) { return; } $items = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::CHILD_FIRST ); foreach ($items as $item) { if ($item->isDir()) { rmdir($item->getPathname()); } else { unlink($item->getPathname()); } } rmdir($dir); } }