Files
PaperClipAI/cli/src/__tests__/install-command.test.ts
T
2026-07-22 19:38:47 +00:00

89 lines
4.0 KiB
TypeScript

import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { installCommand, resolveNpmInstallRequest } from "../commands/install.js";
import { uninstallCommand } from "../commands/uninstall.js";
import { readInstallManifest, resolveInstallStorePaths } from "../install-store.js";
import { resolveCliVersion } from "../version.js";
const ORIGINAL_ENV = { ...process.env };
describe("managed install commands", () => {
let root: string;
beforeEach(() => {
root = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-install-command-"));
process.env = {
...ORIGINAL_ENV,
HOME: path.join(root, "home"),
PAPERCLIP_HOME: path.join(root, "home", ".paperclip"),
PATH: "/usr/bin:/bin",
SHELL: "/bin/bash",
};
fs.mkdirSync(process.env.HOME!, { recursive: true });
vi.spyOn(console, "log").mockImplementation(() => undefined);
});
afterEach(() => {
vi.restoreAllMocks();
process.env = { ...ORIGINAL_ENV };
fs.rmSync(root, { recursive: true, force: true });
});
it("selects stable, canary, and exact-version npm sources", () => {
expect(resolveNpmInstallRequest({})).toEqual({ spec: "latest", channel: "latest" });
expect(resolveNpmInstallRequest({ canary: true })).toEqual({ spec: "canary", channel: "canary" });
expect(resolveNpmInstallRequest({ version: "2026.720.0" })).toEqual({
spec: "2026.720.0",
channel: "pinned",
});
expect(() => resolveNpmInstallRequest({ canary: true, version: "1.2.3" })).toThrow();
expect(() => resolveNpmInstallRequest({ version: "latest" })).toThrow();
});
it("installs through the shim, reports provenance, and uninstalls without deleting user data", async () => {
const version = "2026.720.0";
const runCommand = vi.fn(async (file: string, args: string[], _options?: unknown) => {
if (file === "npm" && args[0] === "view") return { stdout: JSON.stringify(version), stderr: "" };
if (file === "npm" && args[0] === "install") {
const prefix = args[args.indexOf("--prefix") + 1];
const entrypoint = path.join(prefix, "node_modules", "paperclipai", "dist", "index.js");
fs.mkdirSync(path.dirname(entrypoint), { recursive: true });
fs.writeFileSync(entrypoint, "#!/usr/bin/env node\n");
return { stdout: "", stderr: "" };
}
if (file === process.execPath && args.at(-1) === "--version") {
return { stdout: `${version}\n`, stderr: "" };
}
throw new Error(`Unexpected command: ${file} ${args.join(" ")}`);
});
await installCommand({}, { runCommand, now: () => new Date("2026-07-22T18:00:00.000Z") });
const paths = resolveInstallStorePaths();
const manifest = readInstallManifest(paths);
expect(manifest?.version).toBe(version);
expect(manifest?.channel).toBe("latest");
expect(fs.realpathSync(paths.currentPath)).toBe(fs.realpathSync(manifest!.payloadPath));
expect(fs.existsSync(paths.shimPath)).toBe(true);
const installCall = runCommand.mock.calls.find(
([file, args]) => file === "npm" && args[0] === "install",
);
expect(installCall?.[1]).toContain("--@paperclipai:registry=https://registry.npmjs.org");
const installOptions = installCall?.[2] as { env?: NodeJS.ProcessEnv } | undefined;
expect(installOptions?.env?.npm_config_userconfig).toContain(".npmrc-");
const entrypoint = path.join(manifest!.payloadPath, "node_modules", "paperclipai", "dist", "index.js");
expect(resolveCliVersion(entrypoint)).toContain(`managed npm latest; payload ${manifest!.payloadPath}`);
const userData = path.join(process.env.PAPERCLIP_HOME!, "instances", "default", "keep.txt");
fs.mkdirSync(path.dirname(userData), { recursive: true });
fs.writeFileSync(userData, "keep");
await uninstallCommand();
expect(fs.existsSync(paths.cliRoot)).toBe(false);
expect(fs.existsSync(paths.shimPath)).toBe(false);
expect(fs.readFileSync(userData, "utf8")).toBe("keep");
});
});