From 241e1accde4e04882a7343b2a8caa8bc94291f22 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 11 Aug 2026 06:21:28 -0700 Subject: [PATCH] test(cli): remove private test seams (#122046) --- src/cli/banner.test.ts | 34 ++++++++++++------------- src/cli/banner.ts | 6 ----- src/cli/update-cli.test-helpers.test.ts | 22 ---------------- src/cli/update-cli.test-helpers.ts | 19 -------------- src/cli/update-cli.test.ts | 8 +++--- src/commands/onboard-helpers.test.ts | 15 ++++++----- src/commands/onboard-helpers.ts | 2 -- 7 files changed, 29 insertions(+), 77 deletions(-) delete mode 100644 src/cli/update-cli.test-helpers.test.ts delete mode 100644 src/cli/update-cli.test-helpers.ts diff --git a/src/cli/banner.test.ts b/src/cli/banner.test.ts index fbe36fe5ef1a..79f438eb7c05 100644 --- a/src/cli/banner.test.ts +++ b/src/cli/banner.test.ts @@ -128,32 +128,33 @@ describe("emitCliBanner", () => { expect(written).toContain("( o.o )"); }); - it("keeps lobster day out of plain terminals and pinned tagline modes", async () => { - const { emitCliBanner, testing } = await importFreshBannerModule(); + it.each([ + { label: "plain terminals", mode: "random" as const, richTty: false }, + { label: "pinned tagline modes", mode: "off" as const, richTty: true }, + ])("keeps lobster day out of $label", async ({ mode, richTty }) => { + const { emitCliBanner } = await importFreshBannerModule(); setStdoutIsTty(true); const writeSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true); - const base = { + + emitCliBanner("2026.3.7", { argv: ["node", "openclaw"], commit: "abc1234", env: { LANG: "en_US.UTF-8" }, isTty: true, + mode, now: () => new Date(2026, 1, 26), - platform: "darwin" as const, - }; - - emitCliBanner("2026.3.7", { ...base, mode: "random", richTty: false }); - testing.resetBannerEmittedForTests(); - emitCliBanner("2026.3.7", { ...base, mode: "off", richTty: true }); + platform: "darwin", + richTty, + }); const written = writeSpy.mock.calls.map(([chunk]) => String(chunk)).join(""); expect(written).not.toContain("( o.o )"); }); - it("can reset banner emission state for same-module tests", async () => { - const { emitCliBanner, hasEmittedCliBanner, testing } = await importFreshBannerModule(); + it("emits only once per module instance", async () => { + const { emitCliBanner, hasEmittedCliBanner } = await importFreshBannerModule(); setStdoutIsTty(true); const writeSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true); - const options = { argv: ["node", "openclaw"], commit: "abc1234", @@ -165,12 +166,9 @@ describe("emitCliBanner", () => { }; emitCliBanner("2026.3.7", options); - expect(hasEmittedCliBanner()).toBe(true); - - testing.resetBannerEmittedForTests(); - expect(hasEmittedCliBanner()).toBe(false); - emitCliBanner("2026.3.7", options); - expect(writeSpy).toHaveBeenCalledTimes(2); + + expect(hasEmittedCliBanner()).toBe(true); + expect(writeSpy).toHaveBeenCalledTimes(1); }); }); diff --git a/src/cli/banner.ts b/src/cli/banner.ts index a0440388e651..4a5d158ec2ce 100644 --- a/src/cli/banner.ts +++ b/src/cli/banner.ts @@ -134,9 +134,3 @@ export function emitCliBanner(version: string, options: BannerOptions = {}) { export function hasEmittedCliBanner(): boolean { return bannerEmitted; } - -export const testing = { - resetBannerEmittedForTests(): void { - bannerEmitted = false; - }, -}; diff --git a/src/cli/update-cli.test-helpers.test.ts b/src/cli/update-cli.test-helpers.test.ts deleted file mode 100644 index eff8cbdad345..000000000000 --- a/src/cli/update-cli.test-helpers.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -// Update CLI test-helper tests cover helper fixtures used by update command tests. -import path from "node:path"; -import { describe, expect, it } from "vitest"; -import { isOwningNpmCommand } from "./update-cli.test-helpers.js"; - -describe("isOwningNpmCommand", () => { - it("accepts absolute npm binaries under the owning prefix", () => { - const prefix = path.join(path.sep, "opt", "homebrew"); - - expect(isOwningNpmCommand(path.join(prefix, "bin", "npm"), prefix)).toBe(true); - expect(isOwningNpmCommand(path.join(prefix, "npm.cmd"), prefix)).toBe(true); - }); - - it("rejects plain npm and paths outside the owning prefix", () => { - const prefix = path.join(path.sep, "opt", "homebrew"); - - expect(isOwningNpmCommand("npm", prefix)).toBe(false); - expect(isOwningNpmCommand(path.join(path.sep, "usr", "local", "bin", "npm"), prefix)).toBe( - false, - ); - }); -}); diff --git a/src/cli/update-cli.test-helpers.ts b/src/cli/update-cli.test-helpers.ts deleted file mode 100644 index a99998f7bdfb..000000000000 --- a/src/cli/update-cli.test-helpers.ts +++ /dev/null @@ -1,19 +0,0 @@ -// Update CLI test helpers provide shared fixtures and path helpers for update tests. -import path from "node:path"; - -function isPathInsideRoot(candidate: string, root: string): boolean { - const relative = path.relative(path.resolve(root), path.resolve(candidate)); - return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative)); -} - -export function isOwningNpmCommand(value: unknown, owningPrefix: string): boolean { - if (typeof value !== "string" || !path.isAbsolute(value)) { - return false; - } - const normalized = path.normalize(value); - return ( - normalized !== path.normalize("npm") && - isPathInsideRoot(normalized, owningPrefix) && - /npm(?:\.cmd)?$/i.test(normalized) - ); -} diff --git a/src/cli/update-cli.test.ts b/src/cli/update-cli.test.ts index 232a7b26d3c9..ec2972a5e223 100644 --- a/src/cli/update-cli.test.ts +++ b/src/cli/update-cli.test.ts @@ -27,7 +27,6 @@ import { CLAWHUB_INSTALL_ERROR_CODE } from "../plugins/clawhub-error-codes.js"; import { captureEnv, withEnvAsync } from "../test-utils/env.js"; import { VERSION } from "../version.js"; import { createCliRuntimeCapture, getMockCallOutput } from "./test-runtime-capture.js"; -import { isOwningNpmCommand } from "./update-cli.test-helpers.js"; const confirm = vi.fn(); const select = vi.fn(); @@ -5365,6 +5364,9 @@ describe("update-cli", () => { const pkgRoot = path.join(brewRoot, "openclaw"); const brewNpm = path.join(brewPrefix, "bin", "npm"); const win32PrefixNpm = path.join(brewPrefix, "npm.cmd"); + const owningNpmCommands = new Set([brewNpm, win32PrefixNpm].map(path.normalize)); + const isOwningNpmCommand = (value: unknown) => + typeof value === "string" && owningNpmCommands.has(path.normalize(value)); const pathNpmRoot = createCaseDir("nvm-root"); mockPackageInstallStatus(pkgRoot); pathExists.mockResolvedValue(false); @@ -5376,7 +5378,7 @@ describe("update-cli", () => { if (argv[0] === "npm" && argv[1] === "root" && argv[2] === "-g") { return commandResult({ stdout: `${pathNpmRoot}\n` }); } - if (isOwningNpmCommand(argv[0], brewPrefix) && argv[1] === "root" && argv[2] === "-g") { + if (isOwningNpmCommand(argv[0]) && argv[1] === "root" && argv[2] === "-g") { return commandResult({ stdout: `${brewRoot}\n` }); } return commandResult(); @@ -5395,7 +5397,7 @@ describe("update-cli", () => { .mock.calls.find( ([argv]) => Array.isArray(argv) && - isOwningNpmCommand(argv[0], brewPrefix) && + isOwningNpmCommand(argv[0]) && argv[1] === "i" && argv[2] === "-g" && argv.includes("openclaw@9999.0.0"), diff --git a/src/commands/onboard-helpers.test.ts b/src/commands/onboard-helpers.test.ts index cdf9b0b33fa1..7ad9f502e6f4 100644 --- a/src/commands/onboard-helpers.test.ts +++ b/src/commands/onboard-helpers.test.ts @@ -25,19 +25,12 @@ import { resolveControlUiLinks, resolveLocalControlUiProbeLinks, summarizeExistingConfig, - testing, validateGatewayPasswordInput, waitForGatewayReachable, } from "./onboard-helpers.js"; const tempDirs = useAutoCleanupTempDirTracker(afterEach); -describe("onboard error summaries", () => { - it("keeps the bounded first line UTF-16 well-formed", () => { - expect(testing.summarizeError(`${"x".repeat(118)}🚀tail\nignored`)).toBe(`${"x".repeat(118)}…`); - }); -}); - describe("printWizardHeader", () => { const withColumns = async (columns: number | undefined, run: () => Promise) => { const previous = Object.getOwnPropertyDescriptor(process.stdout, "columns"); @@ -699,6 +692,14 @@ describe("probeGatewayReachable", () => { }); }); + it("bounds thrown probe errors without splitting UTF-16", async () => { + const detail = `${"x".repeat(118)}…`; + const params = { url: "ws://127.0.0.1:18789" }; + mocks.probeGateway.mockRejectedValue(new Error(`${"x".repeat(118)}🚀tail\nignored`)); + expect(await probeGatewayReachable(params)).toEqual({ ok: false, detail }); + expect(await probeGatewayConfiguredModel(params)).toEqual({ kind: "unreachable", detail }); + }); + it("forwards a configured TLS fingerprint to the gateway probe", async () => { mocks.probeGateway.mockResolvedValueOnce({ ok: true, diff --git a/src/commands/onboard-helpers.ts b/src/commands/onboard-helpers.ts index 5ab4349e497c..73cd0ce1e808 100644 --- a/src/commands/onboard-helpers.ts +++ b/src/commands/onboard-helpers.ts @@ -537,7 +537,5 @@ function summarizeError(err: unknown): string { return line.length > 120 ? `${truncateUtf16Safe(line, 119)}…` : line; } -export const testing = { summarizeError }; - /** Default workspace path shown by onboarding prompts. */ export const DEFAULT_WORKSPACE = DEFAULT_AGENT_WORKSPACE_DIR;