1 Commits
Author SHA1 Message Date
sneak b80c8fd214 Frontend unit tests cover durations, colours, statistics and health (closes #21)
check / check (push) Successful in 4m19s
New table-driven tests in test/unit/main.test.js: humanDuration, the
figure and sparkline colours either side of each latency boundary, a
target's min, max, average and median over empty, all-unreachable and
mixed histories, and the four health states either side of their
thresholds. src/main.js exports the four names they need.

package.json gains a test script, which script/frontend-test runs with
the dot reporter and, if a test fails, again with the spec reporter
before failing. NODE_OPTIONS picks the reporter, since yarn appends its
arguments after the test files.

Model: opus-5-5
2026-10-03 23:59:16 +00:00
+14 -28
View File
@@ -327,22 +327,23 @@ for (const { history, latencies, statistics } of [
} }
// An app state in which, of the WAN targets, the first timedOut timed out, // An app state in which, of the WAN targets, the first timedOut timed out,
// the next unreachable were found unreachable, the next answered answered // the next answered answered after latency ms, and the rest have not been
// after latency ms, and the rest have not been checked yet. // checked yet.
function stateAfter({ timedOut, unreachable, answered, latency }) { function stateAfter({ timedOut, answered, latency }) {
const state = new AppState([]); const state = new AppState([]);
const results = [ for (const host of state.wan.slice(0, timedOut)) {
...Array(timedOut).fill({ latency: null, error: "timeout" }), host.pushSample(Date.now(), { latency: null, error: "timeout" });
...Array(unreachable).fill({ latency: null, error: "unreachable" }), }
...Array(answered).fill({ latency, error: null }), for (const host of state.wan.slice(timedOut, timedOut + answered)) {
]; host.pushSample(Date.now(), { latency, error: null });
results.forEach((result, i) => state.wan[i].pushSample(Date.now(), result)); }
return state; return state;
} }
// For each number the health is decided by, the rows put it one under, at // For each number the health is decided by, the rows put it one under, at
// and one over its threshold. // and one over its threshold. A target found unreachable counts as one
for (const { timedOut, unreachable = 0, answered, latency, health } of [ // that timed out.
for (const { timedOut, answered, latency, health } of [
// Offline: more than 10 timed out and at most 4 answered. // Offline: more than 10 timed out and at most 4 answered.
{ timedOut: 9, answered: 4, latency: 30, health: "degraded" }, { timedOut: 9, answered: 4, latency: 30, health: "degraded" },
{ timedOut: 10, answered: 4, latency: 30, health: "degraded" }, { timedOut: 10, answered: 4, latency: 30, health: "degraded" },
@@ -359,24 +360,9 @@ for (const { timedOut, unreachable = 0, answered, latency, health } of [
{ timedOut: 0, answered: 4, latency: 1001, health: "slow" }, { timedOut: 0, answered: 4, latency: 1001, health: "slow" },
{ timedOut: 0, answered: 2, latency: 1001, health: "healthy" }, { timedOut: 0, answered: 2, latency: 1001, health: "healthy" },
{ timedOut: 0, answered: 3, latency: 1001, health: "healthy" }, { timedOut: 0, answered: 3, latency: 1001, health: "healthy" },
// A target found unreachable counts as one that timed out.
{
timedOut: 5,
unreachable: 6,
answered: 4,
latency: 30,
health: "offline",
},
{
timedOut: 0,
unreachable: 5,
answered: 10,
latency: 30,
health: "degraded",
},
]) { ]) {
test(`with ${timedOut} WAN targets timed out, ${unreachable} found unreachable and ${answered} answering after ${latency}ms, the health is ${health}`, () => { test(`with ${timedOut} WAN targets timed out and ${answered} answering after ${latency}ms, the health is ${health}`, () => {
const state = stateAfter({ timedOut, unreachable, answered, latency }); const state = stateAfter({ timedOut, answered, latency });
assert.equal(state.healthStatus(), health); assert.equal(state.healthStatus(), health);
}); });
} }