Files
server/tests/php/Unit/Service/FirewallRuleManagerTest.php
T
2026-07-30 22:35:24 -04:00

110 lines
4.1 KiB
PHP

<?php
declare(strict_types=1);
namespace KTXT\Unit\Service;
use KTXC\Models\Firewall\FirewallRuleObject;
use KTXC\Service\FirewallRuleCache;
use KTXC\Service\FirewallRuleManager;
use KTXC\Service\FirewallRuleScope;
use KTXC\Stores\FirewallStore;
use KTXF\Event\EventDispatcherInterface;
use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations;
use PHPUnit\Framework\Attributes\TestDox;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
#[AllowMockObjectsWithoutExpectations]
class FirewallRuleManagerTest extends TestCase
{
private FirewallStore&MockObject $store;
private EventDispatcherInterface&MockObject $events;
private FirewallRuleManager $manager;
protected function setUp(): void
{
$this->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('listApplicableRules')
->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->applicable('tenant-a'));
$manager->blockIp(FirewallRuleScope::tenant('tenant-a'), '203.0.113.10', null, 'admin');
self::assertSame([], $cache->applicable('tenant-a'));
}
}