123 lines
4.5 KiB
TypeScript
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);
|
|
});
|