import { afterEach, describe, expect, it, vi } from "vitest"; import { fetchRecipesIndex } from "./catalog"; const emptyText = { fr: "", en: "", ko: "", zh: "" }; const minimalBase = { id: "base:x", flavor: "savory" as const, name: emptyText, history: emptyText, ingredients: [], steps: [] }; const minimalVariant = { id: "var:y", baseIds: ["base:x"], name: emptyText, story: emptyText, ingredients: [], steps: [] }; describe("fetchRecipesIndex", () => { afterEach(() => { vi.restoreAllMocks(); }); it("loads and freezes catalog JSON", async () => { const payload = { bases: [minimalBase], variants: [minimalVariant] }; vi.stubGlobal( "fetch", vi.fn(async () => Promise.resolve({ ok: true, json: async () => payload }) ) ); const index = await fetchRecipesIndex("/custom/catalog.json"); expect(index.bases).toHaveLength(1); expect(index.variants).toHaveLength(1); expect(fetch).toHaveBeenCalledWith("/custom/catalog.json", { headers: { Accept: "application/json" } }); }); it("throws when response is not ok", async () => { vi.stubGlobal( "fetch", vi.fn(async () => Promise.resolve({ ok: false, status: 503 })) ); await expect(fetchRecipesIndex()).rejects.toThrow("503"); }); it("throws when payload is not an object", async () => { vi.stubGlobal( "fetch", vi.fn(async () => Promise.resolve({ ok: true, json: async () => null }) ) ); await expect(fetchRecipesIndex()).rejects.toThrow("Invalid recipe catalog payload"); }); it("throws when bases or variants are not arrays", async () => { vi.stubGlobal( "fetch", vi.fn(async () => Promise.resolve({ ok: true, json: async () => ({ bases: [], variants: null }) }) ) ); await expect(fetchRecipesIndex()).rejects.toThrow("Invalid recipe catalog shape"); }); });