test(nodecg): cubrir lifecycle de process-manager con mocks

This commit is contained in:
Pandipipas
2026-02-21 18:37:54 +01:00
parent e3b78cf6ba
commit d3d33324ff
2 changed files with 186 additions and 25 deletions
+124
View File
@@ -0,0 +1,124 @@
import assert from "node:assert/strict";
import { EventEmitter } from "node:events";
import test from "node:test";
import { AppRuntimeConfig } from "../main/config/runtime-config";
import { createNodecgProcessManager } from "../main/nodecg/process-manager";
class MockChildProcess extends EventEmitter {
pid: number | undefined;
killed = false;
exitCode: number | null = null;
signalCode: NodeJS.Signals | null = null;
constructor(pid: number) {
super();
this.pid = pid;
}
}
function getBaseConfig(): AppRuntimeConfig {
return {
title: "Scoreko",
userModelId: "com.scoreko.desktop",
nodecgPort: "9090",
bundleName: "scoreko-dev",
dashboardRoute: "dashboard/scoreko-dev/main.html?standalone=true",
loadingRoute: "dashboard/loading/main.html?standalone=true",
loadDelayMs: 10000,
startupTimeoutMs: 100,
nodecgKillTimeoutMs: 10,
};
}
test("startNodeCG valida instalación de NodeCG antes de arrancar", () => {
const manager = createNodecgProcessManager({
isDev: true,
nodecgPath: "/fake/nodecg",
baseUrl: "http://127.0.0.1:9090",
runtimeConfig: getBaseConfig(),
log: () => undefined,
deps: {
pathExists: () => false,
spawnProcess: () => {
throw new Error("no debe intentar arrancar si la validación falla");
},
},
});
assert.throws(() => {
manager.startNodeCG();
}, /No existe la carpeta NodeCG/);
});
test("waitForNodeCGReady resuelve cuando el endpoint responde 404", async () => {
const child = new MockChildProcess(4321);
const manager = createNodecgProcessManager({
isDev: true,
nodecgPath: "/fake/nodecg",
baseUrl: "http://127.0.0.1:9090",
runtimeConfig: getBaseConfig(),
log: () => undefined,
deps: {
pathExists: () => true,
spawnProcess: () => child as unknown as import("node:child_process").ChildProcess,
fetchUrl: async () => ({ ok: false, status: 404 } as Response),
setTimer: ((handler: (...args: unknown[]) => void, _timeoutMs: number) => {
handler();
return 0 as never;
}),
stdoutWrite: () => undefined,
stderrWrite: () => undefined,
},
});
manager.startNodeCG();
await assert.doesNotReject(async () => {
await manager.waitForNodeCGReady(Date.now());
});
});
test("stopNodeCG envía SIGTERM y luego SIGKILL si el proceso no sale", async () => {
const child = new MockChildProcess(9999);
const timers: Array<() => void> = [];
const killSignals: Array<{ pid: number; signal: NodeJS.Signals }> = [];
const manager = createNodecgProcessManager({
isDev: true,
nodecgPath: "/fake/nodecg",
baseUrl: "http://127.0.0.1:9090",
runtimeConfig: getBaseConfig(),
log: () => undefined,
deps: {
pathExists: () => true,
spawnProcess: () => child as unknown as import("node:child_process").ChildProcess,
fetchUrl: async () => ({ ok: false, status: 404 } as Response),
killProcess: (pid, signal) => {
killSignals.push({ pid, signal });
},
setTimer: ((handler: (...args: unknown[]) => void, _timeoutMs: number) => {
timers.push(() => handler());
return 0 as never;
}),
stdoutWrite: () => undefined,
stderrWrite: () => undefined,
},
});
manager.startNodeCG();
const stopPromise = manager.stopNodeCG();
assert.deepEqual(killSignals, [{ pid: -9999, signal: "SIGTERM" }]);
timers.forEach((runTimer) => {
runTimer();
});
assert.deepEqual(killSignals, [
{ pid: -9999, signal: "SIGTERM" },
{ pid: -9999, signal: "SIGKILL" },
]);
child.emit("exit", 0, null);
await stopPromise;
});