di = $di; } public function priority(Box_App $app, string $id): string { return 'shared:' . $id; } public function injected(Box_App $app): string { return $this->di instanceof Pimple\Container && $this->di['shared_marker'] === 'available' ? 'di:available' : 'di:missing'; } public function collectDebugbar(Box_App $app): string { $app->getDebugBar()->getData(); return 'shared-debugbar:collected'; } } class BoxAppRouteDispatchApp extends Box_App { public function __construct(private readonly string $routeMode) { parent::__construct(); } protected function init(): void { if ($this->routeMode === 'shared-priority') { $this->get('/priority/:id', 'priority', ['id' => '[0-9]+'], BoxAppRouteDispatchSharedController::class); return; } if ($this->routeMode === 'shared-di') { $this->get('/shared-di', 'injected', [], BoxAppRouteDispatchSharedController::class); return; } if ($this->routeMode === 'shared-debugbar-collect') { $this->get('/shared-debugbar-collect', 'collectDebugbar', [], BoxAppRouteDispatchSharedController::class); return; } if ($this->routeMode === 'default-argument') { $this->get('/default', 'withDefault'); return; } if ($this->routeMode === 'debugbar-collect') { $this->get('/debugbar-collect', 'collectDebugbar'); return; } if ($this->routeMode === 'redirect') { $this->get('/redirect-me', 'redirectMe'); return; } $this->get('/local/:id', 'local', ['id' => '[0-9]+']); } public function local(string $id): string { return 'local:' . $id; } public function priority(string $id): string { return 'local:' . $id; } public function withDefault(string $tab = 'overview'): string { return 'default:' . $tab; } public function collectDebugbar(): string { $this->getDebugBar()->getData(); return 'debugbar:collected'; } public function redirectMe(): Symfony\Component\HttpFoundation\Response { return $this->redirect('/target'); } #[Override] public function render($fileName, $variableArray = []): string { return 'error'; } } class BoxAppMaintenanceCheckApp extends Box_App { public function adminPrefixAllowed(): bool { return $this->checkAdminPrefix(); } public function pathAllowed(string $requestPath, string $allowedPath): bool { return $this->pathMatchesMaintenancePattern($requestPath, $allowedPath); } public function ipAllowed(string $visitorIP, array $allowedIPs): bool { return $this->ipMatchesMaintenanceAllowlist($visitorIP, $allowedIPs); } } function routeDispatchApp(string $routeMode, string $path): BoxAppRouteDispatchApp { $app = new BoxAppRouteDispatchApp($routeMode); $di = new Pimple\Container(); $di['request'] = Request::create('http://localhost' . $path); $di['shared_marker'] = 'available'; $di['url'] = new class { public function link(string $path): string { return '/' . ltrim($path, '/'); } }; $di['logger'] = new class { public function withChannel(string $channel): self { return $this; } public function info(string|Stringable $message, array $context = []): void { } }; $app->setDi($di); $app->setUrl($path); return $app; } test('app dispatches local route definitions with matched parameters', function (): void { $response = routeDispatchApp('local', '/local/42')->run(); expect($response->getStatusCode())->toBe(200) ->and($response->getContent())->toBe('local:42'); }); test('app preserves shared controller route priority over local routes', function (): void { $response = routeDispatchApp('shared-priority', '/priority/42')->run(); expect($response->getStatusCode())->toBe(200) ->and($response->getContent())->toBe('shared:42'); }); test('app injects the DI container into shared route controllers', function (): void { $response = routeDispatchApp('shared-di', '/shared-di')->run(); expect($response->getStatusCode())->toBe(200) ->and($response->getContent())->toBe('di:available'); }); test('app dispatch uses controller default arguments when route params are absent', function (): void { $response = routeDispatchApp('default-argument', '/default')->run(); expect($response->getStatusCode())->toBe(200) ->and($response->getContent())->toBe('default:overview'); }); test('app route mapping timing stops before debug bar data is collected during render', function (): void { $response = routeDispatchApp('debugbar-collect', '/debugbar-collect')->run(); expect($response->getStatusCode())->toBe(200) ->and($response->getContent())->toBe('debugbar:collected'); }); test('app shared route mapping timing stops before debug bar data is collected during render', function (): void { $response = routeDispatchApp('shared-debugbar-collect', '/shared-debugbar-collect')->run(); expect($response->getStatusCode())->toBe(200) ->and($response->getContent())->toBe('shared-debugbar:collected'); }); test('app redirect helper returns redirect responses from controllers', function (): void { $response = routeDispatchApp('redirect', '/redirect-me')->run(); expect($response->getStatusCode())->toBe(302) ->and($response->headers->get('Location'))->toBe('/target'); }); test('maintenance path allowlist uses literal prefixes and explicit wildcards', function (): void { $app = new BoxAppMaintenanceCheckApp(); expect($app->pathAllowed('/status', '/status'))->toBeTrue() ->and($app->pathAllowed('/status/deep', '/status'))->toBeTrue() ->and($app->pathAllowed('/status-page', '/status'))->toBeFalse() ->and($app->pathAllowed('/api/guest/staff/login', '/api/guest/staff/*'))->toBeTrue() ->and($app->pathAllowed('/docs/v10', '/docs/v1.0'))->toBeFalse() ->and($app->pathAllowed('/api/admin', ''))->toBeFalse(); }); test('maintenance mode recognizes the configured admin prefix', function (): void { $app = new BoxAppMaintenanceCheckApp(); $di = new Pimple\Container(); $di['request'] = Request::create(rtrim(SYSTEM_URL, '/') . '/' . ltrim(ADMIN_PREFIX, '/') . '/settings'); $app->setDi($di); expect($app->adminPrefixAllowed())->toBeFalse(); }); test('maintenance IP allowlist supports exact IPs, CIDR ranges, and IPv6', function (): void { $app = new BoxAppMaintenanceCheckApp(); expect($app->ipAllowed('203.0.113.10', ['203.0.113.10']))->toBeTrue() ->and($app->ipAllowed('203.0.113.55', ['203.0.113.0/24']))->toBeTrue() ->and($app->ipAllowed('2001:db8::1', ['2001:db8::/32']))->toBeTrue() ->and($app->ipAllowed('198.51.100.10', ['203.0.113.0/24']))->toBeFalse() ->and($app->ipAllowed('198.51.100.10', ['not-an-ip-range']))->toBeFalse(); });