137 lines
4.7 KiB
TypeScript
137 lines
4.7 KiB
TypeScript
import { test, expect } from "@playwright/test";
|
|
import { mkdtemp, writeFile, readFile, rm } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
import { createHash } from "node:crypto";
|
|
import {
|
|
remoteAudio,
|
|
remoteAudioCapabilities,
|
|
} from "@/lib/video-canvas/smart-edit/remote-audio";
|
|
import { ProviderTaskUncertainError } from "@/lib/video-canvas/video-task-lifecycle";
|
|
test.describe.configure({ mode: "serial" });
|
|
const originalFetch = global.fetch,
|
|
oldUrl = process.env.SMART_EDIT_AUDIO_URL,
|
|
oldToken = process.env.SMART_EDIT_AUDIO_TOKEN;
|
|
test.beforeEach(() => {
|
|
process.env.SMART_EDIT_AUDIO_URL = "http://127.0.0.1:19342";
|
|
process.env.SMART_EDIT_AUDIO_TOKEN = "test-".repeat(8);
|
|
});
|
|
test.afterEach(() => {
|
|
global.fetch = originalFetch;
|
|
if (oldUrl === undefined) delete process.env.SMART_EDIT_AUDIO_URL;
|
|
else process.env.SMART_EDIT_AUDIO_URL = oldUrl;
|
|
if (oldToken === undefined) delete process.env.SMART_EDIT_AUDIO_TOKEN;
|
|
else process.env.SMART_EDIT_AUDIO_TOKEN = oldToken;
|
|
});
|
|
test("audio uploads once, resumes same identity, and isolates tenant/task/content", async () => {
|
|
const dir = await mkdtemp(path.join(tmpdir(), "audio-identity-")),
|
|
file = path.join(dir, "input.wav");
|
|
await writeFile(file, "fixture bytes");
|
|
const ids: string[] = [],
|
|
jobs = new Set<string>();
|
|
let uploads = 0;
|
|
let expected = "fixture bytes";
|
|
global.fetch = (async (_url: any, init: any) => {
|
|
if (init.method === "POST") {
|
|
const input = JSON.parse(init.body);
|
|
ids.push(input.id);
|
|
return Response.json({
|
|
id: input.id,
|
|
status: jobs.has(input.id) ? "succeeded" : "awaiting-input",
|
|
stage: "ready",
|
|
...(jobs.has(input.id)
|
|
? {
|
|
result: {
|
|
durationSeconds: 1,
|
|
subtitles: [{ start: 0, end: 800000, text: "测试" }],
|
|
},
|
|
}
|
|
: {}),
|
|
});
|
|
}
|
|
const id = String(_url).split("/").at(-2)!;
|
|
let data = "";
|
|
for await (const c of init.body) data += c;
|
|
expect(data).toBe(expected);
|
|
uploads++;
|
|
jobs.add(id);
|
|
return Response.json({
|
|
id,
|
|
status: "succeeded",
|
|
stage: "done",
|
|
result: {
|
|
durationSeconds: 1,
|
|
subtitles: [{ start: 0, end: 800000, text: "测试" }],
|
|
},
|
|
});
|
|
}) as any;
|
|
try {
|
|
await remoteAudio("transcribe", file, "owner", "task", dir);
|
|
await remoteAudio("transcribe", file, "owner", "task", dir);
|
|
expect(uploads).toBe(1);
|
|
expect(ids[0]).toBe(ids[1]);
|
|
await remoteAudio("transcribe", file, "other", "task", dir);
|
|
await remoteAudio("transcribe", file, "owner", "other-task", dir);
|
|
expect(new Set(ids).size).toBe(3);
|
|
expected = "updated fixture";
|
|
await writeFile(file, expected);
|
|
await remoteAudio("transcribe", file, "owner", "task", dir);
|
|
expect(new Set(ids).size).toBe(4);
|
|
expect(uploads).toBe(4);
|
|
} finally {
|
|
await rm(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
test("unavailable remote audio remains recoverable and does not advertise local fallback", async () => {
|
|
const dir = await mkdtemp(path.join(tmpdir(), "audio-offline-")),
|
|
file = path.join(dir, "input.wav");
|
|
await writeFile(file, "audio");
|
|
global.fetch = (async () => {
|
|
throw Error("offline");
|
|
}) as any;
|
|
try {
|
|
expect(await remoteAudioCapabilities()).toEqual({
|
|
asr: false,
|
|
separation: false,
|
|
});
|
|
await expect(
|
|
remoteAudio("transcribe", file, "u", "t", dir)
|
|
).rejects.toBeInstanceOf(ProviderTaskUncertainError);
|
|
} finally {
|
|
await rm(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
test("corrupt separated download never replaces saved stem", async () => {
|
|
const dir = await mkdtemp(path.join(tmpdir(), "audio-integrity-")),
|
|
file = path.join(dir, "input.wav");
|
|
await writeFile(file, "audio");
|
|
await writeFile(path.join(dir, "vocals.wav"), "previous");
|
|
const bytes = Buffer.from("expected"),
|
|
sha256 = createHash("sha256").update(bytes).digest("hex");
|
|
global.fetch = (async (_url: any, init: any) =>
|
|
init.method === "POST"
|
|
? Response.json({
|
|
id: JSON.parse(init.body).id,
|
|
status: "succeeded",
|
|
stage: "done",
|
|
result: {
|
|
durationSeconds: 1,
|
|
stems: {
|
|
vocals: { bytes: bytes.length, sha256 },
|
|
background: { bytes: bytes.length, sha256 },
|
|
},
|
|
},
|
|
})
|
|
: new Response("corrupt!")) as any;
|
|
try {
|
|
await expect(
|
|
remoteAudio("separate", file, "u", "t", dir)
|
|
).rejects.toBeInstanceOf(ProviderTaskUncertainError);
|
|
expect(await readFile(path.join(dir, "vocals.wav"), "utf8")).toBe(
|
|
"previous"
|
|
);
|
|
} finally {
|
|
await rm(dir, { recursive: true, force: true });
|
|
}
|
|
});
|