fix: clean error message for invalid schema in schema put command

Fixes #54

The `json-cas schema put` command now catches SchemaValidationError
and displays a clean error message instead of showing a raw stack trace.

Changes:
- Import SchemaValidationError from @uncaged/json-cas
- Wrap putSchema() call in try-catch in cmdSchemaPut
- Catch SchemaValidationError specifically and call die(e.message)
- Add 5 comprehensive tests for invalid schema error handling

Test cases:
1. Invalid JSON Schema type value
2. Unknown schema keys
3. Invalid nested schema
4. Non-object root schema
5. Valid schema regression test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-31 08:14:23 +00:00
parent 2932aa5980
commit 3c8b16d7b1
2 changed files with 144 additions and 2 deletions
+134
View File
@@ -597,3 +597,137 @@ describe("Suite 6: CLI Integration with Templates", () => {
}
});
});
// ---- schema put - invalid schema error handling ----
describe("schema put - invalid schema error handling", () => {
test("invalid schema - unknown type value shows clean error", async () => {
const tmpStore = mkdtempSync(join(tmpdir(), "json-cas-test-"));
try {
await runCli(["init"], tmpStore);
const schemaFile = join(tmpStore, "invalid-schema.json");
writeFileSync(schemaFile, JSON.stringify({ type: "invalid" }));
const { exitCode, stderr, stdout } = await runCli(
["schema", "put", schemaFile],
tmpStore,
);
expect(exitCode).not.toBe(0);
expect(stderr).toContain("Invalid schema");
expect(stderr).not.toContain("at ");
expect(stdout).toBe("");
} finally {
rmSync(tmpStore, { recursive: true, force: true });
}
});
test("invalid schema - unknown key shows clean error", async () => {
const tmpStore = mkdtempSync(join(tmpdir(), "json-cas-test-"));
try {
await runCli(["init"], tmpStore);
const schemaFile = join(tmpStore, "invalid-schema.json");
writeFileSync(
schemaFile,
JSON.stringify({ type: "string", unknownKey: true }),
);
const { exitCode, stderr, stdout } = await runCli(
["schema", "put", schemaFile],
tmpStore,
);
expect(exitCode).not.toBe(0);
expect(stderr).toContain("Invalid schema");
expect(stderr).not.toContain("at ");
expect(stdout).toBe("");
} finally {
rmSync(tmpStore, { recursive: true, force: true });
}
});
test("invalid schema - invalid nested schema shows clean error", async () => {
const tmpStore = mkdtempSync(join(tmpdir(), "json-cas-test-"));
try {
await runCli(["init"], tmpStore);
const schemaFile = join(tmpStore, "invalid-schema.json");
writeFileSync(
schemaFile,
JSON.stringify({
type: "object",
properties: {
name: { type: "invalid" },
},
}),
);
const { exitCode, stderr, stdout } = await runCli(
["schema", "put", schemaFile],
tmpStore,
);
expect(exitCode).not.toBe(0);
expect(stderr).toContain("Invalid schema");
expect(stderr).not.toContain("at ");
expect(stdout).toBe("");
} finally {
rmSync(tmpStore, { recursive: true, force: true });
}
});
test("invalid schema - non-object root shows clean error", async () => {
const tmpStore = mkdtempSync(join(tmpdir(), "json-cas-test-"));
try {
await runCli(["init"], tmpStore);
const schemaFile = join(tmpStore, "invalid-schema.json");
writeFileSync(schemaFile, JSON.stringify(["type", "string"]));
const { exitCode, stderr, stdout } = await runCli(
["schema", "put", schemaFile],
tmpStore,
);
expect(exitCode).not.toBe(0);
expect(stderr).toContain("Invalid schema");
expect(stderr).not.toContain("at ");
expect(stdout).toBe("");
} finally {
rmSync(tmpStore, { recursive: true, force: true });
}
});
test("valid schema still works (regression)", async () => {
const tmpStore = mkdtempSync(join(tmpdir(), "json-cas-test-"));
try {
await runCli(["init"], tmpStore);
const schemaFile = join(tmpStore, "valid-schema.json");
writeFileSync(
schemaFile,
JSON.stringify({
type: "object",
properties: {
name: { type: "string" },
age: { type: "number" },
},
required: ["name"],
}),
);
const { exitCode, stderr, stdout } = await runCli(
["schema", "put", schemaFile],
tmpStore,
);
expect(exitCode).toBe(0);
expect(stderr).toBe("");
expect(stdout.trim()).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
} finally {
rmSync(tmpStore, { recursive: true, force: true });
}
});
});
+10 -2
View File
@@ -17,6 +17,7 @@ import {
refs,
renderAsync,
renderDirect,
SchemaValidationError,
TagLabelConflictError,
VariableNotFoundError,
validate,
@@ -243,8 +244,15 @@ async function cmdSchemaPut(args: string[]): Promise<void> {
if (!file) die("Usage: json-cas schema put <file.json>");
const schema = readJsonFile(file) as JSONSchema;
const store = openStore();
const hash = await putSchema(store, schema);
console.log(hash);
try {
const hash = await putSchema(store, schema);
console.log(hash);
} catch (e) {
if (e instanceof SchemaValidationError) {
die(e.message);
}
throw e;
}
}
async function cmdSchemaGet(args: string[]): Promise<void> {