import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import { tmpdir } from "node:os"; import { fileURLToPath } from "node:url"; import { test } from "node:test"; import { compare, inspect, parseVersion, readBaseline } from "./inspect.mjs"; const sha = n => n.toString(16).padStart(40, "0"); const commit = n => ({ sha: sha(n), commit: { message: `Change ${n}\nDetails` } }); const page = (commits, total = commits.length, status = "ahead") => ({ status, total_commits: total, commits }); const release = { tag_name: "v26.04", html_url: "https://github.com/niri-wm/niri/releases/tag/v26.04" }; function responses(t, values) { const calls = []; t.mock.method(globalThis, "fetch", async (url, options) => { assert.equal(new URL(url).origin, "https://api.github.com"); assert.ok(options.signal instanceof AbortSignal); calls.push(url); assert.ok(values.length, `Unexpected request: ${url}`); return new Response(JSON.stringify(values.shift())); }); return calls; } test("versions retain release labels and exact build hashes", () => { assert.deepEqual(parseVersion("26.04 (8ed0da4)"), { version: "26.04", hash: "8ed0da4" }); assert.equal(parseVersion("26.04.1 (8ed0da4)").version, "26.04.1"); for (const value of ["", "26.04", "nightly", "26.04 (unknown)", "26.04 (8ed0da4-dirty)"]) assert.throws(() => parseVersion(value), /Unrecognized/); }); test("baseline is an explicit single comment, never inferred from installed Niri", () => { assert.equal(readBaseline('include "cfg/input.kdl"'), null); assert.equal(readBaseline("// reviewed-with: unknown\n"), null); assert.equal(readBaseline("// reviewed-with: 26.04 (8ed0da4)\r\n").version, "26.04"); for (const source of [ "// reviewed-with: latest", "// reviewed-with: unknown\n// reviewed-with: unknown", "// reviewed-with: 26.04 (8ed0da4) // inferred", ]) assert.throws(() => readBaseline(source)); }); test("comparison collects every commit across pages", async t => { const commits = Array.from({ length: 251 }, (_, i) => commit(i + 1)); const calls = responses(t, [page(commits.slice(0, 100), 251), page(commits.slice(100, 200), 251), page(commits.slice(200), 251)]); const result = await compare(sha(0), sha(251)); assert.equal(result.commits.length, 251); assert.deepEqual(result.commits[0], { sha: sha(1), title: "Change 1", url: `https://github.com/niri-wm/niri/commit/${sha(1)}` }); assert.match(calls[2], /page=3$/); }); test("identical commits have a complete empty range", async t => { responses(t, [page([], 0, "identical")]); assert.equal((await compare(sha(1), sha(1))).total_commits, 0); }); for (const [name, pages] of [ ["older installed revision", [page([], 0, "behind")]], ["divergent history", [page([], 0, "diverged")]], ["incomplete pages", [page([commit(1)], 2), page([], 2)]], ["duplicate commits", [page([commit(1)], 2), page([commit(1)], 2)]], ["changing total", [page([commit(1)], 2), page([commit(2)], 3)]], ["excess commits", [page([commit(1), commit(2)], 1)]], ["missing total", [{ status: "ahead", commits: [] }]], ["missing commits", [{ status: "ahead", total_commits: 1 }]], ["invalid hash", [page([{ ...commit(1), sha: "bad" }])]], ["missing message", [page([{ sha: sha(1) }])]], ]) { test(`rejects ${name} rather than claiming a complete review`, async t => { responses(t, pages); await assert.rejects(compare(sha(0), sha(2))); }); } test("reports reviewed-to-installed separately from development changes", async t => { const calls = responses(t, [ release, { sha: sha(3) }, { sha: sha(2) }, { sha: sha(1) }, page([commit(2)]), page([commit(3)]), ]); const result = await inspect("// reviewed-with: 25.11 (abcdef0)", "niri 26.04 (8ed0da4)"); assert.equal(result.baseline.version, "25.11"); assert.equal(result.installed.version, "26.04"); assert.equal(result.latest_release.tag, "v26.04"); assert.equal(result.upstream.ref, "main"); assert.match(result.installed.docs, new RegExp(`/${sha(2)}/docs/wiki/$`)); assert.match(result.installed.default_config, new RegExp(`/${sha(2)}/resources/default-config.kdl$`)); assert.equal(result.review.commits[0].sha, sha(2)); assert.equal(result.requires_upgrade.commits[0].sha, sha(3)); assert.match(calls[4], new RegExp(`compare/${sha(1)}\\.\\.\\.${sha(2)}`)); }); test("unknown baseline requires full review", async t => { responses(t, [release, { sha: sha(2) }, { sha: sha(2) }, page([], 0, "identical")]); const result = await inspect("// reviewed-with: unknown", "niri 26.04 (8ed0da4)"); assert.equal(result.baseline, null); assert.equal(result.review, null); }); test("malformed local metadata fails before networking", async t => { const calls = responses(t, []); await assert.rejects(inspect("// reviewed-with: latest", "niri 26.04 (8ed0da4)")); await assert.rejects(inspect("", "other-program 26.04 (8ed0da4)")); assert.equal(calls.length, 0); }); test("bad release metadata and resolved hashes fail closed", async t => { responses(t, [{}, release, { sha: "bad" }]); await assert.rejects(inspect("", "niri 26.04 (8ed0da4)"), /release/); await assert.rejects(inspect("", "niri 26.04 (8ed0da4)"), /SHA/); }); test("CLI locates config outside cwd and fails nonzero when Niri is missing", () => { const result = spawnSync(process.execPath, [fileURLToPath(new URL("inspect.mjs", import.meta.url))], { cwd: tmpdir(), env: { ...process.env, PATH: "" }, encoding: "utf8", }); assert.equal(result.status, 1); assert.equal(result.stdout, ""); assert.match(result.stderr, /spawnSync niri ENOENT/); }); test("HTTP, JSON, and network errors propagate", async t => { t.mock.method(globalThis, "fetch", async () => new Response("rate limited", { status: 403 })); await assert.rejects(compare(sha(0), sha(1)), /HTTP 403/); t.mock.method(globalThis, "fetch", async () => new Response("invalid")); await assert.rejects(compare(sha(0), sha(1)), SyntaxError); t.mock.method(globalThis, "fetch", async () => { throw new Error("offline"); }); await assert.rejects(compare(sha(0), sha(1)), /offline/); });