// Run with `bun test`.
import assert from "node:assert/strict";
import { test } from "node:test";
import { array, boolean, integer, object, objectSchema, oneOf, parseArgs, string, ToolError } from "./params";
const props = {
owner: string("Owner.", { min: 1, max: 39 }),
limit: integer("Limit.", { min: 1, max: 50 }).default(20),
state: oneOf("State.", ["open", "closed"] as const).optional(),
files: array("Files.", object("A file.", { path: string("Path."), binary: boolean("Binary.").default(false) })).default([]),
};
test("parses arguments, filling in defaults", () => {
assert.deepEqual(parseArgs(props, { owner: "joni", files: [{ path: "a" }] }), {
owner: "joni",
limit: 20,
state: undefined,
files: [{ path: "a", binary: false }],
});
// Agents send null for "not given", and numbers as strings.
assert.deepEqual(parseArgs(props, { owner: "joni", limit: "5", state: null }), { owner: "joni", limit: 5, state: undefined, files: [] });
});
test("refuses bad arguments with messages an agent can act on", () => {
const fails = (args: unknown, message: RegExp) => assert.throws(() => parseArgs(props, args), (e) => e instanceof ToolError && message.test(e.message));
fails({}, /owner is required/);
fails({ owner: "" }, /owner can't be empty/);
fails({ owner: "a", limit: 99 }, /limit must be between 1 and 50/);
fails({ owner: "a", state: "merged" }, /state must be one of open, closed/);
fails({ owner: "a", file: [] }, /Unknown argument file/);
fails({ owner: "a", files: [{ path: 1 }] }, /files\[0\]\.path must be a string/);
});
test("describes itself as JSON Schema", () => {
const schema = objectSchema(props);
assert.deepEqual(schema.required, ["owner"]);
assert.equal(schema.additionalProperties, false);
assert.deepEqual((schema.properties as Record<string, { default?: unknown }>).limit.default, 20);
});