SPB Git

spb/khaelor Public

KHAELOR — a terminal-native autonomous engineering agent powered by Anthropic.

TypeScript 82.9% HTML 14.9% CSS 1.1% JavaScript 0.7%
4.0 KB · 118 lines typescript
Raw Blame History
1/**2 * KHAELOR3 * File: tests/tools/registry.test.ts4 * Description: Unit tests for the ToolRegistry — registration, schema export, and input validation.5 *6 * Author: Simon-Pierre Boucher7 * Contact: contact@spboucher.ai8 */910import { describe, expect, it } from "vitest";11import {12  ToolRegistry,13  createDefaultToolRegistry,14  createReadTool,15  parseToolInput,16} from "../../src/tools/index.js";1718describe("ToolRegistry", () => {19  it("registers, lists, and gets tools", () => {20    const registry = new ToolRegistry();21    registry.register(createReadTool());22    expect(registry.list().map((t) => t.name)).toEqual(["read"]);23    expect(registry.get("read")?.name).toBe("read");24    expect(registry.get("missing")).toBeUndefined();25  });2627  it("rejects duplicate registration", () => {28    const registry = new ToolRegistry();29    registry.register(createReadTool());30    expect(() => registry.register(createReadTool())).toThrowError(/already registered/);31  });3233  it("registers all seven default tools", () => {34    const registry = createDefaultToolRegistry();35    expect(registry.list().map((t) => t.name)).toEqual([36      "read",37      "write",38      "edit",39      "grep",40      "glob",41      "bash",42      "process",43    ]);44  });4546  it("exports valid Anthropic tool params with at most 5 parameters each", () => {47    const params = createDefaultToolRegistry().toAnthropicTools();48    expect(params).toHaveLength(7);49    for (const param of params) {50      expect(typeof param.name).toBe("string");51      expect(param.description.length).toBeGreaterThan(50);52      expect(param.input_schema.type).toBe("object");53      const propNames = Object.keys(param.input_schema.properties);54      expect(propNames.length).toBeGreaterThan(0);55      expect(propNames.length).toBeLessThanOrEqual(5);56      for (const required of param.input_schema.required) {57        expect(propNames).toContain(required);58      }59      for (const prop of Object.values(param.input_schema.properties)) {60        expect(["string", "integer", "boolean"]).toContain(prop.type);61        expect(prop.description.length).toBeGreaterThan(0);62      }63      // Round-trips as plain JSON (what the Anthropic SDK serializes).64      expect(JSON.parse(JSON.stringify(param))).toEqual(param);65    }66  });6768  it("snake_case parameter naming throughout", () => {69    for (const param of createDefaultToolRegistry().toAnthropicTools()) {70      for (const name of Object.keys(param.input_schema.properties)) {71        expect(name).toMatch(/^[a-z][a-z0-9_]*$/);72      }73    }74  });75});7677describe("parseToolInput", () => {78  const read = createReadTool();7980  it("accepts valid input", () => {81    const parsed = parseToolInput(read, { file_path: "a.ts", offset: 2 });82    expect(parsed.ok).toBe(true);83  });8485  it("produces repair prose for a missing required parameter", () => {86    const parsed = parseToolInput(read, { offset: 2 });87    expect(parsed.ok).toBe(false);88    if (!parsed.ok) {89      expect(parsed.error).toBe(90        'Invalid input for tool "read": parameter "file_path" is required. Please rewrite the input so it satisfies the expected schema.',91      );92    }93  });9495  it("rejects wrong types with repair prose", () => {96    const parsed = parseToolInput(read, { file_path: "a.ts", offset: "two" });97    expect(parsed.ok).toBe(false);98    if (!parsed.ok) expect(parsed.error).toContain('parameter "offset" must be an integer');99  });100101  it("rejects non-object input", () => {102    const parsed = parseToolInput(read, "just a string");103    expect(parsed.ok).toBe(false);104    if (!parsed.ok) expect(parsed.error).toContain("must be a JSON object");105  });106107  it("enforces enums (process action)", () => {108    const registry = createDefaultToolRegistry();109    const processTool = registry.get("process");110    expect(processTool).toBeDefined();111    const parsed = parseToolInput(processTool!, { action: "restart" });112    expect(parsed.ok).toBe(false);113    if (!parsed.ok) {114      expect(parsed.error).toContain('parameter "action" must be one of "start", "list", "read", "write", "stop"');115    }116  });117});118