container = new DefaultContainer(); // Setup stub config $this->config = createStubConfig(debug: false); }); afterEach(function () { Mockery::close(); }); it('integrates namespace blocking with IP restrictions', function () { // Real-world scenario: Admin area with IP restrictions AND namespace blocking $namespaceConfig = [ 'App\Application\Admin\*' => [ 'visibility' => 'admin', // IP-restricted to admin IPs 'access_policy' => NamespaceAccessPolicy::blockedExcept( 'App\Application\Admin\LoginController', 'App\Application\Admin\HealthController' ), ], ]; $service = new RouteAuthorizationService($this->config, $namespaceConfig); // Test 1: Login should be accessible from any IP (in allowlist) $loginRequest = createHttpRequest('192.168.1.100', '/admin/login'); $loginRoute = createRouteContext('App\Application\Admin\LoginController', '/admin/login'); expect(fn () => $service->authorize($loginRequest, $loginRoute)) ->not->toThrow(RouteNotFound::class); // Test 2: Dashboard blocked (not in allowlist) $dashboardRequest = createHttpRequest('192.168.1.100', '/admin/dashboard'); $dashboardRoute = createRouteContext('App\Application\Admin\Dashboard', '/admin/dashboard'); expect(fn () => $service->authorize($dashboardRequest, $dashboardRoute)) ->toThrow(RouteNotFound::class); // Test 3: Health controller allowed (in allowlist) $healthRequest = createHttpRequest('10.0.0.5', '/admin/health'); $healthRoute = createRouteContext('App\Application\Admin\HealthController', '/admin/health'); expect(fn () => $service->authorize($healthRequest, $healthRoute)) ->not->toThrow(RouteNotFound::class); }); it('handles multiple namespace configurations independently', function () { $namespaceConfig = [ // Admin: Completely blocked 'App\Application\Admin\*' => [ 'access_policy' => NamespaceAccessPolicy::blocked(), ], // API: Blocked except health endpoint 'App\Application\Api\*' => [ 'access_policy' => NamespaceAccessPolicy::blockedExcept( 'App\Application\Api\HealthController' ), ], // Web: No restrictions 'App\Application\Web\*' => [ 'visibility' => 'public', ], ]; $service = new RouteAuthorizationService($this->config, $namespaceConfig); $request = createHttpRequest('127.0.0.1', '/test'); // Admin: All blocked $adminRoute = createRouteContext('App\Application\Admin\Dashboard', '/admin/dashboard'); expect(fn () => $service->authorize($request, $adminRoute)) ->toThrow(RouteNotFound::class); // API: Health allowed $apiHealthRoute = createRouteContext('App\Application\Api\HealthController', '/api/health'); expect(fn () => $service->authorize($request, $apiHealthRoute)) ->not->toThrow(RouteNotFound::class); // API: Users blocked $apiUsersRoute = createRouteContext('App\Application\Api\UsersController', '/api/users'); expect(fn () => $service->authorize($request, $apiUsersRoute)) ->toThrow(RouteNotFound::class); // Web: Allowed $webRoute = createRouteContext('App\Application\Web\HomeController', '/'); expect(fn () => $service->authorize($request, $webRoute)) ->not->toThrow(RouteNotFound::class); }); it('production-like configuration scenario', function () { // Realistic production setup $namespaceConfig = [ // Internal tools - completely locked down 'App\Application\Internal\*' => [ 'visibility' => 'admin', 'access_policy' => NamespaceAccessPolicy::blocked(), ], // Admin - IP restricted, only public endpoints accessible 'App\Application\Admin\*' => [ 'visibility' => 'admin', 'access_policy' => NamespaceAccessPolicy::blockedExcept( 'App\Application\Admin\LoginController' ), ], // API - public but monitored 'App\Application\Api\*' => [ 'visibility' => 'public', // No access_policy - all API routes accessible ], ]; $service = new RouteAuthorizationService($this->config, $namespaceConfig); // Public user accessing different areas $publicRequest = createHttpRequest('93.184.216.34', '/test'); // Internal: Blocked (even from public IP, namespace blocked + IP restricted) $internalRoute = createRouteContext('App\Application\Internal\ToolsController', '/internal/tools'); expect(fn () => $service->authorize($publicRequest, $internalRoute)) ->toThrow(RouteNotFound::class); // Admin Login: Allowed (in allowlist, IP restriction doesn't apply to allowlist) $adminLoginRoute = createRouteContext('App\Application\Admin\LoginController', '/admin/login'); expect(fn () => $service->authorize($publicRequest, $adminLoginRoute)) ->not->toThrow(RouteNotFound::class); // Admin Dashboard: Blocked (not in allowlist) $adminDashboardRoute = createRouteContext('App\Application\Admin\Dashboard', '/admin/dashboard'); expect(fn () => $service->authorize($publicRequest, $adminDashboardRoute)) ->toThrow(RouteNotFound::class); // API: Allowed (public visibility, no namespace blocking) $apiRoute = createRouteContext('App\Application\Api\UsersController', '/api/users'); expect(fn () => $service->authorize($publicRequest, $apiRoute)) ->not->toThrow(RouteNotFound::class); }); it('handles deeply nested namespaces correctly', function () { $namespaceConfig = [ 'App\Application\Admin\*' => [ 'access_policy' => NamespaceAccessPolicy::blockedExcept( 'App\Application\Admin\Auth\LoginController' ), ], ]; $service = new RouteAuthorizationService($this->config, $namespaceConfig); $request = createHttpRequest('127.0.0.1', '/test'); // Deeply nested allowed controller $loginRoute = createRouteContext( 'App\Application\Admin\Auth\LoginController', '/admin/auth/login' ); expect(fn () => $service->authorize($request, $loginRoute)) ->not->toThrow(RouteNotFound::class); // Deeply nested blocked controller $userRoute = createRouteContext( 'App\Application\Admin\Users\Management\UserController', '/admin/users/management' ); expect(fn () => $service->authorize($request, $userRoute)) ->toThrow(RouteNotFound::class); }); it('handles no namespace configuration gracefully', function () { // Service with empty config should allow everything $service = new RouteAuthorizationService($this->config, []); $request = createHttpRequest('127.0.0.1', '/test'); $routes = [ createRouteContext('App\Application\Admin\Dashboard', '/admin/dashboard'), createRouteContext('App\Application\Api\UsersController', '/api/users'), createRouteContext('App\Application\Web\HomeController', '/'), ]; foreach ($routes as $route) { expect(fn () => $service->authorize($request, $route)) ->not->toThrow(RouteNotFound::class); } }); }); // Helper functions function createStubConfig(bool $debug = false): TypedConfiguration { return new class ($debug) extends TypedConfiguration { public function __construct(bool $debug) { $this->app = (object)['debug' => $debug]; } }; } function createHttpRequest(string $ipAddress, string $path): HttpRequest { $server = Mockery::mock('server'); $server->shouldReceive('getClientIp') ->andReturn(IpAddress::fromString($ipAddress)); $request = Mockery::mock(HttpRequest::class); $request->server = $server; $request->path = $path; $request->method = Method::GET; return $request; } function createRouteContext(string $controllerClass, string $path): RouteContext { $route = (object)[ 'controller' => $controllerClass, 'action' => 'index', 'attributes' => [], ]; $match = (object)['route' => $route]; $routeContext = Mockery::mock(RouteContext::class); $routeContext->match = $match; $routeContext->path = $path; $routeContext->shouldReceive('isSuccess')->andReturn(true); return $routeContext; }