| 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; |
| 3 | |
| 4 | import type { CheckSample } from "@g1t/contracts"; |
| 5 | |
| 6 | import { latencyPlot, latencySummary, msWords, summaryWords } from "./latency.ts"; |
| 7 | |
| 8 | const at = (minute: number) => new Date(Date.UTC(2026, 9, 8, 7, minute)).toISOString(); |
| 9 | const sample = (minute: number, ms: number | null, outcome: CheckSample["outcome"], extra: Partial<CheckSample> = {}): CheckSample => ({ |
| 10 | component: "speed", |
| 11 | at: at(minute), |
| 12 | ms, |
| 13 | outcome, |
| 14 | colo: "IAD", |
| 15 | first_ms: null, |
| 16 | ...extra, |
| 17 | }); |
| 18 | |
| 19 | test("the chart: answered checks as a line, broken by failures and gaps; slow ones dotted; the slow line drawn", () => { |
| 20 | const samples = [ |
| 21 | sample(0, 300, "up"), |
| 22 | sample(1, 1200, "degraded", { first_ms: 2400 }), |
| 23 | sample(2, 5000, "down"), |
| 24 | sample(3, 400, "up"), |
| 25 | // Five minutes with no check: a gap. |
| 26 | sample(9, 500, "up"), |
| 27 | sample(10, 450, "up"), |
| 28 | ]; |
| 29 | const p = latencyPlot({ key: "speed", slow_ms: 800, from: at(0), to: at(10), samples }, { width: 640, height: 120 }); |
| 30 | assert.equal(p.max, 1500, "half again over the slow line, or the slowest answer, rounded up; a failure's time is not a speed"); |
| 31 | // Three segments: 0-1, 3, and 9-10. |
| 32 | assert.equal(p.line.match(/M/g)!.length, 3); |
| 33 | assert.equal(p.line.match(/L/g)!.length, 2); |
| 34 | assert.equal(p.slow.length, 1); |
| 35 | assert.equal(p.down.length, 1); |
| 36 | assert.ok(p.slowY! > p.plot.y && p.slowY! < p.plot.y + p.plot.height); |
| 37 | // The first check is at the left edge, the last at the right. |
| 38 | assert.ok(p.line.startsWith(`M${p.plot.x} `)); |
| 39 | assert.ok(p.line.includes(`L${p.plot.x + p.plot.width} `)); |
| 40 | assert.deepEqual(p.ticks.map((t) => t.label), ["0", "750 ms", "1.5 s"]); |
| 41 | }); |
| 42 | |
| 43 | test("the chart's scale leaves room over the slow line when every check was fast", () => { |
| 44 | const p = latencyPlot({ key: "api", slow_ms: 1500, from: at(0), to: at(1), samples: [sample(0, 90, "up")] }); |
| 45 | assert.equal(p.max, 2500); |
| 46 | const none = latencyPlot({ key: "api", slow_ms: null, from: at(0), to: at(1), samples: [] }); |
| 47 | assert.equal(none.line, ""); |
| 48 | assert.equal(none.slowY, null); |
| 49 | }); |
| 50 | |
| 51 | test("the summary over the chart", () => { |
| 52 | const s = latencySummary([ |
| 53 | sample(0, 300, "up"), |
| 54 | sample(1, 1200, "degraded", { first_ms: 2400 }), |
| 55 | sample(2, 5000, "down", { colo: "EWR" }), |
| 56 | sample(3, 400, "up"), |
| 57 | ]); |
| 58 | assert.deepEqual(s, { checks: 4, slow: 1, down: 1, median_ms: 400, slowest_ms: 1200, asked_again: 1, colos: ["IAD", "EWR"] }); |
| 59 | assert.equal(summaryWords(s), "4 checks · 1 slow · 1 not answering · median 400 ms · slowest 1.2 s · 1 asked again · from IAD, EWR"); |
| 60 | assert.equal(msWords(800), "800 ms"); |
| 61 | assert.equal(msWords(1500), "1.5 s"); |
| 62 | }); |