Files
skills-vault/test/cli.test.ts
T

144 lines
4.2 KiB
TypeScript

import { expect, test } from "bun:test";
import { existsSync, readFileSync } from "node:fs";
import { resolve } from "node:path";
import { runCli } from "../src/cli";
const repoRoot = resolve(import.meta.dir, "..");
const packageJson = JSON.parse(
readFileSync(resolve(repoRoot, "package.json"), "utf8"),
) as {
bin?: Record<string, string>;
version?: string;
};
function normalizeBinTarget(target: string | undefined) {
return target?.replace(/^[.][/\\]/, "");
}
test("declares a working skvlt bin target", async () => {
expect(normalizeBinTarget(packageJson.bin?.skvlt)).toBe("src/cli.ts");
const targetPath = resolve(repoRoot, packageJson.bin!.skvlt);
expect(existsSync(targetPath)).toBe(true);
const result = Bun.spawnSync(
[process.execPath, targetPath, "backup", "--help"],
{
cwd: repoRoot,
},
);
expect(result.exitCode).toBe(0);
});
test("dispatches the restore command", async () => {
const result = await runCli(["restore", "--help"]);
expect(result.exitCode).toBe(0);
});
test("dispatches the backup command", async () => {
const result = await runCli(["backup", "--help"]);
expect(result.exitCode).toBe(0);
});
test("dispatches the doctor command", async () => {
const result = await runCli(["doctor", "--help"]);
expect(result.exitCode).toBe(0);
});
test("dispatches the completion command", async () => {
const result = await runCli(["completion", "--help"]);
expect(result.exitCode).toBe(0);
});
test("prints top-level help", async () => {
const result = await runCli(["--help"]);
expect(result.exitCode).toBe(0);
expect(result.stdout).toContain("Skills Vault CLI");
expect(result.stdout).toContain("Usage:");
expect(result.stdout).toContain("skvlt <command> [options]");
expect(result.stdout).toContain("backup");
expect(result.stdout).toContain("completion");
expect(result.stdout).toContain("doctor");
expect(result.stdout).toContain("restore");
expect(result.stderr).toBe("");
});
test("prints the package version", async () => {
const result = await runCli(["--version"]);
expect(result.exitCode).toBe(0);
expect(result.stdout.trim()).toBe(packageJson.version);
expect(result.stderr).toBe("");
});
test("prints the package version as JSON", async () => {
const result = await runCli(["--json", "--version"]);
const payload = JSON.parse(result.stdout) as {
ok: boolean;
command: string;
data: { version: string };
};
expect(result.exitCode).toBe(0);
expect(result.stderr).toBe("");
expect(payload).toEqual({
ok: true,
command: "version",
data: { version: packageJson.version! },
});
});
test("rejects unknown commands with guidance", async () => {
const result = await runCli(["sync"]);
expect(result.exitCode).toBe(1);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("Unknown command: sync");
expect(result.stderr).toContain("Run 'skvlt --help'");
});
test("returns JSON errors for unknown commands", async () => {
const result = await runCli(["sync", "--json"]);
const payload = JSON.parse(result.stdout) as {
ok: boolean;
command: string;
error: { code: string; message: string };
};
expect(result.exitCode).toBe(1);
expect(result.stderr).toBe("");
expect(payload.ok).toBe(false);
expect(payload.command).toBe("sync");
expect(payload.error.code).toBe("UNKNOWN_COMMAND");
expect(payload.error.message).toContain("Unknown command: sync");
});
test("uses process argv when invoked like the CLI", async () => {
const { main } = await import("../src/cli");
const processRef = process as unknown as { argv: string[] };
const consoleRef = console as unknown as {
error: (...args: unknown[]) => void;
};
const originalArgv = processRef.argv;
const originalError = consoleRef.error;
let usageText = "";
try {
processRef.argv = ["bun", "/tmp/skvlt/src/cli.ts"];
consoleRef.error = (...args: unknown[]) => {
usageText = args.join(" ");
};
const exitCode = await main();
expect(exitCode).toBe(1);
expect(usageText).toContain("Usage:");
expect(usageText).toContain("skvlt <command> [options]");
} finally {
processRef.argv = originalArgv;
consoleRef.error = originalError;
}
});