Files
server/tests/php/Unit/Service/FirewallStatusServicesTest.php
2026-08-03 22:44:34 -04:00

122 lines
4.9 KiB
PHP

<?php
declare(strict_types=1);
namespace KTXT\Unit\Service;
use KTXC\Context\IdentityContextInterface;
use KTXC\Context\TenantContextInterface;
use KTXC\Models\Tenant\TenantConfiguration;
use KTXC\Service\FirewallStatusService;
use KTXC\Service\FirewallSettingsService;
use KTXC\Service\SystemFirewallLogService;
use KTXC\Service\SystemFirewallStatusService;
use KTXC\Service\TenantFirewallLogService;
use KTXC\Service\TenantFirewallStatusService;
use KTXC\Service\TenantService;
use KTXC\Stores\FirewallStore;
use KTXF\Event\EventDispatcherInterface;
use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations;
use PHPUnit\Framework\Attributes\TestDox;
use PHPUnit\Framework\TestCase;
#[AllowMockObjectsWithoutExpectations]
final class FirewallStatusServicesTest extends TestCase
{
#[TestDox('Tenant metrics and configuration derive their tenant context')]
public function testTenantStatus(): void
{
$store = $this->createMock(FirewallStore::class);
$tenant = $this->createStub(TenantContextInterface::class);
$tenant->method('requireIdentifier')->willReturn('tenant-a');
$tenant->method('configuration')->willReturn(
(new TenantConfiguration())->jsonDeserialize([
'firewall' => ['enabled' => false, 'maxAuthFailures' => 8],
])
);
$identity = $this->createStub(IdentityContextInterface::class);
$identity->method('hasPermission')->willReturn(true);
$store->expects(self::once())
->method('countBlockedRequests')
->with('tenant-a', self::isInstanceOf(\DateTimeImmutable::class))
->willReturn(7);
$service = new TenantFirewallStatusService(
new FirewallStatusService($store), $tenant, $identity, $this->settings()
);
self::assertSame(7, $service->metrics('2026-08-01T00:00:00+00:00')['blockedRequests']);
self::assertFalse($service->configuration()['enabled']);
self::assertSame(8, $service->configuration()['maxAuthFailures']);
}
#[TestDox('System metrics and maintenance status expose operational state')]
public function testSystemStatus(): void
{
$store = $this->createMock(FirewallStore::class);
$identity = $this->createStub(IdentityContextInterface::class);
$identity->method('hasPermission')->willReturn(true);
$store->expects(self::once())
->method('countSystemBlockedRequests')
->with('tenant-a', null)
->willReturn(12);
$store->method('maintenanceStatus')->willReturn(['status' => 'success']);
$service = new SystemFirewallStatusService(
new FirewallStatusService($store), $identity, $this->settings()
);
self::assertSame(12, $service->metrics('tenant-a')['blockedRequests']);
self::assertSame('success', $service->maintenanceStatus()['status']);
}
#[TestDox('Maintenance reports an explicit state before its first run')]
public function testNeverRunStatus(): void
{
$store = $this->createStub(FirewallStore::class);
self::assertSame('never_run', (new FirewallStatusService($store))->maintenanceStatus()['status']);
}
#[TestDox('Status reads require their dedicated permissions')]
public function testPermissions(): void
{
$store = $this->createMock(FirewallStore::class);
$identity = $this->createStub(IdentityContextInterface::class);
$identity->method('hasPermission')->willReturn(false);
$tenant = $this->createStub(TenantContextInterface::class);
$store->expects(self::never())->method('countBlockedRequests');
$store->expects(self::never())->method('maintenanceStatus');
try {
(new TenantFirewallStatusService(
new FirewallStatusService($store), $tenant, $identity, $this->settings()
))->metrics();
self::fail('Tenant metrics should be rejected.');
} catch (\RuntimeException $error) {
self::assertStringContainsString(TenantFirewallLogService::PERMISSION_READ, $error->getMessage());
}
$this->expectExceptionMessage(SystemFirewallStatusService::PERMISSION_MAINTENANCE_READ);
(new SystemFirewallStatusService(
new FirewallStatusService($store), $identity, $this->settings()
))->maintenanceStatus();
}
#[TestDox('Metrics reject invalid dates before querying storage')]
public function testMetricValidation(): void
{
$store = $this->createMock(FirewallStore::class);
$store->expects(self::never())->method('countSystemBlockedRequests');
$this->expectException(\InvalidArgumentException::class);
(new FirewallStatusService($store))->systemMetrics(null, 'not-a-date');
}
private function settings(): FirewallSettingsService
{
return new FirewallSettingsService(
$this->createStub(TenantService::class),
$this->createStub(EventDispatcherInterface::class)
);
}
}