store = $this->createMock(FirewallStore::class); $tenant = $this->createMock(TenantContextInterface::class); $tenant->method('requireIdentifier')->willReturn('tenant-a'); $identity = $this->createMock(IdentityContextInterface::class); $identity->method('hasPermission')->willReturn(true); $manager = new FirewallRuleManager( $this->store, new FirewallRuleCache($this->store), $this->createStub(EventDispatcherInterface::class) ); $this->controller = new FirewallController( new TenantFirewallRuleService($manager, $tenant, $identity), new SystemFirewallRuleService($manager, $identity), new TenantFirewallLogService(new FirewallLogService($this->store), $tenant, $identity), new SystemFirewallLogService(new FirewallLogService($this->store), $identity), new TenantFirewallStatusService(new FirewallStatusService($this->store), $tenant, $identity), new SystemFirewallStatusService(new FirewallStatusService($this->store), $identity) ); } #[TestDox('Tenant rule endpoint returns bounded paginated results')] public function testTenantRules(): void { $this->store->expects(self::once()) ->method('queryRules') ->with('tenant', 'tenant-a', 'active', null, null, 25, 10) ->willReturn(['items' => [], 'total' => 0, 'limit' => 25, 'offset' => 10]); $response = $this->controller->tenantRules(limit: '25', offset: '10'); $data = json_decode($response->getContent(), true, flags: JSON_THROW_ON_ERROR); self::assertSame(200, $response->getStatusCode()); self::assertSame(25, $data['limit']); self::assertSame(10, $data['offset']); } #[TestDox('Rule endpoints reject malformed and excessive pagination')] public function testPaginationValidation(): void { $this->store->expects(self::never())->method('queryRules'); self::assertSame(400, $this->controller->tenantRules(limit: 'invalid')->getStatusCode()); self::assertSame(400, $this->controller->systemRules(limit: '101')->getStatusCode()); } #[TestDox('Metric endpoints return scoped counts and stable validation errors')] public function testMetrics(): void { $this->store->method('countBlockedRequests')->willReturn(4); $response = $this->controller->tenantMetrics(); $data = json_decode($response->getContent(), true, flags: JSON_THROW_ON_ERROR); self::assertSame(4, $data['blockedRequests']); self::assertSame(400, $this->controller->systemMetrics(since: 'not-a-date')->getStatusCode()); } #[TestDox('Current-IP blocks return a structured confirmation conflict')] public function testCurrentIpConflict(): void { $this->store->expects(self::never())->method('depositRule'); $response = $this->controller->createTenantRule( new Request(server: ['REMOTE_ADDR' => '203.0.113.10']), 'ip', 'block', '203.0.113.10', 'Suspected abuse' ); $data = json_decode($response->getContent(), true, flags: JSON_THROW_ON_ERROR); self::assertSame(409, $response->getStatusCode()); self::assertSame('current_ip_confirmation_required', $data['error']['code']); } #[TestDox('Invalid manual rules return a stable validation response')] public function testMutationValidation(): void { $response = $this->controller->createSystemRule( new Request(server: ['REMOTE_ADDR' => '203.0.113.10']), 'device', 'allow', 'device-123', 'Trusted device' ); $data = json_decode($response->getContent(), true, flags: JSON_THROW_ON_ERROR); self::assertSame(400, $response->getStatusCode()); self::assertSame('invalid_firewall_rule', $data['error']['code']); } #[TestDox('Every rule endpoint declares its scope-specific read permission')] public function testRoutePermissions(): void { $expected = [ 'tenantRules' => TenantFirewallRuleService::PERMISSION_READ, 'tenantRule' => TenantFirewallRuleService::PERMISSION_READ, 'effectivePolicy' => TenantFirewallRuleService::PERMISSION_READ, 'systemRules' => SystemFirewallRuleService::PERMISSION_READ, 'systemRule' => SystemFirewallRuleService::PERMISSION_READ, 'tenantLogs' => TenantFirewallLogService::PERMISSION_READ, 'systemLogs' => SystemFirewallLogService::PERMISSION_READ, 'tenantMetrics' => TenantFirewallLogService::PERMISSION_READ, 'tenantConfiguration' => TenantFirewallStatusService::PERMISSION_SETTINGS_READ, 'systemMetrics' => SystemFirewallLogService::PERMISSION_READ, 'maintenanceStatus' => SystemFirewallStatusService::PERMISSION_MAINTENANCE_READ, 'createTenantRule' => TenantFirewallRuleService::PERMISSION_MANAGE, 'createSystemRule' => SystemFirewallRuleService::PERMISSION_MANAGE, ]; foreach ($expected as $method => $permission) { $attributes = (new \ReflectionMethod(FirewallController::class, $method)) ->getAttributes(AuthenticatedRoute::class); self::assertCount(1, $attributes); self::assertSame([$permission], $attributes[0]->newInstance()->permissions); } } }