Skip to content
quarry Artifact [8947843c70]
Login

Artifact [8947843c70]

Artifact 8947843c704a5dc0a32362e5395a2cd2f28f49f2ae0e4b2627801ff9ccc5016d:


import { test } from "node:test";
import assert from "node:assert/strict";
import {
  initialState, step, timelineBroken, formatDuration, composeMessage, STARTING_GRACE_MS,
} from "../src/logic.js";

const up = (version = "v1", node = "n1") => ({ health: { status: 200, ready: true, version, node }, timelineStatus: 200 });
const down = (status = 502) => ({ health: { status, ready: false } });
const T = 60_000;

function run(obsList, start = initialState(), t0 = 1_000_000) {
  let s = start;
  const all = [];
  obsList.forEach((o, i) => {
    const r = step(s, o, t0 + i * T);
    s = r.state;
    all.push(...r.actions);
  });
  return { s, all };
}

test("healthy checks record version and node, no actions", () => {
  const { s, all } = run([up("v1", "a"), up("v1", "a")]);
  assert.deepEqual(all, []);
  assert.equal(s.version, "v1");
  assert.equal(s.node, "a");
  assert.equal(s.incident, null);
});

test("one unhealthy check does not alert", () => {
  const { all } = run([up(), down(), up()]);
  assert.deepEqual(all, []);
});

test("two consecutive unhealthy checks alert once", () => {
  const { s, all } = run([up(), down(), down(), down(), down()]);
  assert.equal(all.length, 1);
  assert.equal(all[0].kind, "down");
  assert.equal(all[0].since, 1_000_000 + T);
  assert.equal(s.incident, "down");
});

test("ready:false with 200 counts as unhealthy", () => {
  const notReady = { health: { status: 200, ready: false, version: "v1" } };
  const { all } = run([up(), notReady, notReady]);
  assert.equal(all[0].kind, "down");
});

test("recovery reports outage length from first failure", () => {
  const { s, all } = run([up(), down(), down(), down(), up()]);
  assert.deepEqual(all.map((a) => a.kind), ["down", "recovered"]);
  assert.equal(all[1].outageMs, 3 * T);
  assert.equal(s.incident, null);
});

test("no recovery message without a prior alert", () => {
  const { all } = run([up(), down(), up()]);
  assert.deepEqual(all, []);
});

test("timeline 5xx twice alerts broken once", () => {
  const bad = { ...up(), timelineStatus: 500 };
  const { s, all } = run([up(), bad, bad, bad]);
  assert.deepEqual(all.map((a) => a.kind), ["broken"]);
  assert.equal(s.incident, "broken");
});

test("single timeline 5xx does not alert", () => {
  const bad = { ...up(), timelineStatus: 500 };
  assert.deepEqual(run([up(), bad, up()]).all, []);
});

test("broken recovers when timeline answers", () => {
  const bad = { ...up(), timelineStatus: 500 };
  const { all } = run([up(), bad, bad, up()]);
  assert.deepEqual(all.map((a) => a.kind), ["broken", "recovered"]);
});

test("503 within grace of a version change is not an incident", () => {
  const starting = { ...up("v2"), timelineStatus: 503 };
  const { all, s } = run([up("v1"), starting, starting, starting]);
  assert.deepEqual(all, []);
  assert.equal(s.brokenStreak, 0);
});

test("503 after the grace window counts", () => {
  const starting = { ...up("v2"), timelineStatus: 503 };
  const n = STARTING_GRACE_MS / T + 3;
  const { all } = run([up("v1"), ...Array(n).fill(starting)]);
  assert.deepEqual(all.map((a) => a.kind), ["broken"]);
});

test("503 without a version change counts", () => {
  const bad = { ...up(), timelineStatus: 503 };
  assert.deepEqual(run([up(), bad, bad]).all.map((a) => a.kind), ["broken"]);
});

test("timelineBroken ignores null and 4xx", () => {
  assert.equal(timelineBroken(null, null, 0), false);
  assert.equal(timelineBroken(404, null, 0), false);
  assert.equal(timelineBroken(500, null, 0), true);
});

test("down then host back but timeline still broken hands over to broken", () => {
  const bad = { ...up(), timelineStatus: 500 };
  const { s, all } = run([up(), down(), down(), bad]);
  assert.deepEqual(all.map((a) => a.kind), ["down", "recovered"]);
  assert.equal(s.incident, "broken");
});

test("first ever observation is not a version change", () => {
  const { s } = run([up("v1")]);
  assert.equal(s.versionChangedAt, null);
});

test("formatDuration", () => {
  assert.equal(formatDuration(45_000), "45s");
  assert.equal(formatDuration(125_000), "2m 5s");
  assert.equal(formatDuration(3_700_000), "1h 1m");
});

test("composeMessage carries state and link", () => {
  const s = { ...initialState(), lastStatus: 502, version: "v9", node: "n7" };
  const m = composeMessage({ kind: "down", since: 0 }, s, "https://forge.example");
  assert.match(m.subject, /1970-01-01T00:00:00.000Z/);
  for (const part of ["502", "v9", "n7", "https://forge.example"]) assert.ok(m.body.includes(part));
  const r = composeMessage({ kind: "recovered", since: 0, outageMs: 90_000 }, s, "u");
  assert.ok(r.subject.includes("1m 30s"));
});