store = $this->createMock(FirewallStore::class); $this->events = $this->createMock(EventDispatcherInterface::class); $this->manager = new FirewallRuleManager( $this->store, new FirewallRuleCache($this->store), $this->events ); } #[TestDox('The shared manager creates rules with the supplied scope and owner')] public function testScopeCreation(): void { $this->store->method('findExactIpRule')->willReturn(null); $this->store->expects($this->exactly(2)) ->method('depositRule') ->willReturnArgument(0); $tenant = $this->manager->blockIp( FirewallRuleScope::tenant('tenant-a'), '203.0.113.10', null, 'admin-a' ); $system = $this->manager->blockIp( FirewallRuleScope::system(), '203.0.113.11', null, 'system-admin' ); self::assertSame('tenant-a', $tenant->getTenantId()); self::assertSame(FirewallRuleObject::SCOPE_TENANT, $tenant->getScope()); self::assertNull($system->getTenantId()); self::assertSame(FirewallRuleObject::SCOPE_SYSTEM, $system->getScope()); } #[TestDox('Malformed rule values are rejected before persistence')] public function testValidation(): void { $this->store->expects($this->never())->method('depositRule'); $this->expectException(\InvalidArgumentException::class); $this->manager->blockIpRange( FirewallRuleScope::tenant('tenant-a'), '2001:db8::/129', null, 'admin-a' ); } #[TestDox('Temporary rules require a positive duration')] public function testDuration(): void { $this->store->expects($this->never())->method('depositRule'); $this->expectException(\InvalidArgumentException::class); $this->manager->blockIp( FirewallRuleScope::system(), '203.0.113.10', null, 'admin', 0 ); } #[TestDox('Rule lifecycle operations cannot cross scope ownership')] public function testOwnership(): void { $tenantRule = (new FirewallRuleObject()) ->setId('tenant-rule') ->setScope(FirewallRuleObject::SCOPE_TENANT) ->setTenantId('tenant-a'); $this->store->method('fetchRule')->willReturn($tenantRule); $this->store->expects($this->never())->method('destroyRule'); self::assertFalse($this->manager->remove(FirewallRuleScope::system(), 'tenant-rule')); self::assertFalse($this->manager->remove(FirewallRuleScope::tenant('tenant-b'), 'tenant-rule')); } #[TestDox('Rule mutations invalidate the shared enforcement cache')] public function testCacheInvalidation(): void { $this->store->expects($this->exactly(2)) ->method('listRules') ->with('tenant-a') ->willReturnOnConsecutiveCalls([], []); $cache = new FirewallRuleCache($this->store); $manager = new FirewallRuleManager($this->store, $cache, $this->events); $this->store->method('findExactIpRule')->willReturn(null); $this->store->method('depositRule')->willReturnArgument(0); self::assertSame([], $cache->tenant('tenant-a')); $manager->blockIp(FirewallRuleScope::tenant('tenant-a'), '203.0.113.10', null, 'admin'); self::assertSame([], $cache->tenant('tenant-a')); } }