import assert from "node:assert/strict"; import { execFileSync } from "node:child_process"; import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import { test } from "node:test"; import { compare, inspect, parseVersion, readBaseline, versionStatus } from "./inspect.mjs"; const baselineVersion = "20240203-110809-5046fc22"; const installedVersion = "20260607-081727-900bab10"; const sha = n => n.toString(16).padStart(40, "0"); const commit = n => ({ sha: sha(n), commit: { message: `Change ${n}\n\nDetails` } }); const comparison = (commits, total = commits.length, status = "ahead") => ({ status, total_commits: total, commits }); 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.equal(options.headers.Accept, "application/vnd.github+json"); assert.ok(options.signal instanceof AbortSignal); calls.push(url); assert.ok(values.length, `Unexpected request: ${url}`); const value = values.shift(); if (value instanceof Error) throw value; return new Response(JSON.stringify(value)); }); return calls; } test("version timestamps order independently of hashes", () => { const current = parseVersion(installedVersion); assert.equal(current.hash, "900bab10"); assert.equal(current.timestamp, "20260607-081727"); assert.equal(versionStatus(null, current), "unknown"); assert.equal(versionStatus(current, parseVersion(baselineVersion)), "older"); assert.equal(versionStatus(parseVersion(baselineVersion), current), "newer"); assert.equal(versionStatus(current, parseVersion("20260607-081727-aaaaaaa")), "equal"); assert.equal(versionStatus(current, parseVersion("20260607-081728-0000000")), "newer"); for (const value of ["nightly", "", "wezterm " + installedVersion, installedVersion + "-dirty", "20260607-081727-notahash"]) { assert.throws(() => parseVersion(value), /Unrecognized/); } }); test("baseline accepts only one literal declaration, including unknown and CRLF", () => { assert.equal(readBaseline("local cfg = {}"), null); assert.equal(readBaseline("local supported_version = nil -- Not reviewed"), null); assert.equal(readBaseline(`local supported_version = '${baselineVersion}'\r\n`).version, baselineVersion); assert.equal(readBaseline(`local supported_version = "${baselineVersion}"`).version, baselineVersion); assert.equal(readBaseline(`-- local supported_version = '${baselineVersion}'`), null); for (const value of [ "local supported_version = nil", "local supported_version = wezterm.version", `local supported_version = '${baselineVersion}' .. '-dirty'`, "local supported_version = nil\nlocal supported_version = nil", ]) assert.throws(() => readBaseline(value), /Expected one literal/); assert.throws(() => readBaseline("local supported_version = 'nightly'"), /Unrecognized/); }); test("comparison paginates beyond 250 commits without relying on changed files", async t => { const commits = Array.from({ length: 251 }, (_, i) => commit(i + 1)); const calls = responses(t, [ comparison(commits.slice(0, 100), 251), comparison(commits.slice(100, 200), 251), comparison(commits.slice(200), 251), ]); const result = await compare(sha(0), sha(251)); assert.equal(result.total_commits, 251); assert.equal(result.commits.length, 251); assert.deepEqual(result.commits[0], { sha: sha(1), title: "Change 1", url: `https://github.com/wezterm/wezterm/commit/${sha(1)}` }); assert.match(calls[2], /per_page=100&page=3$/); }); test("identical commits produce an empty complete comparison", async t => { responses(t, [comparison([], 0, "identical")]); assert.equal((await compare(sha(1), sha(1))).total_commits, 0); }); for (const [name, pages, error] of [ ["behind", [comparison([], 0, "behind")], /history is behind/], ["diverged", [comparison([], 0, "diverged")], /history is diverged/], ["incomplete", [comparison([commit(1)], 2), comparison([], 2)], /Incomplete/], ["repeated", [comparison([commit(1)], 2), comparison([commit(1)], 2)], /repeated/], ["inconsistent total", [comparison([commit(1)], 2), comparison([commit(2)], 3)], /inconsistent/], ["excess commits", [comparison([commit(1), commit(2)], 1)], /Incomplete/], ["missing count", [{ status: "ahead", commits: [] }], /Invalid/], ["missing commits", [{ status: "ahead", total_commits: 1 }], /Invalid/], ["bad SHA", [comparison([{ ...commit(1), sha: "bad" }])], /invalid commit SHA/], ["missing message", [comparison([{ sha: sha(1) }])], /Invalid/], ["network failure", [new Error("network failed")], /network failed/], ]) { test(`comparison rejects ${name}`, async t => { responses(t, pages); await assert.rejects(compare(sha(0), sha(2)), error); }); } test("HTTP and malformed JSON failures propagate", async t => { t.mock.method(globalThis, "fetch", async () => new Response("rate limited", { status: 403 })); await assert.rejects(compare(sha(1), sha(2)), /GitHub HTTP 403/); t.mock.method(globalThis, "fetch", async () => new Response("not JSON")); await assert.rejects(compare(sha(1), sha(2)), SyntaxError); }); test("inspection freezes main and distinguishes review from upgrade-only changes", async t => { const calls = responses(t, [ { sha: sha(3) }, { sha: sha(2) }, { sha: sha(1) }, comparison([commit(2)]), comparison([commit(3)]), ]); const result = await inspect(`local supported_version = '${baselineVersion}'`, installedVersion); assert.equal(result.runtime_check, "newer"); assert.equal(result.baseline.version, baselineVersion); assert.equal(result.installed.version, installedVersion); assert.equal(result.upstream.sha, sha(3)); assert.match(result.installed.changelog, new RegExp(`/${sha(2)}/docs/changelog.md$`)); assert.match(result.installed.lua_docs, new RegExp(`/${sha(2)}/docs/config/lua/$`)); assert.equal(result.review.commits[0].sha, sha(2)); assert.equal(result.requires_upgrade.commits[0].sha, sha(3)); assert.match(calls[0], /commits\/main$/); assert.match(calls[3], new RegExp(`compare/${sha(1)}\\.\\.\\.${sha(2)}\\?`)); }); test("inspection leaves missing baseline unknown", async t => { const calls = responses(t, [{ sha: sha(2) }, { sha: sha(2) }, comparison([], 0, "identical")]); const result = await inspect("", installedVersion); assert.equal(result.baseline, null); assert.equal(result.review, null); assert.equal(result.runtime_check, "unknown"); assert.equal(calls.length, 3); }); test("inspection rejects malformed config before networking and invalid resolved SHAs", async t => { const calls = responses(t, [{ sha: "bad" }]); await assert.rejects(inspect("local supported_version = 'nightly'", installedVersion), /literal/); assert.equal(calls.length, 0); await assert.rejects(inspect("", installedVersion), /invalid commit SHA/); }); test("CLI uses native executables without a shell and resolves paths outside cwd", () => { const directory = mkdtempSync(join(tmpdir(), "wezterm-modernize-")); try { const preload = join(directory, "preload.mjs"); for (const platform of ["linux", "win32"]) { writeFileSync(preload, ` import assert from 'node:assert/strict'; import childProcess from 'node:child_process'; import fs from 'node:fs'; import { syncBuiltinESMExports } from 'node:module'; Object.defineProperty(process, 'platform', { value: '${platform}' }); const read = fs.readFileSync; fs.readFileSync = (file, ...args) => file instanceof URL && file.pathname.endsWith('/wezterm.lua') ? 'local supported_version = nil' : read(file, ...args); childProcess.execFileSync = (file, args, options) => { assert.equal(file, '${platform === "win32" ? "wezterm.exe" : "wezterm"}'); assert.deepEqual(args, ['--version']); assert.ok(!options.shell); assert.equal(options.windowsHide, true); return 'wezterm ${installedVersion}\\r\\n'; }; syncBuiltinESMExports(); globalThis.fetch = async url => new Response(JSON.stringify(url.includes('/commits/') ? { sha: '${sha(2)}' } : { status: 'identical', total_commits: 0, commits: [] })); `); const output = execFileSync(process.execPath, ["--import", pathToFileURL(preload).href, fileURLToPath(new URL("inspect.mjs", import.meta.url))], { cwd: directory, encoding: "utf8", timeout: 10_000, }); const result = JSON.parse(output); assert.equal(result.installed.version, installedVersion); assert.equal(result.config, fileURLToPath(new URL("../../../../wezterm.lua", import.meta.url))); assert.equal(result.runtime_check, "unknown"); } } finally { rmSync(directory, { recursive: true, force: true }); } });