Files
netfelix-audio-fix/server/api/__tests__/review.test.ts
Felix Förtsch 4f1433437b
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m35s
dedupe pending jobs to stop rapid-fire approvals from spawning ghost ffmpeg runs
root cause: all five job-insert sites in review.ts blindly inserted a
'pending' row, so a double-click on approve (or an overlap between
/approve-all and individual /approve) wrote N jobs for the same item.
job 1 stripped subtitles + reordered audio; jobs 2..N then ran the
same stale stream-index command against the already-processed file
and ffmpeg bailed with 'Stream map matches no streams'.

fix: funnel every insert through enqueueAudioJob(), which only writes
when no pending job already exists for that item. covers approve,
retry, approve-all, season approve-all, series approve-all.
2026-04-14 07:36:15 +02:00

51 lines
1.8 KiB
TypeScript

import { Database } from "bun:sqlite";
import { describe, expect, test } from "bun:test";
import { SCHEMA } from "../../db/schema";
import { enqueueAudioJob } from "../review";
function makeDb(): Database {
const db = new Database(":memory:");
for (const stmt of SCHEMA.split(";")) {
const trimmed = stmt.trim();
if (trimmed) db.run(trimmed);
}
db
.prepare("INSERT INTO media_items (id, jellyfin_id, type, name, file_path) VALUES (?, ?, 'Movie', 'T', '/x.mkv')")
.run(1, "jf-1");
return db;
}
describe("enqueueAudioJob dedup", () => {
test("inserts a job when none exists", () => {
const db = makeDb();
expect(enqueueAudioJob(db, 1, "ffmpeg a")).toBe(true);
const { n } = db.prepare("SELECT COUNT(*) as n FROM jobs WHERE item_id = 1").get() as { n: number };
expect(n).toBe(1);
});
test("no-ops when a pending job already exists", () => {
const db = makeDb();
enqueueAudioJob(db, 1, "ffmpeg a");
expect(enqueueAudioJob(db, 1, "ffmpeg b")).toBe(false);
expect(enqueueAudioJob(db, 1, "ffmpeg c")).toBe(false);
const rows = db.prepare("SELECT command FROM jobs WHERE item_id = 1").all() as { command: string }[];
expect(rows.length).toBe(1);
expect(rows[0].command).toBe("ffmpeg a");
});
test("allows a new pending job once the previous one is done or errored", () => {
const db = makeDb();
enqueueAudioJob(db, 1, "ffmpeg a");
db.prepare("UPDATE jobs SET status = 'done' WHERE item_id = 1").run();
expect(enqueueAudioJob(db, 1, "ffmpeg b")).toBe(true);
db.prepare("UPDATE jobs SET status = 'error' WHERE command = 'ffmpeg b'").run();
expect(enqueueAudioJob(db, 1, "ffmpeg c")).toBe(true);
const pending = db.prepare("SELECT COUNT(*) as n FROM jobs WHERE item_id = 1 AND status = 'pending'").get() as {
n: number;
};
expect(pending.n).toBe(1);
});
});