dispatch(Role::Admin, $this->principal(Role::Admin))); } public function testOperatorPrincipalSatisfiesOperatorRoute(): void { self::assertSame(200, $this->dispatch(Role::Operator, $this->principal(Role::Operator))); } public function testViewerPrincipalForbiddenFromOperatorRoute(): void { self::assertSame(403, $this->dispatch(Role::Operator, $this->principal(Role::Viewer))); } public function testOperatorPrincipalForbiddenFromAdminRoute(): void { self::assertSame(403, $this->dispatch(Role::Admin, $this->principal(Role::Operator))); } public function testReporterPrincipalUnauthorizedOnAnyAdminRoute(): void { $principal = new AuthenticatedPrincipal( tokenKind: TokenKind::Reporter, userId: null, role: null, reporterId: 1, consumerId: null, tokenId: 1, ); self::assertSame(401, $this->dispatch(Role::Viewer, $principal)); } public function testServicePrincipalWithoutRoleForbidden(): void { // ImpersonationMiddleware would normally have populated role; if it // didn't (e.g. someone mounted an admin route without it), we expect // a defensive 403 rather than a misleading 200. $principal = new AuthenticatedPrincipal( tokenKind: TokenKind::Service, userId: null, role: null, reporterId: null, consumerId: null, tokenId: 1, ); self::assertSame(403, $this->dispatch(Role::Viewer, $principal)); } public function testMissingPrincipalReturns401(): void { $factory = new ResponseFactory(); $middleware = RbacMiddleware::require($factory, Role::Viewer); $request = (new ServerRequestFactory())->createServerRequest('GET', '/x'); $handler = new class () implements RequestHandlerInterface { public function handle(ServerRequestInterface $request): ResponseInterface { return new Response(); } }; $response = $middleware->process($request, $handler); self::assertSame(401, $response->getStatusCode()); } private function principal(Role $role): AuthenticatedPrincipal { return new AuthenticatedPrincipal( tokenKind: TokenKind::Admin, userId: 42, role: $role, reporterId: null, consumerId: null, tokenId: 7, ); } private function dispatch(Role $required, AuthenticatedPrincipal $principal): int { $factory = new ResponseFactory(); $middleware = RbacMiddleware::require($factory, $required); $request = (new ServerRequestFactory()) ->createServerRequest('GET', '/x') ->withAttribute(TokenAuthenticationMiddleware::ATTR_PRINCIPAL, $principal); $handler = new class () implements RequestHandlerInterface { public function handle(ServerRequestInterface $request): ResponseInterface { return new Response(); } }; return $middleware->process($request, $handler)->getStatusCode(); } }