/** * Regression test for #4100 — Home "Update Available" banner no longer appears. * * Root cause: `GET /api/system/version` derived `latest` ONLY from `npm info` via the * `npm` CLI binary, returning null on ANY error (binary missing in Docker/desktop, * registry unreachable) → updateAvailable=false → banner silently never renders. * Secondary: `isNewer()`'s `v.split(".").map(Number)` collapsed to false on `v`-prefixed * or pre-release version strings (NaN comparisons). * * These assertions fail against the old inline semantics (no module, fragile isNewer, * no npm-binary-free fallback) and pass once `src/lib/system/versionCheck.ts` exists. */ import test from "node:test"; import assert from "node:assert/strict"; import { normalizeVersion, isNewer, resolveLatestVersion, } from "@/lib/system/versionCheck"; test("normalizeVersion strips v-prefix, pre-release/build, returns numeric tuple", () => { assert.deepEqual(normalizeVersion("3.8.28"), [3, 8, 28]); assert.deepEqual(normalizeVersion("v3.8.28"), [3, 8, 28]); assert.deepEqual(normalizeVersion("3.8.28-rc.1"), [3, 8, 28]); assert.deepEqual(normalizeVersion("3.8.28+build.5"), [3, 8, 28]); assert.equal(normalizeVersion(""), null); assert.equal(normalizeVersion("not-a-version"), null); }); test("isNewer: basic ordering and null safety", () => { assert.equal(isNewer("3.8.29", "3.8.28"), true); assert.equal(isNewer("3.8.28", "3.8.28"), false); assert.equal(isNewer("3.8.27", "3.8.28"), false); assert.equal(isNewer(null, "3.8.28"), false); }); test("isNewer: v-prefixed latest is handled (#4100 — old code returned false via NaN)", () => { assert.equal(isNewer("v3.8.29", "3.8.28"), true); }); test("isNewer: pre-release suffix is handled (#4100 — old code returned false via NaN)", () => { assert.equal(isNewer("3.8.29-rc.1", "3.8.28"), true); }); test("isNewer: multi-digit minor ordering", () => { assert.equal(isNewer("3.10.0", "3.9.9"), true); assert.equal(isNewer("3.9.9", "3.10.0"), false); }); test("resolveLatestVersion falls back to the registry when the npm CLI path fails (#4100)", async () => { const latest = await resolveLatestVersion({ npmCli: async () => null, // npm binary missing / registry unreachable via CLI registry: async () => "3.8.29", // npm-binary-free HTTP fallback succeeds }); assert.equal(latest, "3.8.29"); }); test("resolveLatestVersion prefers the npm CLI when it succeeds", async () => { let registryCalled = false; const latest = await resolveLatestVersion({ npmCli: async () => "3.8.30", registry: async () => { registryCalled = true; return "3.8.29"; }, }); assert.equal(latest, "3.8.30"); assert.equal(registryCalled, false); }); test("resolveLatestVersion returns null when both sources fail (no silent crash)", async () => { const latest = await resolveLatestVersion({ npmCli: async () => null, registry: async () => null, }); assert.equal(latest, null); });