From 0f9ea0229a005f3b1585aecf4dfc0fbe05fe205a Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 21 Feb 2026 19:50:23 +0000 Subject: [PATCH] test(infra): dedupe install-source fixtures and cover npm pack parsing --- src/infra/install-source-utils.test.ts | 140 ++++++++++++++++--------- 1 file changed, 92 insertions(+), 48 deletions(-) diff --git a/src/infra/install-source-utils.test.ts b/src/infra/install-source-utils.test.ts index a46c6c9aa..d816f366c 100644 --- a/src/infra/install-source-utils.test.ts +++ b/src/infra/install-source-utils.test.ts @@ -9,6 +9,7 @@ import { } from "./install-source-utils.js"; const runCommandWithTimeoutMock = vi.fn(); +const TEMP_DIR_PREFIX = "openclaw-install-source-utils-"; vi.mock("../process/exec.js", () => ({ runCommandWithTimeout: (...args: unknown[]) => runCommandWithTimeoutMock(...args), @@ -22,6 +23,39 @@ async function createTempDir(prefix: string) { return dir; } +async function createFixtureDir() { + return await createTempDir(TEMP_DIR_PREFIX); +} + +async function createFixtureFile(params: { + fileName: string; + contents: string; + dir?: string; +}): Promise<{ dir: string; filePath: string }> { + const dir = params.dir ?? (await createFixtureDir()); + const filePath = path.join(dir, params.fileName); + await fs.writeFile(filePath, params.contents, "utf-8"); + return { dir, filePath }; +} + +function mockPackCommandResult(params: { stdout: string; stderr?: string; code?: number }) { + runCommandWithTimeoutMock.mockResolvedValue({ + stdout: params.stdout, + stderr: params.stderr ?? "", + code: params.code ?? 0, + signal: null, + killed: false, + }); +} + +async function runPack(spec: string, cwd: string, timeoutMs = 1000) { + return await packNpmSpecToArchive({ + spec, + timeoutMs, + cwd, + }); +} + beforeEach(() => { runCommandWithTimeoutMock.mockReset(); }); @@ -63,9 +97,10 @@ describe("resolveArchiveSourcePath", () => { }); it("rejects unsupported archive extensions", async () => { - const dir = await createTempDir("openclaw-install-source-utils-"); - const filePath = path.join(dir, "plugin.txt"); - await fs.writeFile(filePath, "not-an-archive", "utf-8"); + const { filePath } = await createFixtureFile({ + fileName: "plugin.txt", + contents: "not-an-archive", + }); const result = await resolveArchiveSourcePath(filePath); expect(result.ok).toBe(false); @@ -75,9 +110,10 @@ describe("resolveArchiveSourcePath", () => { }); it("accepts supported archive extensions", async () => { - const dir = await createTempDir("openclaw-install-source-utils-"); - const filePath = path.join(dir, "plugin.zip"); - await fs.writeFile(filePath, "", "utf-8"); + const { filePath } = await createFixtureFile({ + fileName: "plugin.zip", + contents: "", + }); const result = await resolveArchiveSourcePath(filePath); expect(result).toEqual({ ok: true, path: filePath }); @@ -86,8 +122,8 @@ describe("resolveArchiveSourcePath", () => { describe("packNpmSpecToArchive", () => { it("packs spec and returns archive path using JSON output metadata", async () => { - const cwd = await createTempDir("openclaw-install-source-utils-"); - runCommandWithTimeoutMock.mockResolvedValue({ + const cwd = await createFixtureDir(); + mockPackCommandResult({ stdout: JSON.stringify([ { id: "openclaw-plugin@1.2.3", @@ -98,17 +134,9 @@ describe("packNpmSpecToArchive", () => { shasum: "abc123", }, ]), - stderr: "", - code: 0, - signal: null, - killed: false, }); - const result = await packNpmSpecToArchive({ - spec: "openclaw-plugin@1.2.3", - timeoutMs: 1000, - cwd, - }); + const result = await runPack("openclaw-plugin@1.2.3", cwd); expect(result).toEqual({ ok: true, @@ -131,20 +159,12 @@ describe("packNpmSpecToArchive", () => { }); it("falls back to parsing final stdout line when npm json output is unavailable", async () => { - const cwd = await createTempDir("openclaw-install-source-utils-"); - runCommandWithTimeoutMock.mockResolvedValue({ + const cwd = await createFixtureDir(); + mockPackCommandResult({ stdout: "npm notice created package\nopenclaw-plugin-1.2.3.tgz\n", - stderr: "", - code: 0, - signal: null, - killed: false, }); - const result = await packNpmSpecToArchive({ - spec: "openclaw-plugin@1.2.3", - timeoutMs: 1000, - cwd, - }); + const result = await runPack("openclaw-plugin@1.2.3", cwd); expect(result).toEqual({ ok: true, @@ -154,20 +174,14 @@ describe("packNpmSpecToArchive", () => { }); it("returns npm pack error details when command fails", async () => { - const cwd = await createTempDir("openclaw-install-source-utils-"); - runCommandWithTimeoutMock.mockResolvedValue({ + const cwd = await createFixtureDir(); + mockPackCommandResult({ stdout: "fallback stdout", stderr: "registry timeout", code: 1, - signal: null, - killed: false, }); - const result = await packNpmSpecToArchive({ - spec: "bad-spec", - timeoutMs: 5000, - cwd, - }); + const result = await runPack("bad-spec", cwd, 5000); expect(result.ok).toBe(false); if (!result.ok) { @@ -177,24 +191,54 @@ describe("packNpmSpecToArchive", () => { }); it("returns explicit error when npm pack produces no archive name", async () => { - const cwd = await createTempDir("openclaw-install-source-utils-"); - runCommandWithTimeoutMock.mockResolvedValue({ + const cwd = await createFixtureDir(); + mockPackCommandResult({ stdout: " \n\n", - stderr: "", - code: 0, - signal: null, - killed: false, }); - const result = await packNpmSpecToArchive({ - spec: "openclaw-plugin@1.2.3", - timeoutMs: 5000, - cwd, - }); + const result = await runPack("openclaw-plugin@1.2.3", cwd, 5000); expect(result).toEqual({ ok: false, error: "npm pack produced no archive", }); }); + + it("parses scoped metadata from id-only json output even with npm notice prefix", async () => { + const cwd = await createFixtureDir(); + mockPackCommandResult({ + stdout: + "npm notice creating package\n" + + JSON.stringify([ + { + id: "@openclaw/plugin-demo@2.0.0", + filename: "openclaw-plugin-demo-2.0.0.tgz", + }, + ]), + }); + + const result = await runPack("@openclaw/plugin-demo@2.0.0", cwd); + expect(result).toEqual({ + ok: true, + archivePath: path.join(cwd, "openclaw-plugin-demo-2.0.0.tgz"), + metadata: { + resolvedSpec: "@openclaw/plugin-demo@2.0.0", + }, + }); + }); + + it("uses stdout fallback error text when stderr is empty", async () => { + const cwd = await createFixtureDir(); + mockPackCommandResult({ + stdout: "network timeout", + stderr: " ", + code: 1, + }); + + const result = await runPack("bad-spec", cwd); + expect(result).toEqual({ + ok: false, + error: "npm pack failed: network timeout", + }); + }); });