Make metrics reflect autoscaling policies (Refs #137)
CI / checks (push) Successful in 4m49s

This commit is contained in:
2026-10-10 14:48:52 -04:00
parent bd34c3c9e7
commit 33da4482a6
24 changed files with 1264 additions and 370 deletions
+67 -1
View File
@@ -8,6 +8,7 @@ const report = (days: number) => ({
now: 1000,
since: 0,
summary: totals,
running: { average: null, peak: 0, capturedHours: 0 },
points: [],
maps: [],
servers: [],
@@ -22,12 +23,20 @@ const report = (days: number) => ({
since: -1000,
until: 0,
summary: totals,
running: { average: null, peak: 0, capturedHours: 0 },
firstSampleAt: null,
lastSampleAt: null,
partialHistory: true
},
heatmap: { timeZone: 'UTC', cells: [] },
pooledPressure: { value: null, fullMapHours: 0, knownMapHours: 0, coveredMaps: 0, totalMaps: 0 }
pooledAvailability: {
value: null,
noEmptyMapHours: 0,
knownMapHours: 0,
unknownMapHours: 0,
coveredMaps: 0,
totalMaps: 0
}
});
function setup(fetcher: typeof fetch) {
let query = 'days=7';
@@ -124,6 +133,63 @@ describe('metrics background refresh', () => {
expect(refresh.onLoading).toHaveBeenLastCalledWith(false);
});
it('accepts availability reports and rejects incomplete running or policy evidence', async () => {
const value = {
...report(7),
maps: [
{
...totals,
game: 't5',
map: 'Moon',
noEmpty: 0,
availabilityObserved: 0,
availabilityUnknown: 1,
noEmptyAtMaximum: 0,
noEmptyBelowMaximum: 0,
policy: {
id: 'moon',
mode: 'create-destroy',
minimum: 1,
maximum: 3,
spares: 1,
pausedReason: null
}
}
],
points: [
{
...totals,
at: 0,
startAt: 0,
endAt: 1000,
partial: true,
capturedHours: 1 / 3600
}
]
};
const invalid = [
{ ...value, running: undefined },
{ ...value, previous: { ...value.previous, running: undefined } },
{ ...value, maps: [{ ...value.maps[0], policy: { id: 'moon' } }] },
{ ...value, maps: [{ ...value.maps[0], availabilityUnknown: undefined }] },
{ ...value, points: [{ ...value.points[0], capturedHours: undefined }] },
{ ...value, pooledAvailability: undefined }
];
const fetcher = vi.fn();
for (const incomplete of invalid) fetcher.mockResolvedValueOnce(Response.json(incomplete));
fetcher.mockResolvedValueOnce(Response.json(value));
const refresh = setup(fetcher);
for (const _ of invalid) {
await refresh.refresh();
expect(refresh.onData).not.toHaveBeenCalled();
expect(refresh.onError).toHaveBeenLastCalledWith(
'The metrics response was incomplete.'
);
}
await refresh.refresh();
expect(refresh.onData).toHaveBeenLastCalledWith(value);
});
it('times out with retry feedback and clears loading', async () => {
vi.useFakeTimers();
const fetcher = vi.fn(