import { describe, test, expect } from "vitest"; import { buildFfmpegArgs, transcodeThreadCount, MAX_TRANSCODE_THREADS } from "./video-transcode"; /** Alle Werte, die im Argument-Array direkt auf das gesuchte Flag folgen. */ function valuesAfter(args: string[], flag: string): string[] { return args.flatMap((arg, index) => (arg === flag ? [args[index + 1]] : [])); } describe("transcodeThreadCount", () => { test("nutzt die Vorgabe, wenn nichts konfiguriert ist", () => { // Arrange & Act const threads = transcodeThreadCount(undefined); // Assert: eine feste, kleine Zahl — bewusst NICHT die Kernzahl des // Knotens, siehe Begruendung in video-transcode.ts. expect(threads).toBe(2); }); test("uebernimmt einen gueltigen konfigurierten Wert", () => { expect(transcodeThreadCount("4")).toBe(4); }); test("faellt bei unsinnigen Werten auf die Vorgabe zurueck", () => { // Arrange & Act & Assert: eine kaputte Env-Var darf den Upload nicht in // einen unbegrenzten ffmpeg-Lauf kippen lassen. expect(transcodeThreadCount("")).toBe(2); expect(transcodeThreadCount("0")).toBe(2); expect(transcodeThreadCount("-3")).toBe(2); expect(transcodeThreadCount("2.5")).toBe(2); expect(transcodeThreadCount("viele")).toBe(2); }); test("deckelt nach oben, damit die Obergrenze nicht umgangen werden kann", () => { expect(transcodeThreadCount("999")).toBe(MAX_TRANSCODE_THREADS); }); }); describe("buildFfmpegArgs", () => { test("begrenzt Dekoder-, Filter- und Encoder-Threads auf denselben Wert", () => { // Arrange const threads = 2; // Act const args = buildFfmpegArgs("/tmp/in.mp4", "/tmp/out.mp4", threads); // Assert: ohne diese Deckel waehlt x264 seine Threadzahl nach den Kernen // des KNOTENS, nicht nach dem CPU-Anteil des Pods — genau das sprengte // das Speicherlimit und blockierte die Liveness-Probe. expect(valuesAfter(args, "-threads")).toEqual(["2", "2"]); expect(valuesAfter(args, "-filter_threads")).toEqual(["2"]); }); test("setzt den Dekoder-Deckel vor die Eingabedatei", () => { // Arrange & Act const args = buildFfmpegArgs("/tmp/in.mp4", "/tmp/out.mp4", 2); // Assert: ffmpeg ordnet Optionen der NAECHSTEN Datei zu. Ein "-threads" // erst nach "-i" gilt nur noch dem Encoder — das Dekodieren eines // 4K-Videos bliebe unbegrenzt. const firstThreads = args.indexOf("-threads"); const input = args.indexOf("-i"); expect(firstThreads).toBeGreaterThanOrEqual(0); expect(firstThreads).toBeLessThan(input); }); test("laesst ffmpeg nie auf die Standardeingabe warten", () => { // Arrange & Act & Assert: der Kindprozess bekommt eine Pipe als stdin, // die niemand bedient — ohne -nostdin kann ffmpeg dort auf // Tastatureingaben warten und der Upload haengt bis zum Zeitlimit. expect(buildFfmpegArgs("/tmp/in.mp4", "/tmp/out.mp4", 2)).toContain("-nostdin"); }); test("behaelt die web-optimierenden Einstellungen bei", () => { // Arrange & Act const args = buildFfmpegArgs("/tmp/in.mp4", "/tmp/out.mp4", 2); // Assert: Tonspur weg, auf 1920 begrenzt, faststart — die Gruende stehen // am Modulkommentar in video-transcode.ts. expect(args).toContain("-an"); expect(valuesAfter(args, "-vf")).toEqual(["scale='min(1920,iw)':-2"]); expect(valuesAfter(args, "-movflags")).toEqual(["+faststart"]); expect(valuesAfter(args, "-crf")).toEqual(["26"]); expect(valuesAfter(args, "-i")).toEqual(["/tmp/in.mp4"]); expect(args[args.length - 1]).toBe("/tmp/out.mp4"); }); });