feat(firewall): add operational status reads
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
<?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\SystemFirewallLogService;
|
||||
use KTXC\Service\SystemFirewallStatusService;
|
||||
use KTXC\Service\TenantFirewallLogService;
|
||||
use KTXC\Service\TenantFirewallStatusService;
|
||||
use KTXC\Stores\FirewallStore;
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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))->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))->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');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user