import { describe, expect, it } from "vitest"; import { z } from "zod"; import { ContractParseError, ContractValidationError, generateContract, staticModel } from "../src/index.js"; const Profile = z.object({ name: z.string(), seniority: z.enum(["junior", "mid", "senior"]), skills: z.array(z.string()).min(1) }); describe("generateContract", () => { it("returns validated data from strict JSON", async () => { const result = await generateContract({ model: staticModel([ JSON.stringify({ name: "Mira", seniority: "senior", skills: ["infra"] }) ]), schema: Profile, prompt: "Extract the profile." }); expect(result.data.name).toBe("Mira"); expect(result.attempts).toBe(1); }); it("extracts JSON from a fenced response", async () => { const result = await generateContract({ model: staticModel([ "```json\n{\"name\":\"Lee\",\"seniority\":\"mid\",\"skills\":[\"ml\"]}\n```" ]), schema: Profile, prompt: "Extract the profile." }); expect(result.data).toEqual({ name: "Lee", seniority: "mid", skills: ["ml"] }); }); it("repairs validation failures using a retry prompt", async () => { const prompts: string[] = []; const result = await generateContract({ model: { async generate(prompt) { prompts.push(prompt); if (prompts.length === 1) { return JSON.stringify({ name: "Ari", seniority: "principal", skills: [] }); } return JSON.stringify({ name: "Ari", seniority: "senior", skills: ["platform"] }); } }, schema: Profile, prompt: "Extract the profile.", retries: 1 }); expect(result.data.seniority).toBe("senior"); expect(result.attempts).toBe(2); expect(prompts[1]).toContain("Return only corrected JSON"); expect(result.replay.attempts[0]?.issues).toHaveLength(2); }); it("emits useful events", async () => { const events: string[] = []; await generateContract({ model: staticModel([ JSON.stringify({ name: "Noor", seniority: "junior", skills: ["ops"] }) ]), schema: Profile, prompt: "Extract the profile.", onEvent(event) { events.push(event.type); } }); expect(events).toEqual(["attempt", "success"]); }); it("throws validation errors with replay data", async () => { await expect( generateContract({ model: staticModel([JSON.stringify({ name: "Kai", seniority: "staff", skills: [] })]), schema: Profile, prompt: "Extract the profile." }) ).rejects.toMatchObject({ name: "ContractValidationError", replay: { attempts: [ expect.objectContaining({ attempt: 1, rawText: expect.any(String) }) ] } } satisfies Partial); }); it("throws parse errors after retries are exhausted", async () => { await expect( generateContract({ model: staticModel(["not json", "still not json"]), schema: Profile, prompt: "Extract the profile.", retries: 1 }) ).rejects.toBeInstanceOf(ContractParseError); }); });