import { test } from "node:test"; import assert from "node:assert/strict"; import path from "node:path"; import { mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { transcribeOneVideo } from "./transcribeOne"; import { TranscribeError } from "./transcribeError"; import type { Paths } from "../lib/paths"; import type { Worker } from "../lib/workers"; // Run with: // pnpm --filter yt-dlp-transcript-common exec tsx --test common/controller/transcribeOne.test.ts // // Audio that VANISHES MID-RUN is a skip, not a failure. Another lane removing // the file — today the backfill's re-acquire cleanup, which runs in a `finally` // and holds no per-video lock — must not append the video to // failed-transcriptions, which the manual per-channel batch honours forever. // // Driven with a shell script as the worker binary (the repo's fake-bin // convention; this codebase does not module-mock), so the engine's exit is real. async function runWith(script: string): Promise { const dir = await mkdtemp(path.join(tmpdir(), "transcribe-one-")); try { const bin = path.join(dir, "fake-engine.sh"); await writeFile(bin, `#!/bin/sh\n${script}\n`, { mode: 0o755 }); await writeFile(path.join(dir, "audio.mp3"), "not really audio"); const worker: Worker = { id: "w1", name: "fake", kind: "local", enabled: true, priority: 0, appId: "whisper-cpp", // `model` set so app.build never reaches getPaths(). config: { bin, model: "model.bin" }, }; return await transcribeOneVideo({ paths: {} as Paths, videoDir: dir, videoId: "vid1", audioFilename: "audio.mp3", worker, onLog: () => {}, }).then( (v) => v, (e) => e, ); } finally { await rm(dir, { recursive: true, force: true }); } } test("an engine that fails AFTER its audio disappeared is a no-audio skip", async () => { const err = await runWith("rm -f audio.mp3; exit 1"); assert.ok(err instanceof TranscribeError, `not a TranscribeError: ${err}`); assert.equal(err.failureClass, "no-audio"); assert.match(String(err.message), /vanished mid-run/); }); test("an engine that just fails is still a real failure", async () => { // The guard must not swallow genuine engine failures into the skip class — // that would silently stop the queue ever recording a bad video. const err = await runWith("exit 1"); assert.ok(err instanceof Error, `not an Error: ${err}`); assert.notEqual((err as TranscribeError).failureClass, "no-audio"); }); test("an engine that exits 0 with no output, audio gone, is a skip too", async () => { const err = await runWith("rm -f audio.mp3; exit 0"); assert.ok(err instanceof TranscribeError, `not a TranscribeError: ${err}`); assert.equal(err.failureClass, "no-audio"); });