*/ private array $createdFiles = []; /** @var list */ private array $createdDirectories = []; protected function setUp(): void { parent::setUp(); $this->projectRoot = TEST_PROJECT_ROOT; $this->resetRuntimeContext(); } protected function tearDown(): void { $this->cleanupPaths(); $this->resetRuntimeContext(); parent::tearDown(); } public function testLocalAppsAreDiscoveredWithoutTreatingSharedDirectoriesAsApps(): void { $this->createLocalApp('demoapp'); $this->bootApplication(); $locals = AppService::local(true); $this->assertArrayHasKey('index', $locals); $this->assertArrayHasKey('demoapp', $locals); $this->assertArrayNotHasKey('controller', $locals); $this->assertArrayNotHasKey('model', $locals); $payload = $this->dispatchPath($this->bootApplication(), 'demoapp/dashboard/index'); $this->assertSame('demoapp', $payload['app']); $this->assertSame('', $payload['plugin']); $this->assertSame(RequestContext::ENTRY_WEB, $payload['entry']); $this->assertSame('/demoapp', $payload['root']); $this->assertSame('dashboard/index', $payload['pathinfo']); } public function testExplicitPluginPrefixStillWinsOverGlobalRoutes(): void { $this->createLocalApp('demoapp'); $this->createRouteFile('multaccess_explicit_plugin.php', <<<'PHP' dispatchPath($this->bootApplication(), 'system/login'); $this->assertSame('system', $payload['app']); $this->assertSame('system', $payload['plugin']); $this->assertSame(RequestContext::ENTRY_WEB, $payload['entry']); $this->assertSame('/system', $payload['root']); $this->assertSame('login', $payload['pathinfo']); } public function testGlobalRoutesCanBindLocalAndPluginTargets(): void { $this->createLocalApp('demoapp'); $this->createRouteFile('multaccess_targets.php', <<<'PHP' dispatchPath($this->bootApplication(), 'shortcut'); $this->assertSame('demoapp', $local['app']); $this->assertSame('', $local['plugin']); $this->assertSame(RequestContext::ENTRY_WEB, $local['entry']); $this->assertSame('shortcut', $local['pathinfo']); $plugin = $this->dispatchPath($this->bootApplication(), 'open-upload'); $this->assertSame('system', $plugin['app']); $this->assertSame('system', $plugin['plugin']); $this->assertSame(RequestContext::ENTRY_API, $plugin['entry']); $this->assertSame('open-upload', $plugin['pathinfo']); } public function testGlobalRouteGroupsCanDeclareDispatchTarget(): void { $this->createLocalApp('demoapp'); $this->createRouteFile('multaccess_groups.php', <<<'PHP' dispatchPath($this->bootApplication(), 'group-shortcut'); $this->assertSame('demoapp', $local['app']); $this->assertSame('', $local['plugin']); $this->assertSame(RequestContext::ENTRY_WEB, $local['entry']); $plugin = $this->dispatchPath($this->bootApplication(), 'group-open-upload'); $this->assertSame('system', $plugin['app']); $this->assertSame('system', $plugin['plugin']); $this->assertSame(RequestContext::ENTRY_API, $plugin['entry']); } public function testLegacyModuleStyleGlobalRoutesCanStillInferDispatchTarget(): void { $this->createLocalApp('demoapp'); $this->createRouteFile('multaccess_legacy_targets.php', <<<'PHP' dispatchPath($this->bootApplication(), 'legacy-demo'); $this->assertSame('demoapp', $local['app']); $this->assertSame('', $local['plugin']); $this->assertSame(RequestContext::ENTRY_WEB, $local['entry']); $plugin = $this->dispatchPath($this->bootApplication(), 'legacy-system'); $this->assertSame('system', $plugin['app']); $this->assertSame('system', $plugin['plugin']); $this->assertSame(RequestContext::ENTRY_WEB, $plugin['entry']); } private function bootApplication(): App { $this->resetRuntimeContext(); $app = new App($this->projectRoot); RuntimeService::init($app); $app->initialize(); $app->config->set(['with_route' => true], 'app'); $app->config->set(['default_app' => 'index'], 'route'); return $app; } /** * @return array */ private function dispatchPath(App $app, string $pathinfo): array { $request = $app->make('request', [], true); $request->setRoot(''); $request->setPathinfo($pathinfo); $app->instance('request', $request); $response = (new MultAccess($app))->handle($request, static function (Request $request) use ($app): Response { return Response::create([ 'app' => $app->http->getName(), 'plugin' => RequestContext::instance()->pluginCode(), 'entry' => RequestContext::instance()->entryType(), 'root' => $request->root(), 'pathinfo' => $request->pathinfo(), ], 'json'); }); return (array)json_decode((string)$response->getContent(), true, 512, JSON_THROW_ON_ERROR); } private function createLocalApp(string $code): void { $base = $this->projectRoot . '/app/' . $code; $controllerPath = $base . '/controller'; if (!is_dir($controllerPath)) { mkdir($controllerPath, 0777, true); } $this->createdDirectories[] = $base; $file = $controllerPath . '/Index.php'; file_put_contents($file, str_replace('__CODE__', $code, <<<'PHP' createdFiles[] = $file; } private function createRouteFile(string $name, string $content): void { $directory = $this->projectRoot . '/route'; if (!is_dir($directory)) { mkdir($directory, 0777, true); $this->createdDirectories[] = $directory; } $file = $this->projectRoot . '/route/' . $name; file_put_contents($file, $content); $this->createdFiles[] = $file; } private function cleanupPaths(): void { foreach (array_reverse($this->createdFiles) as $file) { if (is_file($file)) { @unlink($file); } } foreach (array_reverse(array_unique($this->createdDirectories)) as $directory) { $this->removeDirectory($directory); } } private function removeDirectory(string $path): void { if ($path === '' || !is_dir($path)) { return; } foreach (scandir($path) ?: [] as $item) { if ($item === '.' || $item === '..') { continue; } $target = $path . DIRECTORY_SEPARATOR . $item; if (is_dir($target)) { $this->removeDirectory($target); } elseif (is_file($target)) { @unlink($target); } } @rmdir($path); } private function resetRuntimeContext(): void { AppService::clear(); RequestContext::clear(); if (function_exists('sysvar')) { sysvar('', ''); } function_exists('test_reset_model_makers') && test_reset_model_makers(); } }