test(core): expand mpv/subsync/tokenizer and cli coverage

This commit is contained in:
kyasuda
2026-02-10 13:13:47 -08:00
committed by sudacode
parent f868fdbbb3
commit 35cad19839
6 changed files with 749 additions and 1 deletions

View File

@@ -0,0 +1,300 @@
import test from "node:test";
import assert from "node:assert/strict";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import {
TriggerSubsyncFromConfigDeps,
runSubsyncManualService,
triggerSubsyncFromConfigService,
} from "./subsync-service";
function makeDeps(
overrides: Partial<TriggerSubsyncFromConfigDeps> = {},
): TriggerSubsyncFromConfigDeps {
const mpvClient = {
connected: true,
currentAudioStreamIndex: null,
send: () => {},
requestProperty: async (name: string) => {
if (name === "path") return "/tmp/video.mkv";
if (name === "sid") return 1;
if (name === "secondary-sid") return null;
if (name === "track-list") {
return [
{ id: 1, type: "sub", selected: true, lang: "jpn" },
{
id: 2,
type: "sub",
selected: false,
external: true,
lang: "eng",
"external-filename": "/tmp/ref.srt",
},
{ id: 3, type: "audio", selected: true, "ff-index": 1 },
];
}
return null;
},
};
return {
getMpvClient: () => mpvClient,
getResolvedConfig: () => ({
defaultMode: "manual",
alassPath: "/usr/bin/alass",
ffsubsyncPath: "/usr/bin/ffsubsync",
ffmpegPath: "/usr/bin/ffmpeg",
}),
isSubsyncInProgress: () => false,
setSubsyncInProgress: () => {},
showMpvOsd: () => {},
runWithSubsyncSpinner: async <T>(task: () => Promise<T>) => task(),
openManualPicker: () => {},
...overrides,
};
}
test("triggerSubsyncFromConfigService returns early when already in progress", async () => {
const osd: string[] = [];
await triggerSubsyncFromConfigService(
makeDeps({
isSubsyncInProgress: () => true,
showMpvOsd: (text) => {
osd.push(text);
},
}),
);
assert.deepEqual(osd, ["Subsync already running"]);
});
test("triggerSubsyncFromConfigService opens manual picker in manual mode", async () => {
const osd: string[] = [];
let payloadTrackCount = 0;
let inProgressState: boolean | null = null;
await triggerSubsyncFromConfigService(
makeDeps({
openManualPicker: (payload) => {
payloadTrackCount = payload.sourceTracks.length;
},
showMpvOsd: (text) => {
osd.push(text);
},
setSubsyncInProgress: (value) => {
inProgressState = value;
},
}),
);
assert.equal(payloadTrackCount, 1);
assert.ok(osd.includes("Subsync: choose engine and source"));
assert.equal(inProgressState, false);
});
test("triggerSubsyncFromConfigService reports failures to OSD", async () => {
const osd: string[] = [];
await triggerSubsyncFromConfigService(
makeDeps({
getMpvClient: () => null,
showMpvOsd: (text) => {
osd.push(text);
},
}),
);
assert.ok(osd.some((line) => line.startsWith("Subsync failed: MPV not connected")));
});
test("runSubsyncManualService requires a source track for alass", async () => {
const result = await runSubsyncManualService(
{ engine: "alass", sourceTrackId: null },
makeDeps(),
);
assert.deepEqual(result, {
ok: false,
message: "Select a subtitle source track for alass",
});
});
test("triggerSubsyncFromConfigService reports path validation failures", async () => {
const osd: string[] = [];
const inProgress: boolean[] = [];
await triggerSubsyncFromConfigService(
makeDeps({
getResolvedConfig: () => ({
defaultMode: "auto",
alassPath: "/missing/alass",
ffsubsyncPath: "/missing/ffsubsync",
ffmpegPath: "/missing/ffmpeg",
}),
setSubsyncInProgress: (value) => {
inProgress.push(value);
},
showMpvOsd: (text) => {
osd.push(text);
},
}),
);
assert.deepEqual(inProgress, [true, false]);
assert.ok(
osd.some((line) =>
line.startsWith("Subsync failed: Configured ffmpeg executable not found"),
),
);
});
function writeExecutableScript(filePath: string, content: string): void {
fs.writeFileSync(filePath, content, { encoding: "utf8", mode: 0o755 });
fs.chmodSync(filePath, 0o755);
}
test("runSubsyncManualService constructs ffsubsync command and returns success", async () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "subsync-ffsubsync-"));
const ffsubsyncLogPath = path.join(tmpDir, "ffsubsync-args.log");
const ffsubsyncPath = path.join(tmpDir, "ffsubsync.sh");
const ffmpegPath = path.join(tmpDir, "ffmpeg.sh");
const alassPath = path.join(tmpDir, "alass.sh");
const videoPath = path.join(tmpDir, "video.mkv");
const primaryPath = path.join(tmpDir, "primary.srt");
fs.writeFileSync(videoPath, "video");
fs.writeFileSync(primaryPath, "sub");
writeExecutableScript(
ffmpegPath,
"#!/bin/sh\nexit 0\n",
);
writeExecutableScript(
alassPath,
"#!/bin/sh\nexit 0\n",
);
writeExecutableScript(
ffsubsyncPath,
`#!/bin/sh\n: > "${ffsubsyncLogPath}"\nfor arg in "$@"; do printf '%s\\n' "$arg" >> "${ffsubsyncLogPath}"; done\nout=\"\"\nprev=\"\"\nfor arg in \"$@\"; do\n if [ \"$prev\" = \"-o\" ]; then out=\"$arg\"; fi\n prev=\"$arg\"\ndone\nif [ -n \"$out\" ]; then : > \"$out\"; fi\nexit 0\n`,
);
const sentCommands: Array<Array<string | number>> = [];
const deps = makeDeps({
getMpvClient: () => ({
connected: true,
currentAudioStreamIndex: 2,
send: (payload) => {
sentCommands.push(payload.command);
},
requestProperty: async (name: string) => {
if (name === "path") return videoPath;
if (name === "sid") return 1;
if (name === "secondary-sid") return null;
if (name === "track-list") {
return [
{
id: 1,
type: "sub",
selected: true,
external: true,
"external-filename": primaryPath,
},
];
}
return null;
},
}),
getResolvedConfig: () => ({
defaultMode: "manual",
alassPath,
ffsubsyncPath,
ffmpegPath,
}),
});
const result = await runSubsyncManualService(
{ engine: "ffsubsync", sourceTrackId: null },
deps,
);
assert.equal(result.ok, true);
assert.equal(result.message, "Subtitle synchronized with ffsubsync");
const ffArgs = fs.readFileSync(ffsubsyncLogPath, "utf8").trim().split("\n");
assert.equal(ffArgs[0], videoPath);
assert.ok(ffArgs.includes("-i"));
assert.ok(ffArgs.includes(primaryPath));
assert.ok(ffArgs.includes("--reference-stream"));
assert.ok(ffArgs.includes("0:2"));
assert.equal(sentCommands[0]?.[0], "sub_add");
assert.deepEqual(sentCommands[1], ["set_property", "sub-delay", 0]);
});
test("runSubsyncManualService constructs alass command and returns failure on non-zero exit", async () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "subsync-alass-"));
const alassLogPath = path.join(tmpDir, "alass-args.log");
const alassPath = path.join(tmpDir, "alass.sh");
const ffmpegPath = path.join(tmpDir, "ffmpeg.sh");
const ffsubsyncPath = path.join(tmpDir, "ffsubsync.sh");
const videoPath = path.join(tmpDir, "video.mkv");
const primaryPath = path.join(tmpDir, "primary.srt");
const sourcePath = path.join(tmpDir, "source.srt");
fs.writeFileSync(videoPath, "video");
fs.writeFileSync(primaryPath, "sub");
fs.writeFileSync(sourcePath, "sub2");
writeExecutableScript(ffmpegPath, "#!/bin/sh\nexit 0\n");
writeExecutableScript(ffsubsyncPath, "#!/bin/sh\nexit 0\n");
writeExecutableScript(
alassPath,
`#!/bin/sh\n: > "${alassLogPath}"\nfor arg in "$@"; do printf '%s\\n' "$arg" >> "${alassLogPath}"; done\nexit 1\n`,
);
const deps = makeDeps({
getMpvClient: () => ({
connected: true,
currentAudioStreamIndex: null,
send: () => {},
requestProperty: async (name: string) => {
if (name === "path") return videoPath;
if (name === "sid") return 1;
if (name === "secondary-sid") return null;
if (name === "track-list") {
return [
{
id: 1,
type: "sub",
selected: true,
external: true,
"external-filename": primaryPath,
},
{
id: 2,
type: "sub",
selected: false,
external: true,
"external-filename": sourcePath,
},
];
}
return null;
},
}),
getResolvedConfig: () => ({
defaultMode: "manual",
alassPath,
ffsubsyncPath,
ffmpegPath,
}),
});
const result = await runSubsyncManualService(
{ engine: "alass", sourceTrackId: 2 },
deps,
);
assert.deepEqual(result, {
ok: false,
message: "alass synchronization failed",
});
const alassArgs = fs.readFileSync(alassLogPath, "utf8").trim().split("\n");
assert.equal(alassArgs[0], sourcePath);
assert.equal(alassArgs[1], primaryPath);
});