185 lines
6.4 KiB
TypeScript
185 lines
6.4 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import {
|
|
parseAiTaskStatus,
|
|
parseTaskCreateResponse,
|
|
parseImageTaskCreateResponse,
|
|
parseAiTaskStatusList,
|
|
parseSseTaskFrame,
|
|
} from "./dtoParsers";
|
|
|
|
describe("parseAiTaskStatus", () => {
|
|
it("parses a well-formed camelCase DTO", () => {
|
|
const result = parseAiTaskStatus({
|
|
taskId: "task-1",
|
|
type: "video",
|
|
status: "running",
|
|
progress: 42,
|
|
resultUrl: "https://example.com/r.mp4",
|
|
error: null,
|
|
createdAt: "2026-01-01T00:00:00Z",
|
|
updatedAt: "2026-01-01T00:01:00Z",
|
|
});
|
|
expect(result.taskId).toBe("task-1");
|
|
expect(result.type).toBe("video");
|
|
expect(result.status).toBe("running");
|
|
expect(result.progress).toBe(42);
|
|
expect(result.resultUrl).toBe("https://example.com/r.mp4");
|
|
});
|
|
|
|
it("tolerates snake_case field names", () => {
|
|
const result = parseAiTaskStatus({
|
|
task_id: "task-2",
|
|
result_url: "https://example.com/x.png",
|
|
created_at: "2026-01-01T00:00:00Z",
|
|
updated_at: "2026-01-01T00:00:00Z",
|
|
});
|
|
expect(result.taskId).toBe("task-2");
|
|
expect(result.resultUrl).toBe("https://example.com/x.png");
|
|
});
|
|
|
|
it("falls back to safe defaults for missing fields", () => {
|
|
const result = parseAiTaskStatus({});
|
|
expect(result.taskId).toBe("");
|
|
expect(result.type).toBe("image");
|
|
expect(result.status).toBe("failed");
|
|
expect(result.progress).toBe(0);
|
|
expect(result.resultUrl).toBeNull();
|
|
expect(result.error).toBeNull();
|
|
});
|
|
|
|
it("rejects unknown status/type values", () => {
|
|
const result = parseAiTaskStatus({ status: "weird", type: "audio" });
|
|
expect(result.status).toBe("failed");
|
|
expect(result.type).toBe("image");
|
|
});
|
|
|
|
it("clamps progress to [0, 100]", () => {
|
|
expect(parseAiTaskStatus({ progress: 150 }).progress).toBe(100);
|
|
expect(parseAiTaskStatus({ progress: -10 }).progress).toBe(0);
|
|
expect(parseAiTaskStatus({ progress: "not-a-number" }).progress).toBe(0);
|
|
});
|
|
|
|
it("preserves numeric conversationId and nulls others", () => {
|
|
expect(parseAiTaskStatus({ conversationId: 7 }).conversationId).toBe(7);
|
|
expect(parseAiTaskStatus({ conversation_id: 9 }).conversationId).toBe(9);
|
|
expect(parseAiTaskStatus({ conversationId: "nope" }).conversationId).toBeNull();
|
|
expect(parseAiTaskStatus({}).conversationId).toBeNull();
|
|
});
|
|
|
|
it("returns empty string for a non-record payload", () => {
|
|
const result = parseAiTaskStatus("garbage");
|
|
expect(result.taskId).toBe("");
|
|
expect(result.status).toBe("failed");
|
|
});
|
|
});
|
|
|
|
describe("parseTaskCreateResponse", () => {
|
|
it("extracts taskId from a create response", () => {
|
|
expect(parseTaskCreateResponse({ taskId: "abc" }).taskId).toBe("abc");
|
|
expect(parseTaskCreateResponse({ task_id: "def" }).taskId).toBe("def");
|
|
expect(parseTaskCreateResponse({ id: "ghi" }).taskId).toBe("ghi");
|
|
});
|
|
|
|
it("throws when taskId is missing", () => {
|
|
expect(() => parseTaskCreateResponse({})).toThrow();
|
|
expect(() => parseTaskCreateResponse({ taskId: "" })).toThrow();
|
|
expect(() => parseTaskCreateResponse({ taskId: " " })).toThrow();
|
|
});
|
|
});
|
|
|
|
describe("parseImageTaskCreateResponse", () => {
|
|
it("includes providerDebug when present", () => {
|
|
const result = parseImageTaskCreateResponse({
|
|
taskId: "img-1",
|
|
providerDebug: {
|
|
requestedModel: "gpt-image",
|
|
effectiveModel: "dall-e-3",
|
|
route: ["primary", "fallback"],
|
|
candidates: [{ provider: "openai", model: "dall-e-3" }],
|
|
},
|
|
});
|
|
expect(result.taskId).toBe("img-1");
|
|
expect(result.providerDebug?.effectiveModel).toBe("dall-e-3");
|
|
expect(result.providerDebug?.route).toEqual(["primary", "fallback"]);
|
|
expect(result.providerDebug?.candidates?.[0]?.model).toBe("dall-e-3");
|
|
});
|
|
|
|
it("omits providerDebug when absent", () => {
|
|
const result = parseImageTaskCreateResponse({ taskId: "img-2" });
|
|
expect(result.taskId).toBe("img-2");
|
|
expect(result.providerDebug).toBeUndefined();
|
|
});
|
|
|
|
it("tolerates snake_case providerDebug fields", () => {
|
|
const result = parseImageTaskCreateResponse({
|
|
taskId: "img-3",
|
|
provider_debug: { requested_model: "x", primary_provider: "openai" },
|
|
});
|
|
expect(result.providerDebug?.requestedModel).toBe("x");
|
|
expect(result.providerDebug?.primaryProvider).toBe("openai");
|
|
});
|
|
|
|
it("throws when taskId missing even if providerDebug present", () => {
|
|
expect(() => parseImageTaskCreateResponse({ providerDebug: {} })).toThrow();
|
|
});
|
|
});
|
|
|
|
describe("parseAiTaskStatusList", () => {
|
|
it("parses a bare array", () => {
|
|
const result = parseAiTaskStatusList([{ taskId: "a" }, { taskId: "b" }]);
|
|
expect(result).toHaveLength(2);
|
|
expect(result[0].taskId).toBe("a");
|
|
});
|
|
|
|
it("parses an envelope { tasks: [...] }", () => {
|
|
const result = parseAiTaskStatusList({ tasks: [{ taskId: "a" }, { task_id: "b" }] });
|
|
expect(result).toHaveLength(2);
|
|
expect(result[1].taskId).toBe("b");
|
|
});
|
|
|
|
it("parses an envelope { items: [...] }", () => {
|
|
const result = parseAiTaskStatusList({ items: [{ taskId: "a" }] });
|
|
expect(result).toHaveLength(1);
|
|
});
|
|
|
|
it("drops rows with no taskId rather than crashing", () => {
|
|
const result = parseAiTaskStatusList([{ taskId: "keep" }, { status: "running" }, {}]);
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].taskId).toBe("keep");
|
|
});
|
|
|
|
it("returns empty array for non-array non-record payload", () => {
|
|
expect(parseAiTaskStatusList(null)).toEqual([]);
|
|
expect(parseAiTaskStatusList("nope")).toEqual([]);
|
|
expect(parseAiTaskStatusList({})).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("parseSseTaskFrame", () => {
|
|
it("parses a well-formed SSE frame", () => {
|
|
const frame = parseSseTaskFrame({
|
|
taskId: "sse-1",
|
|
status: "completed",
|
|
progress: 100,
|
|
resultUrl: "https://example.com/done.png",
|
|
});
|
|
expect(frame.taskId).toBe("sse-1");
|
|
expect(frame.status).toBe("completed");
|
|
expect(frame.progress).toBe(100);
|
|
expect(frame.resultUrl).toBe("https://example.com/done.png");
|
|
});
|
|
|
|
it("clamps progress and rejects unknown status", () => {
|
|
const frame = parseSseTaskFrame({ taskId: "sse-2", status: "oops", progress: 999 });
|
|
expect(frame.status).toBe("failed");
|
|
expect(frame.progress).toBe(100);
|
|
});
|
|
|
|
it("handles a non-object payload", () => {
|
|
const frame = parseSseTaskFrame("garbage");
|
|
expect(frame.taskId).toBe("");
|
|
expect(frame.status).toBe("failed");
|
|
expect(frame.progress).toBe(0);
|
|
});
|
|
});
|