127 lines
4.2 KiB
JavaScript
127 lines
4.2 KiB
JavaScript
// Exercises the actual audio adapter with local provider/storage doubles; no paid calls.
|
|
const fs = require("node:fs"),
|
|
path = require("node:path"),
|
|
assert = require("node:assert/strict");
|
|
(async () => {
|
|
const esbuild = require(
|
|
path.resolve(
|
|
"node_modules/.pnpm",
|
|
fs.readdirSync("node_modules/.pnpm").find((n) => n.startsWith("esbuild@0.25.")),
|
|
"node_modules/esbuild",
|
|
),
|
|
);
|
|
fs.mkdirSync(".qa/audio-expansion", { recursive: true });
|
|
const modules = {
|
|
"server-only": "",
|
|
"@/lib/db/queries":
|
|
"export async function getEnabledAiModelsWithProvider(){return globalThis.__audioModels}",
|
|
"@/lib/storage/service":
|
|
"export async function uploadFile(name,bytes){return globalThis.__audioUpload(name,bytes)}",
|
|
"./download-media":
|
|
"export async function downloadCanvasMedia(url,file){return globalThis.__audioDownload(url,file)}",
|
|
};
|
|
await esbuild.build({
|
|
entryPoints: ["lib/video-canvas/audio-generation.ts"],
|
|
outfile: ".qa/audio-expansion/adapter.cjs",
|
|
bundle: true,
|
|
platform: "node",
|
|
format: "cjs",
|
|
packages: "external",
|
|
alias: { "@": process.cwd() },
|
|
plugins: [
|
|
{
|
|
name: "qa",
|
|
setup(b) {
|
|
b.onResolve({ filter: /.*/ }, (args) =>
|
|
Object.hasOwn(modules, args.path) ? { path: args.path, namespace: "qa" } : undefined,
|
|
);
|
|
b.onLoad({ filter: /.*/, namespace: "qa" }, (args) => ({ contents: modules[args.path] }));
|
|
},
|
|
},
|
|
],
|
|
});
|
|
const { executeCanvasAudio } = require(path.resolve(".qa/audio-expansion/adapter.cjs"));
|
|
global.__audioModels = [
|
|
{
|
|
model: { id: "official", modelId: "qwen3-tts-instruct-flash" },
|
|
provider: {
|
|
type: "alibaba",
|
|
baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
apiKey: "local-fixture",
|
|
},
|
|
},
|
|
];
|
|
let posts = 0,
|
|
downloads = 0,
|
|
uploads = 0,
|
|
checkpoint;
|
|
global.fetch = async (url, init) => {
|
|
posts++;
|
|
assert.equal(init.method, "POST");
|
|
assert.equal(JSON.parse(init.body).input.voice, "Cherry");
|
|
return Response.json({
|
|
request_id: "fixture",
|
|
output: {
|
|
audio: {
|
|
url: "https://example.com/test.wav",
|
|
expires_at: Math.floor(Date.now() / 1000) + 3600,
|
|
},
|
|
},
|
|
});
|
|
};
|
|
global.__audioDownload = async (url, file) => {
|
|
downloads++;
|
|
assert.ok(checkpoint);
|
|
fs.writeFileSync(file, Buffer.from("RIFF1234WAVEfixtureaudio"));
|
|
};
|
|
global.__audioUpload = async (name, bytes) => {
|
|
uploads++;
|
|
if (uploads === 1) throw Error("storage unavailable");
|
|
assert.ok(bytes.byteLength);
|
|
return { url: "https://storage.example.com/archived.wav" };
|
|
};
|
|
const input = { mode: "tts", modelRef: "official", text: "测试", voice: "Cherry" };
|
|
const lifecycle = {
|
|
assertActive: async () => {},
|
|
checkpoint: async (value) => {
|
|
checkpoint = value.upstreamAudio;
|
|
},
|
|
};
|
|
await assert.rejects(() => executeCanvasAudio(input, lifecycle), /转存暂未完成/);
|
|
assert.equal(posts, 1);
|
|
assert.ok(checkpoint);
|
|
const result = await executeCanvasAudio(input, { ...lifecycle, resume: checkpoint });
|
|
assert.equal(result.url, "https://storage.example.com/archived.wav");
|
|
assert.equal(posts, 1);
|
|
assert.equal(downloads, 2);
|
|
await assert.rejects(
|
|
() => executeCanvasAudio(input, { ...lifecycle, resume: { ...checkpoint, expiresAt: 1 } }),
|
|
/过期/,
|
|
);
|
|
assert.equal(posts, 1);
|
|
global.fetch = async () => {
|
|
posts++;
|
|
throw Error("connection lost");
|
|
};
|
|
await assert.rejects(() => executeCanvasAudio(input, lifecycle), /不会自动重发/);
|
|
assert.equal(posts, 2);
|
|
global.fetch = async () => Response.json({ code: "Forbidden" }, { status: 403 });
|
|
await assert.rejects(() => executeCanvasAudio(input, lifecycle), /403/);
|
|
await assert.rejects(
|
|
() =>
|
|
executeCanvasAudio(input, {
|
|
...lifecycle,
|
|
assertActive: async () => {
|
|
throw Error("lost lease");
|
|
},
|
|
}),
|
|
/lost lease/,
|
|
);
|
|
console.log(
|
|
"PASS: actual adapter body, checkpoint-before-download, archival recovery without POST, expiry, transport uncertainty, provider failure and lease fencing",
|
|
);
|
|
})().catch((e) => {
|
|
console.error(e);
|
|
process.exitCode = 1;
|
|
});
|