Merge pull request 'fix: cas has now returns exit 1 for non-existent hashes' (#482) from fix/481-cas-has-exit-code into main
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
import { execSync } from "node:child_process";
|
||||
import { mkdir, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
||||
import { cmdCasPutText } from "../commands/cas.js";
|
||||
|
||||
let storageRoot: string;
|
||||
let uwfPath: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
storageRoot = join(
|
||||
tmpdir(),
|
||||
`uwf-cas-exit-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
||||
);
|
||||
await mkdir(storageRoot, { recursive: true });
|
||||
|
||||
// Find the uwf CLI path
|
||||
uwfPath = join(__dirname, "../../src/cli.ts");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await rm(storageRoot, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
type ExecResult = {
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
exitCode: number;
|
||||
};
|
||||
|
||||
function execUwf(args: string[]): ExecResult {
|
||||
try {
|
||||
const stdout = execSync(`bun ${uwfPath} ${args.join(" ")}`, {
|
||||
env: { ...process.env, WORKFLOW_STORAGE_ROOT: storageRoot },
|
||||
encoding: "utf-8",
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
return { stdout, stderr: "", exitCode: 0 };
|
||||
} catch (error: unknown) {
|
||||
if (
|
||||
error &&
|
||||
typeof error === "object" &&
|
||||
"stdout" in error &&
|
||||
"stderr" in error &&
|
||||
"status" in error
|
||||
) {
|
||||
return {
|
||||
stdout: (error.stdout as Buffer | string).toString(),
|
||||
stderr: (error.stderr as Buffer | string).toString(),
|
||||
exitCode: error.status as number,
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
describe("uwf cas has CLI exit codes", () => {
|
||||
test("exits 0 when hash exists", async () => {
|
||||
// Setup: Create a temp storage root, put a text node, capture hash
|
||||
const putResult = await cmdCasPutText(storageRoot, "test content");
|
||||
const hash = putResult.hash;
|
||||
|
||||
// Execute: uwf cas has <hash>
|
||||
const result = execUwf(["cas", "has", hash]);
|
||||
|
||||
// Assert: stdout contains {"exists":true}, exit code === 0
|
||||
expect(result.stdout).toContain('"exists":true');
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test("exits 1 when hash does not exist", () => {
|
||||
// Setup: Create a temp storage root (empty CAS store)
|
||||
// Execute: uwf cas has NOSUCHHASH123
|
||||
const result = execUwf(["cas", "has", "NOSUCHHASH123"]);
|
||||
|
||||
// Assert: stdout contains {"exists":false}, exit code === 1
|
||||
expect(result.stdout).toContain('"exists":false');
|
||||
expect(result.exitCode).toBe(1);
|
||||
});
|
||||
|
||||
test("JSON output format unchanged for exists=true", async () => {
|
||||
// Setup: Create store, put node
|
||||
const putResult = await cmdCasPutText(storageRoot, "test");
|
||||
const hash = putResult.hash;
|
||||
|
||||
// Execute: uwf cas has <hash>
|
||||
const result = execUwf(["cas", "has", hash]);
|
||||
|
||||
// Assert: stdout JSON parses correctly to {exists: true}
|
||||
const parsed = JSON.parse(result.stdout.trim());
|
||||
expect(parsed).toEqual({ exists: true });
|
||||
});
|
||||
|
||||
test("JSON output format unchanged for exists=false", () => {
|
||||
// Setup: Create empty store
|
||||
// Execute: uwf cas has INVALID
|
||||
const result = execUwf(["cas", "has", "INVALID"]);
|
||||
|
||||
// Assert: stdout JSON parses correctly to {exists: false}
|
||||
const parsed = JSON.parse(result.stdout.trim());
|
||||
expect(parsed).toEqual({ exists: false });
|
||||
});
|
||||
|
||||
test("YAML output format preserves exit code behavior for exists=true", async () => {
|
||||
// Setup: Create store with node
|
||||
const putResult = await cmdCasPutText(storageRoot, "test");
|
||||
const hash = putResult.hash;
|
||||
|
||||
// Execute: uwf --format yaml cas has <hash>
|
||||
const result = execUwf(["--format", "yaml", "cas", "has", hash]);
|
||||
|
||||
// Assert: exit code === 0, output is YAML format
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.stdout).toContain("exists:");
|
||||
expect(result.stdout).toContain("true");
|
||||
});
|
||||
|
||||
test("YAML output format preserves exit code behavior for exists=false", () => {
|
||||
// Setup: Create empty store
|
||||
// Execute: uwf --format yaml cas has INVALID
|
||||
const result = execUwf(["--format", "yaml", "cas", "has", "INVALID"]);
|
||||
|
||||
// Assert: exit code === 1, output is YAML format
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.stdout).toContain("exists:");
|
||||
expect(result.stdout).toContain("false");
|
||||
});
|
||||
});
|
||||
|
||||
describe("regression: other cas commands unaffected", () => {
|
||||
test("uwf cas get still exits 1 on not-found with error message", () => {
|
||||
// Execute: uwf cas get NOSUCHHASH
|
||||
const result = execUwf(["cas", "get", "NOSUCHHASH"]);
|
||||
|
||||
// Assert: exit code === 1, stderr contains "Node not found"
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.stderr).toContain("Node not found");
|
||||
});
|
||||
|
||||
test("uwf cas put-text behavior unchanged", () => {
|
||||
// Execute: uwf cas put-text "hello"
|
||||
const result = execUwf(["cas", "put-text", "hello"]);
|
||||
|
||||
// Assert: exit code === 0, returns hash
|
||||
expect(result.exitCode).toBe(0);
|
||||
const parsed = JSON.parse(result.stdout.trim());
|
||||
expect(parsed).toHaveProperty("hash");
|
||||
expect(typeof parsed.hash).toBe("string");
|
||||
expect(parsed.hash.length).toBe(13); // Crockford Base32 XXH64 hash length
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
import { mkdir, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
||||
import { cmdCasHas, cmdCasPutText } from "../commands/cas.js";
|
||||
|
||||
let storageRoot: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
storageRoot = join(tmpdir(), `uwf-cas-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
||||
await mkdir(storageRoot, { recursive: true });
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await rm(storageRoot, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe("cmdCasHas", () => {
|
||||
test("returns {exists: true} for existing hash", async () => {
|
||||
// Setup: Create a test store, put a node, get its hash
|
||||
const putResult = await cmdCasPutText(storageRoot, "test content");
|
||||
const hash = putResult.hash;
|
||||
|
||||
// Execute: Call cmdCasHas with the valid hash
|
||||
const result = await cmdCasHas(storageRoot, hash);
|
||||
|
||||
// Assert: Result equals {exists: true}
|
||||
expect(result).toEqual({ exists: true });
|
||||
});
|
||||
|
||||
test("returns {exists: false} for non-existent hash", async () => {
|
||||
// Setup: Create an empty test store
|
||||
// (storageRoot already created in beforeEach)
|
||||
|
||||
// Execute: Call cmdCasHas with an invalid hash
|
||||
const result = await cmdCasHas(storageRoot, "INVALIDHASH12");
|
||||
|
||||
// Assert: Result equals {exists: false}
|
||||
expect(result).toEqual({ exists: false });
|
||||
});
|
||||
|
||||
test("does not throw for non-existent hash", async () => {
|
||||
// Setup: Create an empty test store
|
||||
// Execute & Assert: Does not throw, returns {exists: false}
|
||||
await expect(cmdCasHas(storageRoot, "NOSUCHHASH123")).resolves.toEqual({
|
||||
exists: false,
|
||||
});
|
||||
});
|
||||
|
||||
test("handles malformed hash gracefully", async () => {
|
||||
// Setup: Create a test store
|
||||
// Execute: Call cmdCasHas with a too-short hash
|
||||
const result = await cmdCasHas(storageRoot, "xyz");
|
||||
|
||||
// Assert: Returns {exists: false} (store.has() returns false)
|
||||
expect(result).toEqual({ exists: false });
|
||||
});
|
||||
|
||||
test("handles empty hash string", async () => {
|
||||
// Execute: Call cmdCasHas with an empty string
|
||||
const result = await cmdCasHas(storageRoot, "");
|
||||
|
||||
// Assert: Returns {exists: false}
|
||||
expect(result).toEqual({ exists: false });
|
||||
});
|
||||
|
||||
test("handles hash with special characters", async () => {
|
||||
// Execute: Call cmdCasHas with special characters
|
||||
const result = await cmdCasHas(storageRoot, "HASH!@#");
|
||||
|
||||
// Assert: Returns {exists: false}
|
||||
expect(result).toEqual({ exists: false });
|
||||
});
|
||||
});
|
||||
@@ -549,7 +549,11 @@ cas
|
||||
.action((hash: string) => {
|
||||
const storageRoot = resolveStorageRoot();
|
||||
runAction(async () => {
|
||||
writeOutput(await cmdCasHas(storageRoot, hash));
|
||||
const result = await cmdCasHas(storageRoot, hash);
|
||||
writeOutput(result);
|
||||
if (!result.exists) {
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user