/** * WorthDoing.ai * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai * File: tests/tool-schemas.test.ts * Description: Tool contract tests — Zod validation boundaries and Anthropic tool definition consistency. */ import { describe, it, expect } from "vitest"; import { toolSchemas, anthropicTools } from "@/lib/agent/tools"; const UUID = "3f0a9b1c-2d4e-4f6a-8b9c-0d1e2f3a4b5c"; describe("tool schemas", () => { it("every Anthropic tool definition has a matching Zod schema", () => { const defNames = anthropicTools.map((t) => t.name).sort(); const schemaNames = Object.keys(toolSchemas).sort(); expect(defNames).toEqual(schemaNames); }); it("search_web rejects empty and over-long queries", () => { expect(toolSchemas.search_web.safeParse({ query: "a", intent: "explore" }).success).toBe(false); expect(toolSchemas.search_web.safeParse({ query: "x".repeat(401), intent: "explore" }).success).toBe(false); expect(toolSchemas.search_web.safeParse({ query: "local ai pain points", intent: "falsify" }).success).toBe(true); }); it("search_web rejects unknown intents", () => { expect(toolSchemas.search_web.safeParse({ query: "test query", intent: "vibes" }).success).toBe(false); }); it("scrape_page requires a valid URL", () => { expect(toolSchemas.scrape_page.safeParse({ url: "not-a-url", reason: "check" }).success).toBe(false); expect(toolSchemas.scrape_page.safeParse({ url: "https://example.com/x", reason: "check signal" }).success).toBe(true); }); it("hypothesis confidence is bounded to [0,1]", () => { const base = { title: "Test hypothesis", statement: "Falsifiable statement here", rationale: "because" }; expect(toolSchemas.create_hypothesis.safeParse({ ...base, confidence: 1.2 }).success).toBe(false); expect(toolSchemas.create_hypothesis.safeParse({ ...base, confidence: -0.1 }).success).toBe(false); expect(toolSchemas.create_hypothesis.safeParse({ ...base, confidence: 0.5 }).success).toBe(true); }); it("save_evidence requires a real quote and valid source id", () => { expect( toolSchemas.save_evidence.safeParse({ source_id: "not-a-uuid", kind: "support", quote: "long enough quote text", summary: "summary", strength: 0.5, }).success, ).toBe(false); expect( toolSchemas.save_evidence.safeParse({ source_id: UUID, kind: "contradict", quote: "a sufficiently long quote from the page", summary: "counter-signal", strength: 0.7, links: [{ hypothesis_id: UUID, relation: "contradicts", weight: 0.8 }], }).success, ).toBe(true); }); it("create_opportunity demands at least 5 scores and 2 evidence links", () => { const score = (dimension: string) => ({ dimension, score: 70, confidence: 0.8, reasoning: "grounded in evidence", evidence_ids: [UUID], }); const base = { title: "Test opportunity", summary: "s".repeat(30), problem: "p".repeat(30), why_now: "w".repeat(30), risks: "r".repeat(30), skeptic_case: "sk".repeat(30), evidence: [ { evidence_id: UUID, role: "demand" }, { evidence_id: UUID, role: "risk" }, ], }; expect( toolSchemas.create_opportunity.safeParse({ ...base, scores: [score("demand"), score("neglectedness"), score("feasibility")], }).success, ).toBe(false); expect( toolSchemas.create_opportunity.safeParse({ ...base, scores: [score("demand"), score("neglectedness"), score("feasibility"), score("why_now"), score("impact")], }).success, ).toBe(true); }); it("all custom tool input schemas forbid additional properties (strict boundary)", () => { for (const tool of anthropicTools) { const schema = tool.input_schema as { additionalProperties?: boolean }; expect(schema.additionalProperties, `${tool.name} must set additionalProperties: false`).toBe(false); } }); });