diff --git a/packages/cli-json-cas/src/cli.test.ts b/packages/cli-json-cas/src/cli.test.ts index 90146d5..51186cf 100644 --- a/packages/cli-json-cas/src/cli.test.ts +++ b/packages/cli-json-cas/src/cli.test.ts @@ -315,6 +315,207 @@ describe("ucas render command", () => { }); }); +describe("Schema Hash Validation in put command", () => { + test("put with non-existent literal hash should fail", async () => { + await runCliAlias("init"); + + const payloadFile = join(testDir, "payload.json"); + writeFileSync(payloadFile, JSON.stringify({ test: "data" })); + + const { stderr, exitCode } = await runCliAlias( + "put", + "AAAAAAAAAAAAA", + payloadFile, + ); + + expect(exitCode).not.toBe(0); + expect(stderr).toContain("Schema not found: AAAAAAAAAAAAA"); + }); + + test("put with different non-existent literal hash should fail", async () => { + await runCliAlias("init"); + + const payloadFile = join(testDir, "payload.json"); + writeFileSync(payloadFile, JSON.stringify({ test: "data" })); + + const { stderr, exitCode } = await runCliAlias( + "put", + "ZZZZZZZZZZZZZ", + payloadFile, + ); + + expect(exitCode).not.toBe(0); + expect(stderr).toContain("Schema not found: ZZZZZZZZZZZZZ"); + }); + + test("put with malformed hash should fail", async () => { + await runCliAlias("init"); + + const payloadFile = join(testDir, "payload.json"); + writeFileSync(payloadFile, JSON.stringify({ test: "data" })); + + const { stderr, exitCode } = await runCliAlias( + "put", + "INVALID_HASH", + payloadFile, + ); + + expect(exitCode).not.toBe(0); + expect(stderr.length).toBeGreaterThan(0); + }); + + test("put with non-existent @alias should fail with clear message", async () => { + await runCliAlias("init"); + + const payloadFile = join(testDir, "payload.json"); + writeFileSync(payloadFile, JSON.stringify({ test: "data" })); + + const { stderr, exitCode } = await runCliAlias( + "put", + "@nonexistent", + payloadFile, + ); + + expect(exitCode).not.toBe(0); + expect(stderr).toContain("Schema not found"); + expect(stderr).toContain("@nonexistent"); + }); + + test("put with valid @string alias should succeed (regression)", async () => { + await runCliAlias("init"); + + const payloadFile = join(testDir, "payload.json"); + writeFileSync(payloadFile, JSON.stringify("hello")); + + const { stdout, stderr, exitCode } = await runCliAlias( + "put", + "@string", + payloadFile, + ); + + expect(exitCode).toBe(0); + expect(stderr).toBe(""); + expect(stdout).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/); + }); + + test("put with valid @number alias should succeed (regression)", async () => { + await runCliAlias("init"); + + const payloadFile = join(testDir, "payload.json"); + writeFileSync(payloadFile, "42"); + + const { stdout, stderr, exitCode } = await runCliAlias( + "put", + "@number", + payloadFile, + ); + + expect(exitCode).toBe(0); + expect(stderr).toBe(""); + expect(stdout).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/); + }); + + test("put with valid @object alias should succeed (regression)", async () => { + await runCliAlias("init"); + + const payloadFile = join(testDir, "payload.json"); + writeFileSync(payloadFile, JSON.stringify({ key: "value" })); + + const { stdout, stderr, exitCode } = await runCliAlias( + "put", + "@object", + payloadFile, + ); + + expect(exitCode).toBe(0); + expect(stderr).toBe(""); + expect(stdout).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/); + }); + + test("put with explicit valid schema hash should succeed (regression)", async () => { + await runCliAlias("init"); + + // First, create a custom schema + const schemaFile = join(testDir, "schema.json"); + writeFileSync( + schemaFile, + JSON.stringify({ + type: "object", + properties: { name: { type: "string" } }, + }), + ); + const { stdout: schemaHash } = await runCliAlias( + "schema", + "put", + schemaFile, + ); + + // Now create a node with that schema + const payloadFile = join(testDir, "payload.json"); + writeFileSync(payloadFile, JSON.stringify({ name: "test" })); + + const { stdout, stderr, exitCode } = await runCliAlias( + "put", + schemaHash.trim(), + payloadFile, + ); + + expect(exitCode).toBe(0); + expect(stderr).toBe(""); + expect(stdout).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/); + }); + + test("all bootstrap schema aliases should work (regression)", async () => { + await runCliAlias("init"); + + const aliases = ["@string", "@number", "@object", "@array", "@bool"]; + const payloads = [ + JSON.stringify("test"), + "123", + JSON.stringify({}), + JSON.stringify([]), + "true", + ]; + + for (let i = 0; i < aliases.length; i++) { + const payloadFile = join(testDir, `payload-${i}.json`); + writeFileSync(payloadFile, payloads[i] ?? ""); + + const { exitCode } = await runCliAlias( + "put", + aliases[i] ?? "", + payloadFile, + ); + + expect(exitCode).toBe(0); + } + }); + + test("error message should preserve original input (hash)", async () => { + await runCliAlias("init"); + + const payloadFile = join(testDir, "payload.json"); + writeFileSync(payloadFile, JSON.stringify({ test: "data" })); + + const { stderr } = await runCliAlias("put", "AAAAAAAAAAAAA", payloadFile); + + // Error should show the original hash, not a resolved version + expect(stderr).toContain("AAAAAAAAAAAAA"); + }); + + test("error message should preserve original input (alias)", async () => { + await runCliAlias("init"); + + const payloadFile = join(testDir, "payload.json"); + writeFileSync(payloadFile, JSON.stringify({ test: "data" })); + + const { stderr } = await runCliAlias("put", "@invalid", payloadFile); + + // Error should show the original alias, not a resolved hash + expect(stderr).toContain("@invalid"); + }); +}); + describe("Suite 6: CLI Integration with Templates", () => { test("6.1 CLI with Template (Default Parameters)", async () => { const tmpStore = mkdtempSync(join(tmpdir(), "json-cas-test-")); diff --git a/packages/cli-json-cas/src/index.ts b/packages/cli-json-cas/src/index.ts index 51bdf4d..158e6a1 100644 --- a/packages/cli-json-cas/src/index.ts +++ b/packages/cli-json-cas/src/index.ts @@ -294,6 +294,12 @@ async function cmdPut(args: string[]): Promise { const typeHash = await resolveTypeHash(typeHashOrAlias); const payload = readJsonFile(file); const store = openStore(); + + // Check if schema exists before storing + if (!store.has(typeHash)) { + die(`Schema not found: ${typeHashOrAlias}`); + } + const hash = await store.put(typeHash, payload); console.log(hash); }