request('GET', '/api/v1/admin/me'); self::assertSame(401, $response->getStatusCode()); self::assertSame('unauthorized', $this->decode($response)['error']); } public function testBadlyFormattedTokenReturns401(): void { $response = $this->request('GET', '/api/v1/admin/me', [ 'Authorization' => 'Bearer not_a_real_token', ]); self::assertSame(401, $response->getStatusCode()); } public function testWellFormedButUnknownTokenReturns401(): void { // Right format, never persisted → no DB hit on lookup. $raw = 'irdb_adm_ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; $response = $this->request('GET', '/api/v1/admin/me', [ 'Authorization' => 'Bearer ' . $raw, ]); self::assertSame(401, $response->getStatusCode()); } public function testReporterTokenOnAdminRouteReturns401(): void { $reporterId = $this->createReporter(); $token = $this->createToken(TokenKind::Reporter, reporterId: $reporterId); $response = $this->request('GET', '/api/v1/admin/me', [ 'Authorization' => 'Bearer ' . $token, ]); self::assertSame(401, $response->getStatusCode()); } public function testAdminTokenWithViewerRoleSatisfiesViewerRoute(): void { $token = $this->createToken(TokenKind::Admin, role: Role::Viewer); $response = $this->request('GET', '/api/v1/admin/me', [ 'Authorization' => 'Bearer ' . $token, ]); self::assertSame(200, $response->getStatusCode()); $body = $this->decode($response); self::assertSame('viewer', $body['role']); self::assertSame('admin-token', $body['source']); self::assertNull($body['user_id']); } public function testAdminTokenWithAdminRoleSatisfiesAdminRoute(): void { $token = $this->createToken(TokenKind::Admin, role: Role::Admin); // /admin/me requires Viewer; an Admin role satisfies it. $response = $this->request('GET', '/api/v1/admin/me', [ 'Authorization' => 'Bearer ' . $token, ]); self::assertSame(200, $response->getStatusCode()); self::assertSame('admin', $this->decode($response)['role']); } public function testServiceTokenWithoutImpersonationHeaderReturns400(): void { $token = $this->createToken(TokenKind::Service); $response = $this->request('GET', '/api/v1/admin/me', [ 'Authorization' => 'Bearer ' . $token, ]); self::assertSame(400, $response->getStatusCode()); self::assertSame('missing X-Acting-User-Id', $this->decode($response)['error']); } public function testServiceTokenWithUnknownUserReturns403(): void { $token = $this->createToken(TokenKind::Service); $response = $this->request('GET', '/api/v1/admin/me', [ 'Authorization' => 'Bearer ' . $token, 'X-Acting-User-Id' => '99999', ]); self::assertSame(403, $response->getStatusCode()); } public function testServiceTokenWithMalformedImpersonationHeaderReturns400(): void { $token = $this->createToken(TokenKind::Service); $response = $this->request('GET', '/api/v1/admin/me', [ 'Authorization' => 'Bearer ' . $token, 'X-Acting-User-Id' => 'not-an-int', ]); self::assertSame(400, $response->getStatusCode()); } public function testServiceTokenImpersonatingViewerReachesViewerRoute(): void { $token = $this->createToken(TokenKind::Service); $userId = $this->createUser(Role::Viewer, isLocal: false, subject: 'sub-viewer'); $response = $this->request('GET', '/api/v1/admin/me', [ 'Authorization' => 'Bearer ' . $token, 'X-Acting-User-Id' => (string) $userId, ]); self::assertSame(200, $response->getStatusCode()); $body = $this->decode($response); self::assertSame($userId, $body['user_id']); self::assertSame('viewer', $body['role']); self::assertSame('oidc', $body['source']); self::assertFalse($body['is_local']); } public function testServiceTokenImpersonatingAdminReachesAdminRoute(): void { $token = $this->createToken(TokenKind::Service); $userId = $this->createUser(Role::Admin, isLocal: true); $response = $this->request('GET', '/api/v1/admin/me', [ 'Authorization' => 'Bearer ' . $token, 'X-Acting-User-Id' => (string) $userId, ]); self::assertSame(200, $response->getStatusCode()); $body = $this->decode($response); self::assertSame('admin', $body['role']); self::assertTrue($body['is_local']); self::assertSame('local', $body['source']); } public function testActingUserHeaderIgnoredForAdminToken(): void { // Per SPEC §8: X-Acting-User-Id is *only* trusted with the service token. // For an admin token it must not be 400'd, just ignored. $token = $this->createToken(TokenKind::Admin, role: Role::Viewer); $response = $this->request('GET', '/api/v1/admin/me', [ 'Authorization' => 'Bearer ' . $token, 'X-Acting-User-Id' => '99999', ]); self::assertSame(200, $response->getStatusCode()); self::assertSame('admin-token', $this->decode($response)['source']); } }