Files
diegosouzapw--omniroute/tests/unit/system-version-check-4100.test.ts
2026-07-13 13:39:12 +08:00

123 lines
4.5 KiB
TypeScript

/**
* 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,
getLatestVersionFromGitHub,
} 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 falls back to GitHub when npm CLI and registry both fail (#4100 — npm blocked, GitHub reachable)", async () => {
let githubCalled = false;
const latest = await resolveLatestVersion({
npmCli: async () => null,
registry: async () => null,
github: async () => {
githubCalled = true;
return "v3.8.39"; // GitHub releases tag_name — v-prefix tolerated downstream
},
});
assert.equal(latest, "v3.8.39");
assert.equal(githubCalled, true);
});
test("resolveLatestVersion does NOT hit GitHub when the registry already answered", async () => {
let githubCalled = false;
const latest = await resolveLatestVersion({
npmCli: async () => null,
registry: async () => "3.8.39",
github: async () => {
githubCalled = true;
return "v3.8.40";
},
});
assert.equal(latest, "3.8.39");
assert.equal(githubCalled, false);
});
test("resolveLatestVersion returns null only when ALL three sources fail (no silent crash)", async () => {
const latest = await resolveLatestVersion({
npmCli: async () => null,
registry: async () => null,
github: async () => null,
});
assert.equal(latest, null);
});
test("getLatestVersionFromGitHub parses tag_name from the releases API", async () => {
const fakeFetch = (async () =>
new Response(JSON.stringify({ tag_name: "v3.8.39" }), {
status: 200,
headers: { "Content-Type": "application/json" },
})) as unknown as typeof fetch;
assert.equal(await getLatestVersionFromGitHub(fakeFetch), "v3.8.39");
});
test("getLatestVersionFromGitHub returns null on a non-OK response", async () => {
const fakeFetch = (async () =>
new Response("rate limited", { status: 403 })) as unknown as typeof fetch;
assert.equal(await getLatestVersionFromGitHub(fakeFetch), null);
});