Files
diegosouzapw--omniroute/tests/unit/video-generation-handler.test.ts
2026-07-13 13:39:12 +08:00

584 lines
17 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
process.env.DATA_DIR = mkdtempSync(join(tmpdir(), "omniroute-video-"));
const { handleVideoGeneration } = await import("../../open-sse/handlers/videoGeneration.ts");
const { VIDEO_PROVIDERS } = await import("../../open-sse/config/videoRegistry.ts");
function immediateTimeout(callback, _ms, ...args) {
if (typeof callback === "function") callback(...args);
return 0;
}
test("handleVideoGeneration rejects invalid model strings", async () => {
const result = await handleVideoGeneration({
body: { model: "invalid-video-model", prompt: "x" },
credentials: null,
log: null,
});
assert.equal(result.success, false);
assert.equal(result.status, 400);
assert.match(result.error, /Invalid video model/);
});
test("handleVideoGeneration treats unknown provider prefixes as invalid video models", async () => {
const result = await handleVideoGeneration({
body: { model: "mystery/model-1", prompt: "x" },
credentials: null,
log: null,
});
assert.equal(result.success, false);
assert.equal(result.status, 400);
assert.match(result.error, /Invalid video model: mystery\/model-1/);
});
test("handleVideoGeneration routes SD WebUI payloads and normalizes mp4 output", async () => {
const originalFetch = globalThis.fetch;
let captured;
globalThis.fetch = async (url, options = {}) => {
captured = {
url: String(url),
body: JSON.parse(String(options.body || "{}")),
};
return new Response(
JSON.stringify({
video: "bXA0LWJhc2U2NA==",
}),
{ status: 200, headers: { "content-type": "application/json" } }
);
};
try {
const result = await handleVideoGeneration({
body: {
model: "sdwebui/animatediff-webui",
prompt: "ocean wave",
negative_prompt: "low quality",
size: "640x360",
steps: 30,
cfg_scale: 8,
frames: 24,
fps: 12,
},
credentials: null,
log: null,
});
assert.equal(captured.url, "http://localhost:7860/animatediff/v1/generate");
assert.deepEqual(captured.body, {
prompt: "ocean wave",
negative_prompt: "low quality",
width: 640,
height: 360,
steps: 30,
cfg_scale: 8,
frames: 24,
fps: 12,
});
assert.equal(result.success, true);
assert.deepEqual(result.data.data, [{ b64_json: "bXA0LWJhc2U2NA==", format: "mp4" }]);
} finally {
globalThis.fetch = originalFetch;
}
});
test("handleVideoGeneration polls KIE market tasks and returns video URLs", async () => {
const originalFetch = globalThis.fetch;
let createBody;
let pollUrl = "";
globalThis.fetch = async (url, options = {}) => {
const stringUrl = String(url);
if (stringUrl === "https://api.kie.ai/api/v1/jobs/createTask") {
createBody = JSON.parse(String(options.body || "{}"));
return new Response(JSON.stringify({ code: 200, data: { taskId: "kie-video-task" } }), {
status: 200,
headers: { "content-type": "application/json" },
});
}
if (stringUrl.startsWith("https://api.kie.ai/api/v1/jobs/recordInfo")) {
pollUrl = stringUrl;
return new Response(
JSON.stringify({
code: 200,
data: {
state: "success",
resultJson: '{"resultUrls":["https://example.com/kie-video.mp4"]}',
},
}),
{ status: 200, headers: { "content-type": "application/json" } }
);
}
throw new Error(`Unexpected URL: ${stringUrl}`);
};
try {
assert.ok(VIDEO_PROVIDERS.kie.models.some((model) => model.id === "kling-3.0/video"));
const result = await handleVideoGeneration({
body: {
model: "kie/kling-3.0/video",
prompt: "cinematic shot of neon city rain",
},
credentials: { apiKey: "kie-key" },
log: null,
});
assert.equal(createBody.model, "kling-3.0/video");
assert.equal(createBody.input.prompt, "cinematic shot of neon city rain");
assert.match(pollUrl, /taskId=kie-video-task/);
assert.equal(result.success, true);
assert.equal(result.data.data[0].url, "https://example.com/kie-video.mp4");
assert.equal(result.data.data[0].format, "mp4");
} finally {
globalThis.fetch = originalFetch;
}
});
test("handleVideoGeneration executes ComfyUI workflow and returns fetched output files", async () => {
const originalFetch = globalThis.fetch;
const originalSetTimeout = globalThis.setTimeout;
let promptBody;
globalThis.setTimeout = immediateTimeout;
globalThis.fetch = async (url, options = {}) => {
const stringUrl = String(url);
if (stringUrl === "http://localhost:8188/prompt") {
promptBody = JSON.parse(String(options.body || "{}"));
return new Response(JSON.stringify({ prompt_id: "video-1" }), {
status: 200,
headers: { "content-type": "application/json" },
});
}
if (stringUrl === "http://localhost:8188/history/video-1") {
return new Response(
JSON.stringify({
"video-1": {
outputs: {
7: {
gifs: [{ filename: "clip.webp", subfolder: "out", type: "output" }],
},
},
},
}),
{ status: 200, headers: { "content-type": "application/json" } }
);
}
if (stringUrl.includes("/view?")) {
return new Response(new Uint8Array([1, 2, 3, 4]), { status: 200 });
}
throw new Error(`Unexpected URL: ${stringUrl}`);
};
try {
const result = await handleVideoGeneration({
body: {
model: "comfyui/animatediff",
prompt: "neon car",
size: "720x480",
frames: 20,
fps: 10,
steps: 12,
cfg_scale: 6,
},
credentials: null,
log: null,
});
assert.equal(
promptBody.prompt["4"].inputs.width,
720,
"workflow should use parsed width for latent image"
);
assert.equal(promptBody.prompt["4"].inputs.height, 480);
assert.equal(promptBody.prompt["4"].inputs.batch_size, 20);
assert.equal(promptBody.prompt["7"].inputs.fps, 10);
assert.equal(result.success, true);
assert.deepEqual(result.data.data, [{ b64_json: "AQIDBA==", format: "webp" }]);
} finally {
globalThis.fetch = originalFetch;
globalThis.setTimeout = originalSetTimeout;
}
});
test("handleVideoGeneration returns unknown provider when registry lookup disappears after parsing", async () => {
Object.defineProperty(VIDEO_PROVIDERS, "flakyprovider", {
configurable: true,
enumerable: true,
get() {
delete VIDEO_PROVIDERS.flakyprovider;
return {
id: "flakyprovider",
baseUrl: "http://localhost:9999",
authType: "none",
authHeader: "none",
format: "comfyui",
models: [{ id: "ghost-model", name: "Ghost Model" }],
};
},
});
const result = await handleVideoGeneration({
body: { model: "flakyprovider/ghost-model", prompt: "x" },
credentials: null,
log: null,
});
assert.equal(result.success, false);
assert.equal(result.status, 400);
assert.match(result.error, /Unknown video provider: flakyprovider/);
});
test("handleVideoGeneration rejects unsupported provider formats", async () => {
const originalProvider = VIDEO_PROVIDERS.fakeprovider;
VIDEO_PROVIDERS.fakeprovider = {
id: "fakeprovider",
baseUrl: "http://localhost:9999",
authType: "none",
authHeader: "none",
format: "custom-video",
models: [{ id: "broken-model", name: "Broken Model" }],
};
try {
const result = await handleVideoGeneration({
body: { model: "fakeprovider/broken-model", prompt: "x" },
credentials: null,
log: null,
});
assert.equal(result.success, false);
assert.equal(result.status, 400);
assert.match(result.error, /Unsupported video format: custom-video/);
} finally {
if (originalProvider) {
VIDEO_PROVIDERS.fakeprovider = originalProvider;
} else {
delete VIDEO_PROVIDERS.fakeprovider;
}
}
});
test("handleVideoGeneration normalizes SD WebUI image arrays and applies default dimensions", async () => {
const originalFetch = globalThis.fetch;
const logEntries = [];
let captured;
globalThis.fetch = async (url, options = {}) => {
captured = {
url: String(url),
body: JSON.parse(String(options.body || "{}")),
};
return new Response(
JSON.stringify({
images: ["ZnJhbWUtMQ==", { image: "ZnJhbWUtMg==" }],
}),
{ status: 200, headers: { "content-type": "application/json" } }
);
};
try {
const result = await handleVideoGeneration({
body: {
model: "sdwebui/animatediff-webui",
prompt: "forest path",
},
credentials: null,
log: {
info: (...args) => logEntries.push(["info", ...args]),
error: (...args) => logEntries.push(["error", ...args]),
},
});
assert.equal(captured.url, "http://localhost:7860/animatediff/v1/generate");
assert.deepEqual(captured.body, {
prompt: "forest path",
negative_prompt: "",
width: 512,
height: 512,
steps: 20,
cfg_scale: 7,
frames: 16,
fps: 8,
});
assert.equal(result.success, true);
assert.deepEqual(result.data.data, [
{ b64_json: "ZnJhbWUtMQ==", format: "mp4" },
{ b64_json: "ZnJhbWUtMg==", format: "mp4" },
]);
assert.equal(logEntries[0][0], "info");
assert.match(logEntries[0][2], /sdwebui\/animatediff-webui \(sdwebui\)/);
} finally {
globalThis.fetch = originalFetch;
}
});
test("handleVideoGeneration returns SD WebUI upstream errors and logs them", async () => {
const originalFetch = globalThis.fetch;
const logEntries = [];
globalThis.fetch = async () => new Response("provider busy", { status: 503 });
try {
const result = await handleVideoGeneration({
body: {
model: "sdwebui/animatediff-webui",
prompt: "storm",
},
credentials: null,
log: {
info: (...args) => logEntries.push(["info", ...args]),
error: (...args) => logEntries.push(["error", ...args]),
},
});
assert.equal(result.success, false);
assert.equal(result.status, 503);
assert.equal(result.error, "provider busy");
assert.deepEqual(
logEntries.map((entry) => entry[0]),
["info", "error"]
);
assert.match(logEntries[1][2], /provider busy/);
} finally {
globalThis.fetch = originalFetch;
}
});
test("handleVideoGeneration returns provider errors for ComfyUI failures and logs defaults", async () => {
const originalFetch = globalThis.fetch;
const originalSetTimeout = globalThis.setTimeout;
const logEntries = [];
let promptBody;
globalThis.setTimeout = immediateTimeout;
globalThis.fetch = async (url, options = {}) => {
const stringUrl = String(url);
if (stringUrl === "http://localhost:8188/prompt") {
promptBody = JSON.parse(String(options.body || "{}"));
return new Response(JSON.stringify({ prompt_id: "video-fail" }), {
status: 200,
headers: { "content-type": "application/json" },
});
}
if (stringUrl === "http://localhost:8188/history/video-fail") {
return new Response(
JSON.stringify({
"video-fail": {
outputs: {
7: {
gifs: [{ filename: "broken.webp", subfolder: "out", type: "output" }],
},
},
},
}),
{ status: 200, headers: { "content-type": "application/json" } }
);
}
if (stringUrl.includes("/view?")) {
return new Response("missing output", { status: 500 });
}
throw new Error(`Unexpected URL: ${stringUrl}`);
};
try {
const result = await handleVideoGeneration({
body: {
model: "comfyui/animatediff",
prompt: "night drive",
},
credentials: null,
log: {
info: (...args) => logEntries.push(["info", ...args]),
error: (...args) => logEntries.push(["error", ...args]),
},
});
assert.equal(promptBody.prompt["4"].inputs.width, 512);
assert.equal(promptBody.prompt["4"].inputs.height, 512);
assert.equal(promptBody.prompt["4"].inputs.batch_size, 16);
assert.equal(promptBody.prompt["7"].inputs.fps, 8);
assert.equal(result.success, false);
assert.equal(result.status, 502);
assert.match(result.error, /ComfyUI fetch output failed \(500\)/);
assert.deepEqual(
logEntries.map((entry) => entry[0]),
["info", "error"]
);
assert.match(logEntries[1][2], /comfyui error/i);
} finally {
globalThis.fetch = originalFetch;
globalThis.setTimeout = originalSetTimeout;
}
});
test("handleVideoGeneration submits, polls and downloads Runway text-to-video tasks", async () => {
const originalFetch = globalThis.fetch;
const originalSetTimeout = globalThis.setTimeout;
const seen = [];
let pollCount = 0;
globalThis.setTimeout = immediateTimeout;
globalThis.fetch = async (url, init = {}) => {
const target = String(url);
seen.push({ url: target, init });
if (target === "https://api.dev.runwayml.com/v1/text_to_video") {
const headers = init.headers as Record<string, string>;
const body = JSON.parse(String(init.body || "{}"));
assert.equal(headers.Authorization, "Bearer runway-key");
assert.equal(headers["X-Runway-Version"], "2024-11-06");
assert.equal(body.model, "gen4.5");
assert.equal(body.promptText, "cinematic sunrise");
assert.equal(body.ratio, "1280:720");
assert.equal(body.duration, 6);
return new Response(JSON.stringify({ id: "task-runway-1" }), { status: 200 });
}
if (target === "https://api.dev.runwayml.com/v1/tasks/task-runway-1") {
pollCount += 1;
if (pollCount === 1) {
return new Response(JSON.stringify({ id: "task-runway-1", status: "RUNNING" }), {
status: 200,
});
}
return new Response(
JSON.stringify({
id: "task-runway-1",
status: "SUCCEEDED",
output: ["https://cdn.runway.dev/output-1.mp4"],
}),
{ status: 200 }
);
}
if (target === "https://cdn.runway.dev/output-1.mp4") {
return new Response(new Uint8Array([9, 8, 7, 6]), { status: 200 });
}
throw new Error(`Unexpected URL: ${target}`);
};
try {
const result = await handleVideoGeneration({
body: {
model: "runwayml/gen4.5",
prompt: "cinematic sunrise",
size: "1280x720",
duration: 6,
},
credentials: { apiKey: "runway-key" },
log: null,
});
assert.equal(result.success, true);
assert.deepEqual(result.data.data, [{ b64_json: "CQgHBg==", format: "mp4" }]);
assert.deepEqual(
seen.map((entry) => entry.url),
[
"https://api.dev.runwayml.com/v1/text_to_video",
"https://api.dev.runwayml.com/v1/tasks/task-runway-1",
"https://api.dev.runwayml.com/v1/tasks/task-runway-1",
"https://cdn.runway.dev/output-1.mp4",
]
);
} finally {
globalThis.fetch = originalFetch;
globalThis.setTimeout = originalSetTimeout;
}
});
test("handleVideoGeneration routes Runway image-to-video requests and can return output URLs", async () => {
const originalFetch = globalThis.fetch;
const originalSetTimeout = globalThis.setTimeout;
let submittedBody;
globalThis.setTimeout = immediateTimeout;
globalThis.fetch = async (url, init = {}) => {
const target = String(url);
if (target === "https://api.dev.runwayml.com/v1/image_to_video") {
submittedBody = JSON.parse(String(init.body || "{}"));
return new Response(JSON.stringify({ id: "task-runway-2" }), { status: 200 });
}
if (target === "https://api.dev.runwayml.com/v1/tasks/task-runway-2") {
return new Response(
JSON.stringify({
id: "task-runway-2",
status: "SUCCEEDED",
output: ["https://cdn.runway.dev/output-2.mp4"],
}),
{ status: 200 }
);
}
throw new Error(`Unexpected URL: ${target}`);
};
try {
const result = await handleVideoGeneration({
body: {
model: "runwayml/gen4_turbo",
prompt: "make the frame move",
prompt_image: "https://assets.example.com/frame.png",
response_format: "url",
size: "720x1280",
},
credentials: { apiKey: "runway-key" },
log: null,
});
assert.deepEqual(submittedBody, {
model: "gen4_turbo",
promptText: "make the frame move",
ratio: "720:1280",
duration: 5,
promptImage: "https://assets.example.com/frame.png",
});
assert.equal(result.success, true);
assert.deepEqual(result.data.data, [
{ url: "https://cdn.runway.dev/output-2.mp4", format: "mp4" },
]);
} finally {
globalThis.fetch = originalFetch;
globalThis.setTimeout = originalSetTimeout;
}
});
test("handleVideoGeneration rejects Runway models that require promptImage", async () => {
const result = await handleVideoGeneration({
body: {
model: "runwayml/gen4_turbo",
prompt: "animate this",
},
credentials: { apiKey: "runway-key" },
log: null,
});
assert.equal(result.success, false);
assert.equal(result.status, 400);
assert.match(result.error, /requires promptImage/i);
});