Files
OmniRoute/tests/unit/guardrails/videoBridgeSubtitleRuntime.test.ts
Diego Rodrigues de Sa e Souza 60dc242178 feat(video): derive embedded subtitle provenance in the protected broker (#11659) (#12011)
FU-05 subtitle adapter (Refs #11659 — deliberately not Closes: the adapter is not yet wired into the live describeVideoPart path, that composition point is sibling #12009 which just landed): server-owned, loopback-only ffprobe/ffmpeg subtitle extraction that legitimately earns the "embedded" provenance label, mirroring the existing frame-extraction lifecycle. Broker route now also serves ?subtitles=1, stamped with the shared broker fingerprint so the client-side adapter can verify the payload actually came from the trusted process. Bounded, ReDoS-safe WebVTT parser.
2026-08-29 19:33:27 -03:00

197 lines
7.0 KiB
TypeScript

import assert from "node:assert/strict";
import { access, writeFile } from "node:fs/promises";
import test from "node:test";
import {
extractSubtitleStreamToFile,
extractVideoSubtitlesFromBytes,
probeSubtitleStreams,
readBoundedSubtitleOutput,
VIDEO_SUBTITLE_MAX_OUTPUT_BYTES,
} from "../../../src/lib/guardrails/videoBridgeSubtitleRuntime.ts";
import { type VideoCommandRunner } from "../../../src/lib/guardrails/videoBridgeRuntime.ts";
const CONTAINER_PROBE_STDOUT = JSON.stringify({
format: { duration: "12.0", format_name: "mov,mp4,m4a,3gp,3g2,mj2" },
streams: [{ index: 0, codec_type: "video", width: 640, height: 360 }],
});
function isSubtitleStreamProbe(args: readonly string[]): boolean {
return args.some((arg) => arg.includes("codec_name"));
}
test("lists only allowlisted subtitle streams, ordered by index and capped at two", async () => {
const runner: VideoCommandRunner = async (executable, args) => {
assert.equal(executable, "ffprobe");
assert.ok(isSubtitleStreamProbe(args));
return {
stdout: JSON.stringify({
streams: [
{ index: 4, codec_type: "subtitle", codec_name: "ass" },
{ index: 3, codec_type: "subtitle", codec_name: "webvtt" },
{ index: 1, codec_type: "subtitle", codec_name: "subrip" },
{ index: 2, codec_type: "subtitle", codec_name: "mov_text" },
{ index: 0, codec_type: "audio", codec_name: "aac" },
],
}),
stderr: "",
};
};
const probes = await probeSubtitleStreams("/tmp/input.video", { runner, timeoutMs: 5_000 });
assert.deepEqual(probes, [
{ codecName: "subrip", streamIndex: 1 },
{ codecName: "mov_text", streamIndex: 2 },
]);
});
test("an unparseable ffprobe response yields zero subtitle streams instead of throwing", async () => {
const runner: VideoCommandRunner = async () => ({ stdout: "not json", stderr: "" });
const probes = await probeSubtitleStreams("/tmp/input.video", { runner, timeoutMs: 5_000 });
assert.deepEqual(probes, []);
});
test("extracts a single allowlisted stream to bounded WebVTT text", async () => {
const runner: VideoCommandRunner = async (executable, args) => {
if (executable === "ffmpeg") {
await writeFile(args.at(-1) ?? "", "WEBVTT\n\n00:00:01.000 --> 00:00:02.000\nhi\n");
}
return { stdout: "", stderr: "" };
};
const outputPath = "/tmp/omniroute-subtitle-runtime-test.vtt";
await extractSubtitleStreamToFile("/tmp/input.video", outputPath, 3, {
runner,
timeoutMs: 5_000,
});
const text = await readBoundedSubtitleOutput(outputPath);
assert.match(text, /WEBVTT/);
});
test("readBoundedSubtitleOutput rejects output above the byte cap", async () => {
const outputPath = "/tmp/omniroute-subtitle-runtime-oversized.vtt";
await writeFile(outputPath, "x".repeat(VIDEO_SUBTITLE_MAX_OUTPUT_BYTES + 1));
await assert.rejects(() => readBoundedSubtitleOutput(outputPath), /byte limit/);
});
test("readBoundedSubtitleOutput rejects an empty extraction result", async () => {
const outputPath = "/tmp/omniroute-subtitle-runtime-empty.vtt";
await writeFile(outputPath, "");
await assert.rejects(() => readBoundedSubtitleOutput(outputPath), /no output/);
});
test("full bytes-to-WebVTT pipeline probes the container once, extracts up to two streams, and always cleans up", async () => {
const calls: string[] = [];
let capturedInputPath = "";
const runner: VideoCommandRunner = async (executable, args) => {
calls.push(executable);
if (capturedInputPath === "") capturedInputPath = String(args.at(-1));
if (executable === "ffprobe") {
if (isSubtitleStreamProbe(args)) {
return {
stdout: JSON.stringify({
streams: [
{ index: 2, codec_type: "subtitle", codec_name: "webvtt" },
{ index: 3, codec_type: "subtitle", codec_name: "subrip" },
],
}),
stderr: "",
};
}
return { stdout: CONTAINER_PROBE_STDOUT, stderr: "" };
}
await writeFile(args.at(-1) ?? "", "WEBVTT\n\n00:00:01.000 --> 00:00:02.000\nhello\n");
return { stdout: "", stderr: "" };
};
const result = await extractVideoSubtitlesFromBytes(Buffer.from("fake-video-bytes"), {
maxDurationSeconds: 600,
runner,
timeoutMs: 5_000,
});
assert.equal(result.durationSeconds, 12);
assert.equal(result.streams.length, 2);
assert.deepEqual(
result.streams.map((stream) => stream.streamIndex),
[2, 3]
);
assert.equal(calls.filter((name) => name === "ffmpeg").length, 2);
assert.ok(capturedInputPath.includes("omniroute-video-subtitle-"));
const temporaryDirectory = capturedInputPath.slice(0, capturedInputPath.lastIndexOf("/"));
await assert.rejects(() => access(temporaryDirectory));
});
test("one unusable stream (extraction throws) does not block trying the next allowlisted stream", async () => {
let ffmpegCalls = 0;
const runner: VideoCommandRunner = async (executable, args) => {
if (executable === "ffprobe") {
if (isSubtitleStreamProbe(args)) {
return {
stdout: JSON.stringify({
streams: [
{ index: 2, codec_type: "subtitle", codec_name: "webvtt" },
{ index: 5, codec_type: "subtitle", codec_name: "mov_text" },
],
}),
stderr: "",
};
}
return { stdout: CONTAINER_PROBE_STDOUT, stderr: "" };
}
ffmpegCalls += 1;
if (ffmpegCalls === 1) throw new Error("simulated ffmpeg failure");
await writeFile(args.at(-1) ?? "", "WEBVTT\n\n00:00:01.000 --> 00:00:02.000\nok\n");
return { stdout: "", stderr: "" };
};
const result = await extractVideoSubtitlesFromBytes(Buffer.from("fake-video-bytes"), {
maxDurationSeconds: 600,
runner,
timeoutMs: 5_000,
});
assert.equal(result.streams.length, 1);
assert.equal(result.streams[0].streamIndex, 5);
});
test("temp directory is removed even when the container probe itself fails", async () => {
let capturedInputPath = "";
const runner: VideoCommandRunner = async (_executable, args) => {
capturedInputPath = String(args.at(-1));
throw new Error("ffprobe unavailable");
};
await assert.rejects(() =>
extractVideoSubtitlesFromBytes(Buffer.from("fake-video-bytes"), {
maxDurationSeconds: 600,
runner,
timeoutMs: 5_000,
})
);
assert.ok(capturedInputPath.includes("omniroute-video-subtitle-"));
const temporaryDirectory = capturedInputPath.slice(0, capturedInputPath.lastIndexOf("/"));
await assert.rejects(() => access(temporaryDirectory));
});
test("an already-aborted signal is rejected before any command runs", async () => {
const controller = new AbortController();
controller.abort();
let ran = false;
const runner: VideoCommandRunner = async () => {
ran = true;
return { stdout: "", stderr: "" };
};
await assert.rejects(
() =>
extractVideoSubtitlesFromBytes(Buffer.from("fake-video-bytes"), {
maxDurationSeconds: 600,
runner,
signal: controller.signal,
timeoutMs: 5_000,
}),
/aborted/
);
assert.equal(ran, false);
});