Skip to content

Commit e6858ac

Browse files
committed
test(web): add unit tests for video-convert ffmpeg flow
Made-with: Cursor
1 parent 46bed19 commit e6858ac

1 file changed

Lines changed: 95 additions & 0 deletions

File tree

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { EventEmitter } from "node:events";
2+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3+
4+
vi.mock("ffmpeg-static", () => ({
5+
default: "/usr/local/bin/ffmpeg",
6+
}));
7+
8+
const mockReadFile = vi.fn(async (_path: string) => Buffer.from("video-data"));
9+
const mockUnlink = vi.fn(async (_path: string) => undefined);
10+
11+
vi.mock("node:fs", async (importOriginal) => {
12+
const actual = await importOriginal<typeof import("node:fs")>();
13+
return {
14+
...actual,
15+
existsSync: (path: string) => path === "/usr/local/bin/ffmpeg",
16+
promises: {
17+
...actual.promises,
18+
readFile: (path: string) => mockReadFile(path),
19+
unlink: (path: string) => mockUnlink(path),
20+
},
21+
};
22+
});
23+
24+
class MockChildProcess extends EventEmitter {
25+
stderr = new EventEmitter();
26+
}
27+
28+
let spawnedProcesses: MockChildProcess[] = [];
29+
let spawnArgs: { command: string; args: string[] }[] = [];
30+
31+
vi.mock("node:child_process", () => ({
32+
spawn: (command: string, args: string[]) => {
33+
const proc = new MockChildProcess();
34+
spawnedProcesses.push(proc);
35+
spawnArgs.push({ command, args });
36+
return proc;
37+
},
38+
}));
39+
40+
describe("video-convert", () => {
41+
beforeEach(() => {
42+
vi.clearAllMocks();
43+
spawnedProcesses = [];
44+
spawnArgs = [];
45+
});
46+
47+
afterEach(() => {
48+
vi.resetModules();
49+
});
50+
51+
it("uses stream copy before transcoding", async () => {
52+
const { convertRemoteVideoToMp4Buffer } = await import(
53+
"@/lib/video-convert"
54+
);
55+
56+
const resultPromise = convertRemoteVideoToMp4Buffer(
57+
"https://example.com/video.m3u8",
58+
);
59+
60+
setTimeout(() => {
61+
spawnedProcesses[0]?.emit("close", 0);
62+
}, 10);
63+
64+
const result = await resultPromise;
65+
66+
expect(result.toString()).toBe("video-data");
67+
expect(spawnArgs).toHaveLength(1);
68+
expect(spawnArgs[0]?.args).toContain("-c");
69+
expect(spawnArgs[0]?.args).toContain("copy");
70+
});
71+
72+
it("falls back to transcoding when stream copy fails", async () => {
73+
const { convertRemoteVideoToMp4Buffer } = await import(
74+
"@/lib/video-convert"
75+
);
76+
77+
const resultPromise = convertRemoteVideoToMp4Buffer(
78+
"https://example.com/video.m3u8",
79+
);
80+
81+
setTimeout(() => {
82+
spawnedProcesses[0]?.stderr.emit("data", Buffer.from("copy failed"));
83+
spawnedProcesses[0]?.emit("close", 1);
84+
setTimeout(() => {
85+
spawnedProcesses[1]?.emit("close", 0);
86+
}, 10);
87+
}, 10);
88+
89+
await resultPromise;
90+
91+
expect(spawnArgs).toHaveLength(2);
92+
expect(spawnArgs[1]?.args).toContain("libx264");
93+
expect(spawnArgs[1]?.args).toContain("aac");
94+
});
95+
});

0 commit comments

Comments
 (0)