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]) ); } } }