serverBackup = $_SERVER; $_SERVER = []; RequestContext::resetForTests(); ApiAuth::authenticate(); } protected function tearDown(): void { $_SERVER = $this->serverBackup; RequestContext::resetForTests(); ApiAuth::authenticate(); } public function testItRecordsMutatingRequest(): void { $_SERVER['REQUEST_METHOD'] = 'POST'; $_SERVER['REQUEST_URI'] = '/api/v1/users/123'; $audit = $this->createMock(SystemAuditService::class); $audit->expects($this->once()) ->method('record') ->with( 'api.request', 'success', $this->callback(static function (array $context): bool { $metadata = is_array($context['metadata'] ?? null) ? $context['metadata'] : []; return ($metadata['endpoint_key'] ?? '') === '/api/v1/users/{id}' && ($metadata['status_code'] ?? null) === 201 && ($metadata['status_class'] ?? '') === '2xx' && ($metadata['auth_mode'] ?? '') === 'public' && ($metadata['write_method'] ?? null) === true && ($metadata['security_endpoint'] ?? null) === false; }) ); $reporter = new ApiSystemAuditReporter($audit); $reporter->start(); $reporter->finish(201); } public function testItSkipsNonSecurityGetSuccess(): void { $_SERVER['REQUEST_METHOD'] = 'GET'; $_SERVER['REQUEST_URI'] = '/api/v1/users'; $audit = $this->createMock(SystemAuditService::class); $audit->expects($this->never())->method('record'); $reporter = new ApiSystemAuditReporter($audit); $reporter->start(); $reporter->finish(200); } public function testItRecordsDeniedSecurityGetFailure(): void { $_SERVER['REQUEST_METHOD'] = 'GET'; $_SERVER['REQUEST_URI'] = '/api/v1/me/tokens'; $audit = $this->createMock(SystemAuditService::class); $audit->expects($this->once()) ->method('record') ->with( 'api.request', 'denied', $this->callback(static function (array $context): bool { $metadata = is_array($context['metadata'] ?? null) ? $context['metadata'] : []; return ($metadata['endpoint_key'] ?? '') === '/api/v1/me/tokens' && ($metadata['status_code'] ?? null) === 401 && ($metadata['status_class'] ?? '') === '4xx' && ($metadata['security_endpoint'] ?? null) === true; }) ); $reporter = new ApiSystemAuditReporter($audit); $reporter->start(); $reporter->finish(401, 'unauthorized'); } public function testItRecordsFailedServerError(): void { $_SERVER['REQUEST_METHOD'] = 'GET'; $_SERVER['REQUEST_URI'] = '/api/v1/users'; $audit = $this->createMock(SystemAuditService::class); $audit->expects($this->once()) ->method('record') ->with( 'api.request', 'failed', $this->callback(static function (array $context): bool { $metadata = is_array($context['metadata'] ?? null) ? $context['metadata'] : []; return ($metadata['status_code'] ?? null) === 500 && ($metadata['status_class'] ?? '') === '5xx' && ($metadata['write_method'] ?? null) === false; }) ); $reporter = new ApiSystemAuditReporter($audit); $reporter->start(); $reporter->finish(500, 'unexpected_error'); } public function testFinishIsIdempotent(): void { $_SERVER['REQUEST_METHOD'] = 'POST'; $_SERVER['REQUEST_URI'] = '/api/v1/tenants'; $audit = $this->createMock(SystemAuditService::class); $audit->expects($this->once())->method('record'); $reporter = new ApiSystemAuditReporter($audit); $reporter->start(); $reporter->finish(201); $reporter->finish(201); } }