Resolve recommended-profile Critical and High findings, retain narrow analyzer exceptions, and update the edge-broker WebSocket dependency to a non-vulnerable release.
156 lines
5.8 KiB
PHP
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);
|
|
});
|