fixturesDir = sys_get_temp_dir() . '/scheduler-module-registry-' . uniqid(); mkdir($this->fixturesDir, 0777, true); } protected function tearDown(): void { $this->removeDir($this->fixturesDir); } public function testModuleJobAppearsInDefinitionsAndIsExecutable(): void { $moduleRegistry = $this->createRegistryWithModuleJob(); $container = new AppContainer(); $container->set(ModuleRegistry::class, static fn (): ModuleRegistry => $moduleRegistry); $container->set( DummyModuleScheduledJobHandler::class, static fn (): DummyModuleScheduledJobHandler => new DummyModuleScheduledJobHandler() ); $registry = new ScheduledJobRegistry( $this->createMock(UserLifecycleService::class), $container ); $definitions = $registry->definitions(); self::assertArrayHasKey('addressbook.sync', $definitions); self::assertSame('Address book sync', $definitions['addressbook.sync']['label'] ?? null); $execution = $registry->execute('addressbook.sync', 123); self::assertSame('success', $execution['status']); self::assertSame(['actor' => 123], $execution['result']); } private function createRegistryWithModuleJob(): ModuleRegistry { mkdir($this->fixturesDir . '/mod-job', 0777, true); file_put_contents( $this->fixturesDir . '/mod-job/module.php', ' 'mod-job', 'scheduler_jobs' => [[ 'job_key' => 'addressbook.sync', 'handler' => DummyModuleScheduledJobHandler::class, 'label' => 'Address book sync', 'description' => 'Sync job', 'default_enabled' => true, 'default_timezone' => 'UTC', 'default_schedule_type' => 'daily', 'default_schedule_interval' => 1, 'default_schedule_time' => '03:00', 'default_schedule_weekdays_csv' => null, 'default_catchup_once' => true, 'allowed_schedule_types' => ['daily', 'weekly'], ]], ], true) . ';' ); return ModuleRegistry::boot($this->fixturesDir, ['mod-job']); } 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); } } final class DummyModuleScheduledJobHandler implements ScheduledJobHandlerInterface { public function definition(): array { return [ 'label' => 'dummy', 'description' => 'dummy', 'default_enabled' => 1, 'default_timezone' => 'UTC', 'default_schedule_type' => 'daily', 'default_schedule_interval' => 1, 'default_schedule_time' => '00:00', 'default_schedule_weekdays_csv' => null, 'default_catchup_once' => 1, 'allowed_schedule_types' => ['daily'], ]; } public function execute(?int $actorUserId): array { return [ 'status' => 'success', 'error_code' => null, 'error_message' => null, 'result' => ['actor' => $actorUserId], ]; } }