Files
api/services/nginx/app/tests/Unit/Workfeed/DepartmentWeatherCacheHelpersTest.php
T
Jeppe B 2a6a86c9c3 Resolve backend Qodana critical and high findings (#314)
Resolve recommended-profile Critical and High findings, retain narrow analyzer exceptions, and update the edge-broker WebSocket dependency to a non-vulnerable release.
2026-07-17 05:44:16 +02:00

156 lines
5.8 KiB
PHP

<?php
app_require('routes/moduleWeatherAPIRoute.php');
use routes\moduleWeatherAPIRoute;
function weather_cache_invoke_private(moduleWeatherAPIRoute $route, string $method, array $args = []): mixed
{
$reflection = new ReflectionClass($route);
$target = $reflection->getMethod($method);
return $target->invokeArgs($route, $args);
}
beforeEach(function (): void {
$_SERVER['REQUEST_URI'] = '/departments/weather';
$this->oldTtl = getenv('DEPARTMENTS_WEATHER_CACHE_TTL');
$this->oldStaleTtl = getenv('DEPARTMENTS_WEATHER_STALE_TTL');
$this->oldHotTtl = getenv('DEPARTMENTS_WEATHER_PRELOAD_HOT_TTL');
});
afterEach(function (): void {
if ($this->oldTtl === false) {
putenv('DEPARTMENTS_WEATHER_CACHE_TTL');
} else {
putenv('DEPARTMENTS_WEATHER_CACHE_TTL=' . $this->oldTtl);
}
if ($this->oldStaleTtl === false) {
putenv('DEPARTMENTS_WEATHER_STALE_TTL');
} else {
putenv('DEPARTMENTS_WEATHER_STALE_TTL=' . $this->oldStaleTtl);
}
if ($this->oldHotTtl === false) {
putenv('DEPARTMENTS_WEATHER_PRELOAD_HOT_TTL');
} else {
putenv('DEPARTMENTS_WEATHER_PRELOAD_HOT_TTL=' . $this->oldHotTtl);
}
});
it('uses sane ttl defaults and clamps negative ttl to zero', function (): void {
$route = new moduleWeatherAPIRoute();
putenv('DEPARTMENTS_WEATHER_CACHE_TTL');
expect(weather_cache_invoke_private($route, 'getDepartmentWeatherCacheTtl'))->toBe(60);
putenv('DEPARTMENTS_WEATHER_CACHE_TTL=45');
expect(weather_cache_invoke_private($route, 'getDepartmentWeatherCacheTtl'))->toBe(45);
putenv('DEPARTMENTS_WEATHER_CACHE_TTL=-10');
expect(weather_cache_invoke_private($route, 'getDepartmentWeatherCacheTtl'))->toBe(0);
});
it('uses sane stale ttl defaults and clamps stale ttl below fresh ttl', function (): void {
$route = new moduleWeatherAPIRoute();
putenv('DEPARTMENTS_WEATHER_STALE_TTL');
expect(weather_cache_invoke_private($route, 'getDepartmentWeatherStaleCacheTtl', [60]))->toBe(300);
putenv('DEPARTMENTS_WEATHER_STALE_TTL=10');
expect(weather_cache_invoke_private($route, 'getDepartmentWeatherStaleCacheTtl', [60]))->toBe(60);
putenv('DEPARTMENTS_WEATHER_STALE_TTL=900');
expect(weather_cache_invoke_private($route, 'getDepartmentWeatherStaleCacheTtl', [60]))->toBe(900);
});
it('uses sane hot activity ttl defaults and clamps to a positive value', function (): void {
$route = new moduleWeatherAPIRoute();
putenv('DEPARTMENTS_WEATHER_PRELOAD_HOT_TTL');
expect(weather_cache_invoke_private($route, 'getDepartmentWeatherHotActivityTtl'))->toBe(900);
putenv('DEPARTMENTS_WEATHER_PRELOAD_HOT_TTL=-5');
expect(weather_cache_invoke_private($route, 'getDepartmentWeatherHotActivityTtl'))->toBe(1);
});
it('builds deterministic cache keys for department sets and timeline ranges', function (): void {
$route = new moduleWeatherAPIRoute();
$range = [
'start' => new DateTime('2026-03-24 00:00:00'),
'endExclusive' => new DateTime('2026-03-25 00:00:00'),
];
$differentRange = [
'start' => new DateTime('2026-03-24 00:00:00'),
'endExclusive' => new DateTime('2026-03-26 00:00:00'),
];
$keyA = weather_cache_invoke_private($route, 'getDepartmentWeatherCacheKey', [[1, 3, 5], $range]);
$keyB = weather_cache_invoke_private($route, 'getDepartmentWeatherCacheKey', [[1, 3, 5], $range]);
$keyC = weather_cache_invoke_private($route, 'getDepartmentWeatherCacheKey', [[1, 3, 5], $differentRange]);
expect($keyA)->toBe($keyB);
expect($keyA)->not->toBe($keyC);
expect($keyA)->toStartWith('departments_weather:timeline:v2:');
});
it('builds order-insensitive cache keys for equivalent department id sets', function (): void {
$route = new moduleWeatherAPIRoute();
$range = [
'start' => new DateTime('2026-03-24 00:00:00'),
'endExclusive' => new DateTime('2026-03-25 00:00:00'),
];
$ordered = weather_cache_invoke_private($route, 'getDepartmentWeatherCacheKey', [[1, 3, 5], $range]);
$shuffled = weather_cache_invoke_private($route, 'getDepartmentWeatherCacheKey', [[5, 1, 3], $range]);
expect($ordered)->toBe($shuffled);
});
it('changes weather timeline cache key when department weather targets change', function (): void {
$route = new moduleWeatherAPIRoute();
$range = [
'start' => new DateTime('2026-03-24 00:00:00'),
'endExclusive' => new DateTime('2026-03-25 00:00:00'),
];
$baseTargets = [
1 => ['degraded_threshold' => 1.0, 'healthy_threshold' => 1.3],
3 => ['degraded_threshold' => 1.0, 'healthy_threshold' => 1.3],
];
$updatedTargets = [
1 => ['degraded_threshold' => 1.0, 'healthy_threshold' => 1.4],
3 => ['degraded_threshold' => 1.0, 'healthy_threshold' => 1.3],
];
$baseKey = weather_cache_invoke_private($route, 'getDepartmentWeatherCacheKey', [[1, 3], $range, $baseTargets]);
$sameKey = weather_cache_invoke_private($route, 'getDepartmentWeatherCacheKey', [[3, 1], $range, $baseTargets]);
$updatedKey = weather_cache_invoke_private($route, 'getDepartmentWeatherCacheKey', [[1, 3], $range, $updatedTargets]);
expect($baseKey)->toBe($sameKey);
expect($baseKey)->not->toBe($updatedKey);
});
it('falls back to resolver directly when cache ttl is disabled', function (): void {
$route = new moduleWeatherAPIRoute();
putenv('DEPARTMENTS_WEATHER_CACHE_TTL=0');
$calls = 0;
$result = weather_cache_invoke_private($route, 'withCachedDepartmentWeatherTimeline', [
[1, 3, 5],
[
'start' => new DateTime('2026-03-24 00:00:00'),
'endExclusive' => new DateTime('2026-03-25 00:00:00'),
],
static function () use (&$calls): array {
$calls++;
return ['ok' => true];
},
]);
expect($result)->toBe(['ok' => true]);
expect($calls)->toBe(1);
});