From 12cbaddadeec5712810c75ed8628ca0ef1f8f96c Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 13 Mar 2026 19:37:49 +0000 Subject: [PATCH] test: expand runtime guard and path prepend coverage --- src/infra/path-prepend.test.ts | 33 +++++++++++++++++++++++++++++++++ src/infra/runtime-guard.test.ts | 27 +++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 src/infra/path-prepend.test.ts diff --git a/src/infra/path-prepend.test.ts b/src/infra/path-prepend.test.ts new file mode 100644 index 000000000..29dfb504c --- /dev/null +++ b/src/infra/path-prepend.test.ts @@ -0,0 +1,33 @@ +import path from "node:path"; +import { describe, expect, it } from "vitest"; +import { mergePathPrepend, normalizePathPrepend } from "./path-prepend.js"; + +describe("path prepend helpers", () => { + it("normalizes prepend lists by trimming, skipping blanks, and deduping", () => { + expect( + normalizePathPrepend([ + " /custom/bin ", + "", + " /custom/bin ", + "/opt/bin", + // oxlint-disable-next-line typescript/no-explicit-any + 42 as any, + ]), + ).toEqual(["/custom/bin", "/opt/bin"]); + expect(normalizePathPrepend()).toEqual([]); + }); + + it("merges prepended paths ahead of existing values without duplicates", () => { + expect(mergePathPrepend(`/usr/bin${path.delimiter}/opt/bin`, ["/custom/bin", "/usr/bin"])).toBe( + ["/custom/bin", "/usr/bin", "/opt/bin"].join(path.delimiter), + ); + expect(mergePathPrepend(undefined, ["/custom/bin"])).toBe("/custom/bin"); + expect(mergePathPrepend("/usr/bin", [])).toBe("/usr/bin"); + }); + + it("trims existing path entries while preserving order", () => { + expect( + mergePathPrepend(` /usr/bin ${path.delimiter} ${path.delimiter} /opt/bin `, ["/custom/bin"]), + ).toBe(["/custom/bin", "/usr/bin", "/opt/bin"].join(path.delimiter)); + }); +}); diff --git a/src/infra/runtime-guard.test.ts b/src/infra/runtime-guard.test.ts index 410fe5d4a..ca1080b84 100644 --- a/src/infra/runtime-guard.test.ts +++ b/src/infra/runtime-guard.test.ts @@ -4,6 +4,7 @@ import { detectRuntime, isAtLeast, parseSemver, + isSupportedNodeVersion, type RuntimeDetails, runtimeSatisfies, } from "./runtime-guard.js"; @@ -12,6 +13,7 @@ describe("runtime-guard", () => { it("parses semver with or without leading v", () => { expect(parseSemver("v22.1.3")).toEqual({ major: 22, minor: 1, patch: 3 }); expect(parseSemver("1.3.0")).toEqual({ major: 1, minor: 3, patch: 0 }); + expect(parseSemver("22.16.0-beta.1")).toEqual({ major: 22, minor: 16, patch: 0 }); expect(parseSemver("invalid")).toBeNull(); }); @@ -49,6 +51,9 @@ describe("runtime-guard", () => { expect(runtimeSatisfies(nodeOld)).toBe(false); expect(runtimeSatisfies(nodeTooOld)).toBe(false); expect(runtimeSatisfies(unknown)).toBe(false); + expect(isSupportedNodeVersion("22.16.0")).toBe(true); + expect(isSupportedNodeVersion("22.15.9")).toBe(false); + expect(isSupportedNodeVersion(null)).toBe(false); }); it("throws via exit when runtime is too old", () => { @@ -67,6 +72,7 @@ describe("runtime-guard", () => { }; expect(() => assertSupportedRuntime(runtime, details)).toThrow("exit"); expect(runtime.error).toHaveBeenCalledWith(expect.stringContaining("requires Node")); + expect(runtime.error).toHaveBeenCalledWith(expect.stringContaining("Detected: node 20.0.0")); }); it("returns silently when runtime meets requirements", () => { @@ -84,4 +90,25 @@ describe("runtime-guard", () => { expect(() => assertSupportedRuntime(runtime, details)).not.toThrow(); expect(runtime.exit).not.toHaveBeenCalled(); }); + + it("reports unknown runtimes with fallback labels", () => { + const runtime = { + log: vi.fn(), + error: vi.fn(), + exit: vi.fn(() => { + throw new Error("exit"); + }), + }; + const details: RuntimeDetails = { + kind: "unknown", + version: null, + execPath: null, + pathEnv: "(not set)", + }; + + expect(() => assertSupportedRuntime(runtime, details)).toThrow("exit"); + expect(runtime.error).toHaveBeenCalledWith( + expect.stringContaining("Detected: unknown runtime (exec: unknown)."), + ); + }); });