Files
api/services/nginx/app/tests/Unit/Selfserve/EdgeGatewayViewCacheTest.php
T

220 lines
8.6 KiB
PHP

<?php
app_require('classes/edge_gateway_manager.php');
app_require('classes/edge_gateway_view_cache.php');
use classes\edge_gateway_manager;
use classes\edge_gateway_view_cache;
if (!class_exists('EdgeGatewayViewCacheRedisFake')) {
class EdgeGatewayViewCacheRedisFake
{
/** @var array<string,string> */
public array $store = [];
public function get(string $key): ?string
{
return $this->store[$key] ?? null;
}
public function setEx(string $key, string $value, int $ttl): void
{
$this->store[$key] = $value;
}
public function clear_keys(string $pattern): void
{
$regex = '/^' . str_replace('\*', '.*', preg_quote($pattern, '/')) . '$/';
foreach (array_keys($this->store) as $key) {
if (preg_match($regex, $key) === 1) {
unset($this->store[$key]);
}
}
}
}
}
beforeEach(function (): void {
$this->oldTtl = getenv('EDGE_GATEWAY_VIEW_CACHE_TTL');
putenv('EDGE_GATEWAY_VIEW_CACHE_TTL=15');
$this->redis = new EdgeGatewayViewCacheRedisFake();
edge_gateway_view_cache::setAdapterForTests($this->redis);
});
afterEach(function (): void {
edge_gateway_view_cache::setAdapterForTests(null);
if ($this->oldTtl === false) {
putenv('EDGE_GATEWAY_VIEW_CACHE_TTL');
return;
}
putenv('EDGE_GATEWAY_VIEW_CACHE_TTL=' . $this->oldTtl);
});
function edgeGatewayCacheGatewayRow(
int $gatewayId,
int $departmentId,
string $status,
array $inventorySummary = ['total' => 0, 'online' => 0, 'offline' => 0],
array $bindingSummary = ['total' => 0, 'fallback_overrides' => 0],
array $fallbackSummary = ['cloud_only_relays' => 0, 'local_only_relays' => 0]
): array {
return [
'id' => $gatewayId,
'department_id' => $departmentId,
'label' => 'Gateway ' . $gatewayId,
'status' => $status,
'version_drift' => ['is_drifted' => false],
'channel_status' => ['broker' => ['connected' => $status === edge_gateway_manager::STATUS_ONLINE]],
'active_operation' => null,
'recent_operations_summary' => ['pending' => 0, 'in_progress' => 0],
'backlog_depth' => ['operations' => 0, 'commands' => 0],
'metadata' => [
'system_metrics' => [
'latency_ms' => 100,
'cpu_usage_pct' => 10,
'memory_usage_pct' => 20,
'disk_usage_pct' => 30,
],
],
'inventory_summary' => $inventorySummary,
'binding_summary' => $bindingSummary,
'fallback_summary' => $fallbackSummary,
'inventory' => [],
'bindings' => [],
'recent_commands' => [],
'audit_logs' => [],
'operations' => [],
];
}
it('uses sane ttl defaults and deterministic cache keys', function (): void {
putenv('EDGE_GATEWAY_VIEW_CACHE_TTL');
expect(edge_gateway_view_cache::getTtl())->toBe(15);
putenv('EDGE_GATEWAY_VIEW_CACHE_TTL=25');
expect(edge_gateway_view_cache::getTtl())->toBe(25);
putenv('EDGE_GATEWAY_VIEW_CACHE_TTL=-5');
expect(edge_gateway_view_cache::getTtl())->toBe(0);
expect(edge_gateway_view_cache::listKey(null, false))->toBe('edge_gateway:view:v1:list:department:all:detail:0');
expect(edge_gateway_view_cache::detailKey(701))->toBe('edge_gateway:view:v1:detail:701');
});
it('stores and retrieves cached list and detail payloads', function (): void {
$gateway = edgeGatewayCacheGatewayRow(701, 1, edge_gateway_manager::STATUS_ONLINE, ['total' => 2, 'online' => 1, 'offline' => 1]);
$payload = [
'gateways' => [$gateway],
'fleet_usage' => edge_gateway_manager::summarizeFleetUsageFromGatewayRows([$gateway]),
];
edge_gateway_view_cache::storeListPayload(null, false, $payload, 30);
edge_gateway_view_cache::storeDetailPayload(701, $gateway, 30);
expect(edge_gateway_view_cache::getListPayload(null, false))->toBe($payload);
expect(edge_gateway_view_cache::getDetailPayload(701))->toBe($gateway);
});
it('syncs gateway snapshots into cached list payloads and refreshes fleet usage', function (): void {
$staleGateway = edgeGatewayCacheGatewayRow(
701,
1,
edge_gateway_manager::STATUS_OFFLINE,
['total' => 1, 'online' => 0, 'offline' => 1],
['total' => 1, 'fallback_overrides' => 0],
['cloud_only_relays' => 0, 'local_only_relays' => 0]
);
$otherGateway = edgeGatewayCacheGatewayRow(
702,
2,
edge_gateway_manager::STATUS_ONLINE,
['total' => 1, 'online' => 1, 'offline' => 0],
['total' => 1, 'fallback_overrides' => 1],
['cloud_only_relays' => 1, 'local_only_relays' => 0]
);
edge_gateway_view_cache::storeListPayload(null, false, [
'gateways' => [$staleGateway, $otherGateway],
'fleet_usage' => edge_gateway_manager::summarizeFleetUsageFromGatewayRows([$staleGateway, $otherGateway]),
]);
edge_gateway_view_cache::storeListPayload(1, false, [
'gateways' => [$staleGateway],
'fleet_usage' => edge_gateway_manager::summarizeFleetUsageFromGatewayRows([$staleGateway]),
]);
$freshGateway = [
'id' => 701,
'department_id' => 1,
'label' => 'Gateway 701',
'status' => edge_gateway_manager::STATUS_ONLINE,
'version_drift' => ['is_drifted' => false],
'channel_status' => ['broker' => ['connected' => true]],
'active_operation' => null,
'recent_operations_summary' => ['pending' => 0, 'in_progress' => 0],
'backlog_depth' => ['operations' => 0, 'commands' => 0],
'metadata' => [
'system_metrics' => [
'latency_ms' => 150,
'cpu_usage_pct' => 15,
'memory_usage_pct' => 25,
'disk_usage_pct' => 35,
],
],
'inventory' => [
['id' => 1, 'online' => true],
['id' => 2, 'online' => true],
],
'bindings' => [
['id' => 1, 'fallback_mode' => edge_gateway_manager::RELAY_FALLBACK_PREFER_LOCAL],
['id' => 2, 'fallback_mode' => edge_gateway_manager::RELAY_FALLBACK_CLOUD_ONLY],
],
'recent_commands' => [],
'audit_logs' => [],
'operations' => [],
'fallback_summary' => ['cloud_only_relays' => 1, 'local_only_relays' => 0],
];
edge_gateway_view_cache::syncGateway($freshGateway);
$allGatewaysPayload = edge_gateway_view_cache::getListPayload(null, false);
$departmentPayload = edge_gateway_view_cache::getListPayload(1, false);
$detailPayload = edge_gateway_view_cache::getDetailPayload(701);
expect($detailPayload)->not->toBeNull();
expect($detailPayload['inventory_summary'])->toBe(['total' => 2, 'online' => 2, 'offline' => 0]);
expect($detailPayload['binding_summary'])->toBe(['total' => 2, 'fallback_overrides' => 1]);
expect($allGatewaysPayload)->not->toBeNull();
expect($allGatewaysPayload['gateways'][0]['status'])->toBe(edge_gateway_manager::STATUS_ONLINE);
expect($allGatewaysPayload['gateways'][0]['inventory'])->toBe([]);
expect($allGatewaysPayload['fleet_usage']['gateways']['online'])->toBe(2);
expect($allGatewaysPayload['fleet_usage']['inventory']['online'])->toBe(3);
expect($allGatewaysPayload['fleet_usage']['bindings']['cloud_only'])->toBe(2);
expect($departmentPayload)->not->toBeNull();
expect($departmentPayload['gateways'][0]['status'])->toBe(edge_gateway_manager::STATUS_ONLINE);
expect($departmentPayload['fleet_usage']['inventory']['online'])->toBe(2);
});
it('removes deleted gateways from cached list and detail payloads', function (): void {
$gatewayA = edgeGatewayCacheGatewayRow(701, 1, edge_gateway_manager::STATUS_ONLINE);
$gatewayB = edgeGatewayCacheGatewayRow(702, 1, edge_gateway_manager::STATUS_OFFLINE);
$payload = [
'gateways' => [$gatewayA, $gatewayB],
'fleet_usage' => edge_gateway_manager::summarizeFleetUsageFromGatewayRows([$gatewayA, $gatewayB]),
];
edge_gateway_view_cache::storeListPayload(null, false, $payload);
edge_gateway_view_cache::storeListPayload(1, false, $payload);
edge_gateway_view_cache::storeDetailPayload(701, $gatewayA);
edge_gateway_view_cache::removeGateway(701, 1);
expect(edge_gateway_view_cache::getDetailPayload(701))->toBeNull();
expect(edge_gateway_view_cache::getListPayload(null, false)['gateways'])->toHaveCount(1);
expect(edge_gateway_view_cache::getListPayload(1, false)['gateways'])->toHaveCount(1);
expect(edge_gateway_view_cache::getListPayload(null, false)['fleet_usage']['gateways']['total'])->toBe(1);
});