Files
openclaw/src/infra/install-source-utils.test.ts

162 lines
4.5 KiB
TypeScript

import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
packNpmSpecToArchive,
resolveArchiveSourcePath,
withTempDir,
} from "./install-source-utils.js";
const runCommandWithTimeoutMock = vi.fn();
vi.mock("../process/exec.js", () => ({
runCommandWithTimeout: (...args: unknown[]) => runCommandWithTimeoutMock(...args),
}));
const tempDirs: string[] = [];
async function createTempDir(prefix: string) {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
beforeEach(() => {
runCommandWithTimeoutMock.mockReset();
});
afterEach(async () => {
while (tempDirs.length > 0) {
const dir = tempDirs.pop();
if (!dir) {
break;
}
await fs.rm(dir, { recursive: true, force: true });
}
});
describe("withTempDir", () => {
it("creates a temp dir and always removes it after callback", async () => {
let observedDir = "";
const markerFile = "marker.txt";
const value = await withTempDir("openclaw-install-source-utils-", async (tmpDir) => {
observedDir = tmpDir;
await fs.writeFile(path.join(tmpDir, markerFile), "ok", "utf-8");
await expect(fs.stat(path.join(tmpDir, markerFile))).resolves.toBeDefined();
return "done";
});
expect(value).toBe("done");
await expect(fs.stat(observedDir)).rejects.toThrow();
});
});
describe("resolveArchiveSourcePath", () => {
it("returns not found error for missing archive paths", async () => {
const result = await resolveArchiveSourcePath("/tmp/does-not-exist-openclaw-archive.tgz");
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error).toContain("archive not found");
}
});
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 result = await resolveArchiveSourcePath(filePath);
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error).toContain("unsupported archive");
}
});
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 result = await resolveArchiveSourcePath(filePath);
expect(result).toEqual({ ok: true, path: filePath });
});
});
describe("packNpmSpecToArchive", () => {
it("packs spec and returns archive path using the final non-empty stdout line", async () => {
const cwd = await createTempDir("openclaw-install-source-utils-");
runCommandWithTimeoutMock.mockResolvedValue({
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,
});
expect(result).toEqual({
ok: true,
archivePath: path.join(cwd, "openclaw-plugin-1.2.3.tgz"),
});
expect(runCommandWithTimeoutMock).toHaveBeenCalledWith(
["npm", "pack", "openclaw-plugin@1.2.3", "--ignore-scripts"],
expect.objectContaining({
cwd,
timeoutMs: 300_000,
}),
);
});
it("returns npm pack error details when command fails", async () => {
const cwd = await createTempDir("openclaw-install-source-utils-");
runCommandWithTimeoutMock.mockResolvedValue({
stdout: "fallback stdout",
stderr: "registry timeout",
code: 1,
signal: null,
killed: false,
});
const result = await packNpmSpecToArchive({
spec: "bad-spec",
timeoutMs: 5000,
cwd,
});
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error).toContain("npm pack failed");
expect(result.error).toContain("registry timeout");
}
});
it("returns explicit error when npm pack produces no archive name", async () => {
const cwd = await createTempDir("openclaw-install-source-utils-");
runCommandWithTimeoutMock.mockResolvedValue({
stdout: " \n\n",
stderr: "",
code: 0,
signal: null,
killed: false,
});
const result = await packNpmSpecToArchive({
spec: "openclaw-plugin@1.2.3",
timeoutMs: 5000,
cwd,
});
expect(result).toEqual({
ok: false,
error: "npm pack produced no archive",
});
});
});