File size: 3,612 Bytes
96f2542 192ae87 96f2542 8122b04 96f2542 192ae87 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | 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");
});
});
|