import { describe, expect, it } from "vitest"; import { validateToolParameters, validateTools } from "./tools.js"; const goodObject = { type: "object", properties: { query: { type: "string", description: "search text" }, limit: { type: "integer" }, tags: { type: "array", items: { type: "string" } }, meta: { type: "object", properties: { id: { type: "string" } }, required: ["id"] }, }, required: ["query"], }; describe("validateToolParameters", () => { it("accepts the strict subset (object root, nested, array items, enum)", () => { expect(validateToolParameters(goodObject).ok).toBe(true); expect( validateToolParameters({ type: "object", properties: { kind: { type: "string", enum: ["a", "b"] }, flag: { type: "boolean" }, n: { type: "number" } }, }).ok, ).toBe(true); }); it("rejects a non-object root", () => { expect(validateToolParameters("nope").ok).toBe(false); expect(validateToolParameters(null).ok).toBe(false); expect(validateToolParameters([1]).ok).toBe(false); }); it("rejects forbidden keywords (pattern, $ref, anyOf, minLength)", () => { expect(validateToolParameters({ type: "object", pattern: "x" }).ok).toBe(false); expect(validateToolParameters({ $ref: "#/foo" }).ok).toBe(false); expect(validateToolParameters({ anyOf: [{}] }).ok).toBe(false); expect(validateToolParameters({ type: "string", minLength: 1 }).ok).toBe(false); }); it("rejects unknown types", () => { expect(validateToolParameters({ type: "null" }).ok).toBe(false); expect(validateToolParameters({ type: "date" }).ok).toBe(false); }); it("array requires items", () => { expect(validateToolParameters({ type: "array" }).ok).toBe(false); expect(validateToolParameters({ type: "array", items: { type: "string" } }).ok).toBe(true); }); it("nested properties are validated recursively", () => { expect( validateToolParameters({ type: "object", properties: { inner: { type: "object", properties: { bad: { pattern: "x" } } } }, }).ok, ).toBe(false); }); it("required must reference existing properties", () => { expect( validateToolParameters({ type: "object", properties: { a: { type: "string" } }, required: ["zzz"] }).ok, ).toBe(false); }); it("enum must be a non-empty array", () => { expect(validateToolParameters({ type: "string", enum: [] }).ok).toBe(false); }); it("error messages carry the path", () => { const r = validateToolParameters({ type: "object", properties: { a: { pattern: "x" } } }); expect(r.ok).toBe(false); expect(r.error).toContain("properties.a"); }); }); describe("validateTools", () => { it("accepts a valid tool list", () => { expect( validateTools([ { name: "search", description: "search the docs", parameters: goodObject }, { name: "get-page_2", description: "get a page", parameters: { type: "object" } }, ]).ok, ).toBe(true); }); it("rejects bad tool names", () => { expect(validateTools([{ name: "has space", description: "d", parameters: { type: "object" } }]).ok).toBe(false); expect(validateTools([{ name: "", description: "d", parameters: { type: "object" } }]).ok).toBe(false); }); it("rejects duplicate names", () => { expect( validateTools([ { name: "x", description: "d", parameters: { type: "object" } }, { name: "x", description: "d", parameters: { type: "object" } }, ]).ok, ).toBe(false); }); it("requires a non-empty description", () => { expect(validateTools([{ name: "x", description: "", parameters: { type: "object" } }]).ok).toBe(false); }); it("propagates parameter errors with the tool name", () => { const r = validateTools([{ name: "x", description: "d", parameters: { pattern: "bad" } }]); expect(r.ok).toBe(false); expect(r.error).toContain("tool.x.parameters"); }); });