92 lines
3.4 KiB
TypeScript
92 lines
3.4 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { NULL_INPUT_VALUE, parseSchemaArguments } from "../src/schema-input";
|
|
import type { ToolDefinition } from "../src/contracts";
|
|
|
|
const schema = (properties: NonNullable<ToolDefinition["inputSchema"]["properties"]>, required: string[] = []): ToolDefinition["inputSchema"] => ({
|
|
type: "object",
|
|
properties,
|
|
required,
|
|
});
|
|
|
|
describe("schema-driven tool arguments", () => {
|
|
it("parses strings, integers, decimals, booleans, enums, arrays and objects by declared type", () => {
|
|
const inputSchema = schema({
|
|
query: { type: "string" },
|
|
limit: { type: "integer", minimum: 1, maximum: 20 },
|
|
score: { type: "number" },
|
|
includeClosed: { type: "boolean" },
|
|
format: { type: "string", enum: ["compact", "full"] },
|
|
labels: { type: "array" },
|
|
filters: { type: "object" },
|
|
}, ["query", "limit", "includeClosed", "format"]);
|
|
|
|
expect(parseSchemaArguments({
|
|
query: "pull request",
|
|
limit: "4",
|
|
score: "3.5",
|
|
includeClosed: "false",
|
|
format: JSON.stringify("full"),
|
|
labels: "[\"bug\",\"urgent\"]",
|
|
filters: "{\"author\":\"jens\"}",
|
|
}, inputSchema)).toEqual({
|
|
arguments: {
|
|
query: "pull request",
|
|
limit: 4,
|
|
score: 3.5,
|
|
includeClosed: false,
|
|
format: "full",
|
|
labels: ["bug", "urgent"],
|
|
filters: { author: "jens" },
|
|
},
|
|
errors: {},
|
|
valid: true,
|
|
});
|
|
});
|
|
|
|
it("reports missing required values, malformed JSON and type mismatches clearly", () => {
|
|
const inputSchema = schema({
|
|
count: { type: "integer" },
|
|
enabled: { type: "boolean" },
|
|
tags: { type: "array" },
|
|
options: { type: "object" },
|
|
}, ["count", "enabled", "tags", "options"]);
|
|
|
|
const parsed = parseSchemaArguments({ count: "2.3", enabled: "sometimes", tags: "{oops", options: "[]" }, inputSchema);
|
|
|
|
expect(parsed.valid).toBe(false);
|
|
expect(parsed.arguments).toEqual({});
|
|
expect(parsed.errors).toEqual({
|
|
count: "Enter a whole number.",
|
|
enabled: "Choose yes or no.",
|
|
tags: "Enter valid JSON for this array.",
|
|
options: "Enter a JSON object.",
|
|
});
|
|
});
|
|
|
|
it("applies string and numeric bounds and rejects enum values outside the schema", () => {
|
|
const inputSchema = schema({
|
|
query: { type: "string", minLength: 2, maxLength: 5 },
|
|
limit: { type: "integer", minimum: 1, maximum: 8 },
|
|
mode: { type: "string", enum: ["safe", "full"] },
|
|
});
|
|
|
|
const parsed = parseSchemaArguments({ query: "x", limit: "9", mode: JSON.stringify("unsafe") }, inputSchema);
|
|
|
|
expect(parsed.valid).toBe(false);
|
|
expect(parsed.errors.query).toBe("Enter at least 2 characters.");
|
|
expect(parsed.errors.limit).toBe("Enter a number no greater than 8.");
|
|
expect(parsed.errors.mode).toBe("Choose a value listed by this tool's schema.");
|
|
});
|
|
|
|
it("omits empty optional fields and accepts explicit null only for nullable fields", () => {
|
|
const inputSchema = schema({ optional: { type: "string" }, nullable: { type: ["string", "null"] } });
|
|
|
|
expect(parseSchemaArguments({ optional: "", nullable: NULL_INPUT_VALUE }, inputSchema)).toEqual({
|
|
arguments: { nullable: null },
|
|
errors: {},
|
|
valid: true,
|
|
});
|
|
expect(parseSchemaArguments({ nullable: NULL_INPUT_VALUE }, schema({ nullable: { type: "string" } })).errors.nullable).toBe("This field does not accept null.");
|
|
});
|
|
});
|