Files
server/tests/php/Unit/Service/FirewallSettingsServiceTest.php
T
2026-08-05 23:56:28 -04:00

87 lines
3.5 KiB
PHP

<?php
declare(strict_types=1);
namespace KTXT\Unit\Service;
use KTXC\Models\Tenant\TenantConfiguration;
use KTXC\Models\Tenant\TenantObject;
use KTXC\Service\FirewallSettingsService;
use KTXC\Service\TenantService;
use KTXF\Event\EventDispatcherInterface;
use KTXC\Security\Event\FirewallSettingsUpdatedEvent;
use PHPUnit\Framework\Attributes\TestDox;
use PHPUnit\Framework\TestCase;
final class FirewallSettingsServiceTest extends TestCase
{
#[TestDox('Settings updates preserve other tenant configuration and emit complete audit context')]
public function testUpdate(): void
{
$tenant = (new TenantObject())
->setId('507f1f77bcf86cd799439011')
->setIdentifier('tenant-a')
->setConfiguration((new TenantConfiguration())->jsonDeserialize([
'firewall' => ['enabled' => true, 'maxAuthFailures' => 5],
'security' => ['code' => 'preserved'],
]));
$tenants = $this->createMock(TenantService::class);
$tenants->method('fetchById')->with('tenant-a')->willReturn($tenant);
$tenants->expects(self::once())
->method('deposit')
->with(self::callback(static fn(TenantObject $updated): bool =>
$updated->getConfiguration()->firewall()->maxAuthFailures() === 8
&& $updated->getConfiguration()->security()->jsonSerialize()['code'] === 'preserved'
))
->willReturnArgument(0);
$events = $this->createMock(EventDispatcherInterface::class);
$events->expects(self::once())
->method('dispatch')
->with(self::callback(static fn(FirewallSettingsUpdatedEvent $event): bool =>
$event->getName() === FirewallSettingsUpdatedEvent::class
&& $event->getTenantId() === 'tenant-a'
&& $event->getIdentityId() === 'admin-a'
&& $event->get('changeReason') === 'Tighten authentication controls'
&& $event->get('previous')['maxAuthFailures'] === 5
&& $event->get('current')['maxAuthFailures'] === 8
));
$result = (new FirewallSettingsService($tenants, $events))->update(
'tenant-a', false, 8, 600, 7200, 'Tighten authentication controls', 'admin-a'
);
self::assertSame([
'enabled' => false,
'maxAuthFailures' => 8,
'authFailureWindow' => 600,
'autoBlockDuration' => 7200,
], $result);
}
#[TestDox('Invalid settings are rejected before tenant reads or persistence')]
public function testValidation(): void
{
$tenants = $this->createMock(TenantService::class);
$tenants->expects(self::never())->method('fetchById');
$tenants->expects(self::never())->method('deposit');
$this->expectException(\InvalidArgumentException::class);
(new FirewallSettingsService(
$tenants,
$this->createStub(EventDispatcherInterface::class)
))->update('tenant-a', true, 0, 300, 3600, 'Invalid threshold', 'admin-a');
}
#[TestDox('Unknown tenants return no configuration without emitting audit events')]
public function testMissingTenant(): void
{
$tenants = $this->createStub(TenantService::class);
$events = $this->createMock(EventDispatcherInterface::class);
$events->expects(self::never())->method('dispatch');
self::assertNull((new FirewallSettingsService($tenants, $events))->update(
'missing', true, 5, 300, 3600, 'Apply defaults', 'admin-a'
));
}
}