Spaces:
Sleeping
Sleeping
| import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; | |
| import { activateModel, generate, getActiveModel, listModels } from "@/lib/api"; | |
| import { generateDialog } from "@/lib/api"; | |
| const fetchMock = vi.fn(); | |
| beforeEach(() => { | |
| vi.stubGlobal("fetch", fetchMock); | |
| }); | |
| afterEach(() => { | |
| vi.unstubAllGlobals(); | |
| fetchMock.mockReset(); | |
| }); | |
| describe("api", () => { | |
| it("listModels GETs /api/models", async () => { | |
| fetchMock.mockResolvedValue(new Response(JSON.stringify([{ id: "x" }]))); | |
| const out = await listModels(); | |
| expect(fetchMock).toHaveBeenCalledWith("/api/models"); | |
| expect(out[0].id).toBe("x"); | |
| }); | |
| it("getActiveModel returns status object", async () => { | |
| fetchMock.mockResolvedValue( | |
| new Response(JSON.stringify({ id: "x", status: "loaded" })), | |
| ); | |
| const out = await getActiveModel(); | |
| expect(out.status).toBe("loaded"); | |
| }); | |
| it("activateModel posts to /api/models/{id}/activate", async () => { | |
| fetchMock.mockResolvedValue(new Response("{}", { status: 202 })); | |
| await activateModel("foo"); | |
| expect(fetchMock).toHaveBeenCalledWith( | |
| "/api/models/foo/activate", | |
| expect.objectContaining({ method: "POST" }), | |
| ); | |
| }); | |
| it("generate posts multipart and returns response blob with seed", async () => { | |
| fetchMock.mockResolvedValue( | |
| new Response("RIFFFAKE", { | |
| status: 200, | |
| headers: { "X-Seed-Used": "777" }, | |
| }), | |
| ); | |
| const out = await generate({ modelId: "x", text: "hi", params: {} }); | |
| expect(typeof out.blob.size).toBe("number"); | |
| expect(out.seedUsed).toBe(777); | |
| expect(fetchMock).toHaveBeenCalledWith( | |
| "/api/generate", | |
| expect.objectContaining({ method: "POST" }), | |
| ); | |
| }); | |
| it("generate surfaces error JSON on 4xx", async () => { | |
| fetchMock.mockResolvedValue( | |
| new Response( | |
| JSON.stringify({ error: { code: "model_not_found", message: "x" } }), | |
| { status: 404, headers: { "content-type": "application/json" } }, | |
| ), | |
| ); | |
| await expect( | |
| generate({ modelId: "x", text: "hi", params: {} }), | |
| ).rejects.toThrow(/model_not_found/); | |
| }); | |
| }); | |
| describe("generateDialog", () => { | |
| it("posts multipart with engine_id and per-speaker clips", async () => { | |
| fetchMock.mockResolvedValue( | |
| new Response("RIFFOK", { | |
| status: 200, | |
| headers: { "X-Seed-Used": "33" }, | |
| }), | |
| ); | |
| const out = await generateDialog({ | |
| engineId: "x", | |
| text: "SPEAKER A: hi\nSPEAKER B: hi", | |
| params: { temperature: 0.8 }, | |
| speakers: [ | |
| { letter: "A", reference: new Blob(["a"], { type: "audio/wav" }) }, | |
| { letter: "B", reference: new Blob(["b"], { type: "audio/wav" }) }, | |
| ], | |
| }); | |
| expect(out.seedUsed).toBe(33); | |
| expect(typeof out.blob.size).toBe("number"); | |
| const call = fetchMock.mock.calls[0]; | |
| expect(call[0]).toBe("/api/generate/dialog"); | |
| const body = call[1].body as FormData; | |
| expect(body.get("engine_id")).toBe("x"); | |
| expect(body.get("text")).toContain("SPEAKER A:"); | |
| expect(body.get("reference_wav_a")).toBeInstanceOf(Blob); | |
| expect(body.get("reference_wav_b")).toBeInstanceOf(Blob); | |
| }); | |
| it("forwards language only when provided", async () => { | |
| fetchMock.mockResolvedValue(new Response("RIFF", { status: 200 })); | |
| await generateDialog({ | |
| engineId: "x", | |
| text: "SPEAKER A: hi", | |
| language: "fr", | |
| params: {}, | |
| speakers: [{ letter: "A", reference: new Blob(["a"]) }], | |
| }); | |
| const body = fetchMock.mock.calls[0][1].body as FormData; | |
| expect(body.get("language")).toBe("fr"); | |
| }); | |
| }); | |