SPB Git

spb/worthdoing Public

Autonomous investigation agent that discovers, challenges, and ranks things genuinely worth doing — Claude + Firecrawl, Next.js 16, PostgreSQL

TypeScript 91.5% SQL 5.8% CSS 2.2%
4.0 KB · 105 lines typescript
Raw Blame History
1/**2 * WorthDoing.ai3 * Author: Simon-Pierre Boucher4 * Contact: contact@spboucher.ai5 * File: tests/tool-schemas.test.ts6 * Description: Tool contract tests — Zod validation boundaries and Anthropic tool definition consistency.7 */8import { describe, it, expect } from "vitest";9import { toolSchemas, anthropicTools } from "@/lib/agent/tools";1011const UUID = "3f0a9b1c-2d4e-4f6a-8b9c-0d1e2f3a4b5c";1213describe("tool schemas", () => {14  it("every Anthropic tool definition has a matching Zod schema", () => {15    const defNames = anthropicTools.map((t) => t.name).sort();16    const schemaNames = Object.keys(toolSchemas).sort();17    expect(defNames).toEqual(schemaNames);18  });1920  it("search_web rejects empty and over-long queries", () => {21    expect(toolSchemas.search_web.safeParse({ query: "a", intent: "explore" }).success).toBe(false);22    expect(toolSchemas.search_web.safeParse({ query: "x".repeat(401), intent: "explore" }).success).toBe(false);23    expect(toolSchemas.search_web.safeParse({ query: "local ai pain points", intent: "falsify" }).success).toBe(true);24  });2526  it("search_web rejects unknown intents", () => {27    expect(toolSchemas.search_web.safeParse({ query: "test query", intent: "vibes" }).success).toBe(false);28  });2930  it("scrape_page requires a valid URL", () => {31    expect(toolSchemas.scrape_page.safeParse({ url: "not-a-url", reason: "check" }).success).toBe(false);32    expect(toolSchemas.scrape_page.safeParse({ url: "https://example.com/x", reason: "check signal" }).success).toBe(true);33  });3435  it("hypothesis confidence is bounded to [0,1]", () => {36    const base = { title: "Test hypothesis", statement: "Falsifiable statement here", rationale: "because" };37    expect(toolSchemas.create_hypothesis.safeParse({ ...base, confidence: 1.2 }).success).toBe(false);38    expect(toolSchemas.create_hypothesis.safeParse({ ...base, confidence: -0.1 }).success).toBe(false);39    expect(toolSchemas.create_hypothesis.safeParse({ ...base, confidence: 0.5 }).success).toBe(true);40  });4142  it("save_evidence requires a real quote and valid source id", () => {43    expect(44      toolSchemas.save_evidence.safeParse({45        source_id: "not-a-uuid",46        kind: "support",47        quote: "long enough quote text",48        summary: "summary",49        strength: 0.5,50      }).success,51    ).toBe(false);52    expect(53      toolSchemas.save_evidence.safeParse({54        source_id: UUID,55        kind: "contradict",56        quote: "a sufficiently long quote from the page",57        summary: "counter-signal",58        strength: 0.7,59        links: [{ hypothesis_id: UUID, relation: "contradicts", weight: 0.8 }],60      }).success,61    ).toBe(true);62  });6364  it("create_opportunity demands at least 5 scores and 2 evidence links", () => {65    const score = (dimension: string) => ({66      dimension,67      score: 70,68      confidence: 0.8,69      reasoning: "grounded in evidence",70      evidence_ids: [UUID],71    });72    const base = {73      title: "Test opportunity",74      summary: "s".repeat(30),75      problem: "p".repeat(30),76      why_now: "w".repeat(30),77      risks: "r".repeat(30),78      skeptic_case: "sk".repeat(30),79      evidence: [80        { evidence_id: UUID, role: "demand" },81        { evidence_id: UUID, role: "risk" },82      ],83    };84    expect(85      toolSchemas.create_opportunity.safeParse({86        ...base,87        scores: [score("demand"), score("neglectedness"), score("feasibility")],88      }).success,89    ).toBe(false);90    expect(91      toolSchemas.create_opportunity.safeParse({92        ...base,93        scores: [score("demand"), score("neglectedness"), score("feasibility"), score("why_now"), score("impact")],94      }).success,95    ).toBe(true);96  });9798  it("all custom tool input schemas forbid additional properties (strict boundary)", () => {99    for (const tool of anthropicTools) {100      const schema = tool.input_schema as { additionalProperties?: boolean };101      expect(schema.additionalProperties, `${tool.name} must set additionalProperties: false`).toBe(false);102    }103  });104});105