import { describe, expect, test } from "bun:test"; import { enqueueUnseenJobs, extractErrorSummary, shouldSendLiveUpdate, yieldAfterChunk } from "../execute"; describe("extractErrorSummary", () => { test("pulls the real error line out of ffmpeg's banner", () => { const lines = [ "[stderr] ffmpeg version 7.1.3 ...", "[stderr] built with gcc 14", "[stderr] Stream #0:2(eng): Subtitle: dvd_subtitle (dvdsub), 1280x720", "[stderr] Stream mapping:", "[stderr] Stream #0:2 -> #0:0 (copy)", "[stderr] [srt @ 0x55] Unsupported subtitles codec: dvd_subtitle", "[stderr] [out#0/srt @ 0x55] Could not write header (incorrect codec parameters ?): Invalid argument", "[stderr] Conversion failed!", ]; const summary = extractErrorSummary(lines, new Error("FFmpeg exited with code 234")); expect(summary).toContain("Unsupported subtitles codec: dvd_subtitle"); expect(summary).toContain("Invalid argument"); expect(summary).toContain("Conversion failed!"); // Should NOT include the banner lines. expect(summary).not.toContain("ffmpeg version"); expect(summary).not.toContain("Stream #0:2"); }); test("dedupes identical fatal lines (e.g. repeated warnings)", () => { const lines = ["[stderr] Conversion failed!", "[stderr] Conversion failed!", "[stderr] Conversion failed!"]; const summary = extractErrorSummary(lines); expect(summary?.split("\n").length).toBe(1); }); test("falls back to the thrown error when no fatal line is found", () => { const lines = ["[stderr] ffmpeg version 7", "[stderr] Duration: 00:10:00"]; const summary = extractErrorSummary(lines, new Error("FFmpeg exited with code 1")); expect(summary).toBe("Error: FFmpeg exited with code 1"); }); test("returns null when neither a fatal line nor a thrown error is available", () => { expect(extractErrorSummary([])).toBe(null); expect(extractErrorSummary(["[stderr] ffmpeg version 7"])).toBe(null); }); test("only scans the tail — a banner from a prior run doesn't leak through", () => { // 70 filler lines, real error at the very end; scan window is 60. const filler = Array.from({ length: 70 }, (_, i) => `[stderr] banner line ${i}`); const lines = [...filler, "[stderr] Error: no space left on device"]; const summary = extractErrorSummary(lines); expect(summary).toBe("Error: no space left on device"); }); }); describe("shouldSendLiveUpdate", () => { test("throttles updates until interval passes", () => { expect(shouldSendLiveUpdate(1_000, 800, 500)).toBe(false); expect(shouldSendLiveUpdate(1_301, 800, 500)).toBe(true); }); }); describe("yieldAfterChunk", () => { test("yields once threshold is reached, resets chunk counter", async () => { let yieldCalls = 0; const sleep = async (_ms: number) => { yieldCalls += 1; }; let chunks = 0; chunks = await yieldAfterChunk(chunks, 3, sleep); expect(chunks).toBe(1); chunks = await yieldAfterChunk(chunks, 3, sleep); expect(chunks).toBe(2); chunks = await yieldAfterChunk(chunks, 3, sleep); expect(chunks).toBe(0); expect(yieldCalls).toBe(1); }); }); describe("enqueueUnseenJobs", () => { test("appends only unseen job ids to the active queue", () => { const queue = [{ id: 1 }, { id: 2 }] as { id: number }[]; const seen = new Set([1, 2]); const added = enqueueUnseenJobs(queue, seen, [{ id: 2 }, { id: 3 }, { id: 4 }] as { id: number }[]); expect(added).toBe(2); expect(queue.map((j) => j.id)).toEqual([1, 2, 3, 4]); expect(seen.has(3)).toBeTrue(); expect(seen.has(4)).toBeTrue(); }); });