Files
electric-cherry/tests/unit/metrics-insights.test.ts
2026-10-10 16:40:07 -04:00

208 lines
7.4 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from 'vitest';
import { metricRecommendations } from '../../src/lib/metrics-insights.js';
import type { MapMetrics } from '../../src/lib/metrics.js';
const policy = {
id: 't5-policy',
mode: 'create-destroy' as const,
minimum: 1,
maximum: 3,
spares: 1,
pausedReason: null
};
const map = (values: Partial<MapMetrics> = {}): MapMetrics => ({
game: 't5',
map: 'Moon',
running: { average: 2, peak: 2, capturedHours: 5 },
matches: {
started: 0,
withRecordedPlayers: 0,
averageGroupSize: null,
solo: 0,
soloShare: null
},
observed: 10,
active: 5,
idle: 5,
unknown: 0,
playerHours: 10,
noEmpty: 0,
availabilityObserved: 5,
availabilityUnknown: 0,
noEmptyAtMaximum: 0,
noEmptyBelowMaximum: 0,
policy,
...values
});
describe('autoscaling policy signals', () => {
it('compares map availability with the current maximum without needing player limits', () => {
const [result] = metricRecommendations([
map({
observed: 100,
active: 50,
idle: 50,
noEmpty: 0.1,
availabilityObserved: 4,
noEmptyAtMaximum: 0.1
})
]);
expect(result).toMatchObject({
kind: 'maximum',
tentative: false,
href: '/autoscaling#t5-policy',
action: 'Review policy'
});
expect(result.evidence).toContain('2.5%');
expect(result.evidence).toContain('At or above the current maximum (3)');
expect(result.policySummary).toBe('Current policy: 1–3 lobbies, 1 spare');
});
it('reviews the spare target and create time for no-empty time below the maximum', () => {
const [result] = metricRecommendations([
map({ noEmpty: 1, noEmptyBelowMaximum: 0.9, noEmptyAtMaximum: 0.1 })
]);
expect(result.kind).toBe('spares');
expect(result.reason).toContain('below the current maximum of 3');
expect(result.reason).toContain('raising the spare target from 1');
expect(result.title).toContain('create time');
expect(result.evidence).toContain('below it: 0.9 h');
});
it('uses the larger no-empty duration and favors maximum review on a tie', () => {
const [result] = metricRecommendations([
map({ noEmpty: 1, noEmptyBelowMaximum: 0.5, noEmptyAtMaximum: 0.5 })
]);
expect(result.kind).toBe('maximum');
expect(result.reason).toContain('At least half');
});
it('uses the two-percent threshold without treating occupied time as demand', () => {
expect(
metricRecommendations([
map({ active: 9.99, idle: 0.01, noEmpty: 0.099, noEmptyAtMaximum: 0.099 })
])[0].kind
).toBe('watch');
expect(metricRecommendations([map({ noEmpty: 0.1, noEmptyAtMaximum: 0.1 })])[0].kind).toBe(
'maximum'
);
expect(metricRecommendations([map({ active: 0.1, idle: 9.9 })])[0].kind).toBe('watch');
});
it('marks a tiny recorded sample as tentative', () => {
const [result] = metricRecommendations([
map({
observed: 1 / 120,
active: 1 / 120,
idle: 0,
noEmpty: 1 / 120,
availabilityObserved: 1 / 120,
noEmptyAtMaximum: 1 / 120
})
]);
expect(result.kind).toBe('maximum');
expect(result.tentative).toBe(true);
expect(result.evidence).toContain('30 s');
});
it('measures evidence coverage with map availability time', () => {
const [result] = metricRecommendations([
map({
noEmpty: 1,
noEmptyBelowMaximum: 1,
availabilityObserved: 5,
availabilityUnknown: 2,
observed: 100
})
]);
expect(result.kind).toBe('spares');
expect(result.tentative).toBe(true);
expect(
metricRecommendations([map({ availabilityObserved: 4, availabilityUnknown: 1 })])[0]
.tentative
).toBe(false);
});
it('suggests adding a policy for a map without one', () => {
const [result] = metricRecommendations([map({ policy: null })]);
expect(result).toMatchObject({
kind: 'policy',
title: 'Consider adding an autoscaling policy',
href: '/autoscaling',
action: 'Add policy',
tentative: false
});
});
it('keeps mixed no-empty evidence tentative when most lobby occupancy is unknown', () => {
const [result] = metricRecommendations([
map({
observed: 2,
active: 2,
idle: 0,
unknown: 18,
availabilityObserved: 2,
availabilityUnknown: 0,
noEmpty: 2,
noEmptyAtMaximum: 2
})
]);
expect(result.kind).toBe('maximum');
expect(result.tentative).toBe(true);
expect(result.evidence).toContain('18 h of lobby time has unknown occupancy');
});
it('reviews paused or disabled policies before suggesting limit changes', () => {
const values = { noEmpty: 1, noEmptyAtMaximum: 1 };
const [paused] = metricRecommendations([
map({ ...values, policy: { ...policy, pausedReason: 'Review a failed host command.' } })
]);
expect(paused.kind).toBe('policy');
expect(paused.title).toBe('Review and resume the autoscaling policy');
expect(paused.reason).toContain('Review a failed host command.');
const [disabled] = metricRecommendations([
map({ ...values, policy: { ...policy, mode: 'off' } })
]);
expect(disabled.kind).toBe('policy');
expect(disabled.title).toBe('Review and enable the autoscaling policy');
});
it('does not confuse all-unknown occupancy with an idle fleet or a policy signal', () => {
const [result] = metricRecommendations([
map({
observed: 0,
active: 0,
idle: 0,
unknown: 10,
playerHours: 0,
availabilityObserved: 0,
availabilityUnknown: 5,
policy: null
})
]);
expect(result.kind).toBe('reporting');
expect(result.title).toBe('Check occupancy reporting');
expect(result.tentative).toBe(true);
});
it('distinguishes a zero-duration sample from failed occupancy reporting', () => {
const [result] = metricRecommendations([
map({ observed: 0, active: 0, idle: 0, playerHours: 0, availabilityObserved: 0 })
]);
expect(result.title).toBe('Let observations accumulate');
expect(result.reason).toContain('no elapsed availability observations');
});
it('keeps advice scoped to input maps and sorts policy actions before quiet findings', () => {
const results = metricRecommendations([
map({ map: 'Moon' }),
map({ game: 't6', map: 'Town', noEmpty: 1, noEmptyBelowMaximum: 1 })
]);
expect(results.map(({ game, map, kind }) => ({ game, map, kind }))).toEqual([
{ game: 't6', map: 'Town', kind: 'spares' },
{ game: 't5', map: 'Moon', kind: 'watch' }
]);
expect(metricRecommendations([])).toEqual([]);
});
});