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

293 lines
8.8 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-music-"));
const { handleMusicGeneration } = await import("../../open-sse/handlers/musicGeneration.ts");
const { MUSIC_PROVIDERS } = await import("../../open-sse/config/musicRegistry.ts");
function immediateTimeout(callback, _ms, ...args) {
if (typeof callback === "function") callback(...args);
return 0;
}
test("handleMusicGeneration rejects invalid model strings", async () => {
const result = await handleMusicGeneration({
body: { model: "invalid-music-model", prompt: "x" },
credentials: null,
log: null,
});
assert.equal(result.success, false);
assert.equal(result.status, 400);
assert.match(result.error, /Invalid music model/);
});
test("handleMusicGeneration treats unknown provider prefixes as invalid music models", async () => {
const result = await handleMusicGeneration({
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 music model: mystery\/model-1/);
});
test("handleMusicGeneration executes ComfyUI audio workflow and normalizes wav output", 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: "music-1" }), {
status: 200,
headers: { "content-type": "application/json" },
});
}
if (stringUrl === "http://localhost:8188/history/music-1") {
return new Response(
JSON.stringify({
"music-1": {
outputs: {
7: {
audio: [{ filename: "track.wav", subfolder: "out", type: "output" }],
},
},
},
}),
{ status: 200, headers: { "content-type": "application/json" } }
);
}
if (stringUrl.includes("/view?")) {
return new Response(new Uint8Array([5, 6, 7]), { status: 200 });
}
throw new Error(`Unexpected URL: ${stringUrl}`);
};
try {
const result = await handleMusicGeneration({
body: {
model: "comfyui/musicgen-medium",
prompt: "ambient synth",
negative_prompt: "distortion",
duration: 18,
steps: 55,
cfg_scale: 9,
},
credentials: null,
log: null,
});
assert.equal(promptBody.prompt["4"].inputs.seconds, 18);
assert.equal(promptBody.prompt["5"].inputs.steps, 55);
assert.equal(promptBody.prompt["5"].inputs.cfg, 9);
assert.equal(promptBody.prompt["2"].inputs.text, "ambient synth");
assert.equal(promptBody.prompt["3"].inputs.text, "distortion");
assert.equal(result.success, true);
assert.deepEqual(result.data.data, [{ b64_json: "BQYH", format: "wav" }]);
} finally {
globalThis.fetch = originalFetch;
globalThis.setTimeout = originalSetTimeout;
}
});
test("handleMusicGeneration polls KIE music tasks and returns audio 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/generate") {
createBody = JSON.parse(String(options.body || "{}"));
return new Response(JSON.stringify({ code: 200, data: { taskId: "kie-music-task" } }), {
status: 200,
headers: { "content-type": "application/json" },
});
}
if (stringUrl.startsWith("https://api.kie.ai/api/v1/generate/record-info")) {
pollUrl = stringUrl;
return new Response(
JSON.stringify({
code: 200,
data: {
status: "SUCCESS",
response: {
sunoData: [{ audioUrl: "https://example.com/kie-music.mp3" }],
},
},
}),
{ status: 200, headers: { "content-type": "application/json" } }
);
}
throw new Error(`Unexpected URL: ${stringUrl}`);
};
try {
const result = await handleMusicGeneration({
body: {
model: "kie/V4",
prompt: "relaxing piano ambience",
},
credentials: { apiKey: "kie-key" },
log: null,
});
assert.equal(createBody.model, "V4");
assert.equal(createBody.customMode, false);
assert.equal(createBody.instrumental, true);
assert.equal(createBody.prompt, "relaxing piano ambience");
assert.match(pollUrl, /taskId=kie-music-task/);
assert.equal(result.success, true);
assert.equal(result.data.data[0].url, "https://example.com/kie-music.mp3");
assert.equal(result.data.data[0].format, "mp3");
} finally {
globalThis.fetch = originalFetch;
}
});
test("handleMusicGeneration rejects unsupported provider formats", async () => {
const originalProvider = MUSIC_PROVIDERS.fakeprovider;
MUSIC_PROVIDERS.fakeprovider = {
id: "fakeprovider",
baseUrl: "http://localhost:9999",
authType: "none",
authHeader: "none",
format: "custom",
models: [{ id: "broken-model", name: "Broken Model" }],
};
try {
const result = await handleMusicGeneration({
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 music format: custom/);
} finally {
if (originalProvider) {
MUSIC_PROVIDERS.fakeprovider = originalProvider;
} else {
delete MUSIC_PROVIDERS.fakeprovider;
}
}
});
test("handleMusicGeneration returns unknown provider when registry lookup disappears after parsing", async () => {
Object.defineProperty(MUSIC_PROVIDERS, "flakyprovider", {
configurable: true,
enumerable: true,
get() {
delete MUSIC_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 handleMusicGeneration({
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 music provider: flakyprovider/);
});
test("handleMusicGeneration 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: "music-fail" }), {
status: 200,
headers: { "content-type": "application/json" },
});
}
if (stringUrl === "http://localhost:8188/history/music-fail") {
return new Response(
JSON.stringify({
"music-fail": {
outputs: {
7: {
audio: [{ filename: "broken.wav", 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 handleMusicGeneration({
body: {
model: "comfyui/musicgen-medium",
prompt: "slow piano",
},
credentials: null,
log: {
info: (...args) => logEntries.push(["info", ...args]),
error: (...args) => logEntries.push(["error", ...args]),
},
});
assert.equal(promptBody.prompt["4"].inputs.seconds, 10);
assert.equal(promptBody.prompt["5"].inputs.steps, 100);
assert.equal(promptBody.prompt["5"].inputs.cfg, 7);
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;
}
});