MeshChatX/tests/electron/backendProcess.test.js

203 lines
8.3 KiB
JavaScript
Raw Permalink Normal View History

import { beforeEach, describe, expect, it, vi } from "vitest";
import { createBackendProcessManager } from "../../electron/backendProcess.js";
function createFakeChildProcess() {
const { EventEmitter } = require("node:events");
const proc = new EventEmitter();
proc.stdout = new EventEmitter();
proc.stdout.setEncoding = () => {};
proc.stderr = new EventEmitter();
proc.stderr.setEncoding = () => {};
proc.pid = 4242;
proc.exitCode = null;
proc.signalCode = null;
return proc;
}
describe("electron/backendProcess", () => {
let fakeProc;
let spawnMock;
beforeEach(() => {
fakeProc = createFakeChildProcess();
spawnMock = vi.fn(() => fakeProc);
});
it("keeps the shell open and notifies renderer when backend exits during app use", async () => {
const notifyRenderer = vi.fn();
const showCrashPage = vi.fn();
const manager = createBackendProcessManager({
log: vi.fn(),
getDefaultStorageDir: () => "/tmp/storage",
getDefaultReticulumConfigDir: () => "/tmp/reticulum",
getMainWindowPageKind: () => "app",
isQuiting: () => false,
notifyRenderer,
showCrashPage,
spawn: spawnMock,
});
manager.setUserProvidedArguments([]);
await manager.spawnBackend("/tmp/ReticulumMeshChatX", { backend: { ok: true, issues: [] } });
fakeProc.emit("exit", 255);
await new Promise((resolve) => setImmediate(resolve));
expect(notifyRenderer).toHaveBeenCalledWith("backend-process-exited", expect.objectContaining({ code: 255 }));
expect(showCrashPage).not.toHaveBeenCalled();
expect(manager.getRuntimeState().running).toBe(false);
expect(manager.getRuntimeState().lastExitCode).toBe(255);
});
it("notifies loading screen when backend exits during startup", async () => {
const notifyRenderer = vi.fn();
const showCrashPage = vi.fn();
const manager = createBackendProcessManager({
log: vi.fn(),
getDefaultStorageDir: () => "/tmp/storage",
getDefaultReticulumConfigDir: () => "/tmp/reticulum",
getMainWindowPageKind: () => "loading",
isQuiting: () => false,
notifyRenderer,
showCrashPage,
spawn: spawnMock,
});
manager.setUserProvidedArguments([]);
await manager.spawnBackend("/tmp/ReticulumMeshChatX", { backend: { ok: true, issues: [] } });
fakeProc.emit("exit", 9);
await new Promise((resolve) => setImmediate(resolve));
expect(notifyRenderer).toHaveBeenCalledWith(
"backend-startup-failed",
expect.objectContaining({ code: 9, paths: expect.any(Object) })
);
expect(showCrashPage).not.toHaveBeenCalled();
expect(manager.getLastCrash()).toEqual(expect.objectContaining({ code: 9 }));
});
it("opens the crash page when backend exits outside the main shell", async () => {
const notifyRenderer = vi.fn();
const showCrashPage = vi.fn();
const manager = createBackendProcessManager({
log: vi.fn(),
getDefaultStorageDir: () => "/tmp/storage",
getDefaultReticulumConfigDir: () => "/tmp/reticulum",
getMainWindowPageKind: () => "other",
isQuiting: () => false,
notifyRenderer,
showCrashPage,
spawn: spawnMock,
});
manager.setUserProvidedArguments([]);
await manager.spawnBackend("/tmp/ReticulumMeshChatX", { backend: { ok: true, issues: [] } });
fakeProc.emit("exit", 1);
await new Promise((resolve) => setImmediate(resolve));
expect(notifyRenderer).toHaveBeenCalled();
expect(showCrashPage).toHaveBeenCalledWith(expect.objectContaining({ code: 1 }));
});
it("does not use AppContainer launcher on win32 by default", async () => {
const previousPlatform = process.platform;
Object.defineProperty(process, "platform", { value: "win32" });
delete process.env.MESHCHAT_APPCONTAINER;
try {
const manager = createBackendProcessManager({
log: vi.fn(),
getDefaultStorageDir: () => "C:\\Users\\test\\.reticulum-meshchatx",
getDefaultReticulumConfigDir: () => "C:\\Users\\test\\.reticulum",
getMainWindowPageKind: () => "loading",
isQuiting: () => false,
notifyRenderer: vi.fn(),
showCrashPage: vi.fn(),
spawn: spawnMock,
});
manager.setUserProvidedArguments([]);
await manager.spawnBackend("C:\\App\\ReticulumMeshChatX.exe", {
backend: { ok: true, issues: [] },
});
const args = spawnMock.mock.calls[0][1];
expect(args).not.toContain("--meshchatx-run-module");
expect(args[0]).toBe("--headless");
} finally {
Object.defineProperty(process, "platform", { value: previousPlatform });
}
});
it("wraps win32 spawn through the AppContainer launcher when MESHCHAT_APPCONTAINER=1", async () => {
const previousPlatform = process.platform;
const previousEnv = process.env.MESHCHAT_APPCONTAINER;
Object.defineProperty(process, "platform", { value: "win32" });
process.env.MESHCHAT_APPCONTAINER = "1";
try {
const manager = createBackendProcessManager({
log: vi.fn(),
getDefaultStorageDir: () => "C:\\Users\\test\\.reticulum-meshchatx",
getDefaultReticulumConfigDir: () => "C:\\Users\\test\\.reticulum",
getMainWindowPageKind: () => "loading",
isQuiting: () => false,
notifyRenderer: vi.fn(),
showCrashPage: vi.fn(),
spawn: spawnMock,
});
manager.setUserProvidedArguments([]);
await manager.spawnBackend("C:\\App\\ReticulumMeshChatX.exe", {
backend: { ok: true, issues: [] },
});
expect(spawnMock).toHaveBeenCalledWith(
"C:\\App\\ReticulumMeshChatX.exe",
expect.arrayContaining([
"--meshchatx-run-module",
"meshchatx.src.backend.appcontainer_launcher",
"--headless",
"--port",
"9337",
]),
expect.objectContaining({ windowsHide: true })
);
} finally {
Object.defineProperty(process, "platform", { value: previousPlatform });
if (previousEnv === undefined) {
delete process.env.MESHCHAT_APPCONTAINER;
} else {
process.env.MESHCHAT_APPCONTAINER = previousEnv;
}
}
});
it("skips AppContainer launcher when MESHCHAT_APPCONTAINER=0 on win32", async () => {
const previousPlatform = process.platform;
const previousEnv = process.env.MESHCHAT_APPCONTAINER;
Object.defineProperty(process, "platform", { value: "win32" });
process.env.MESHCHAT_APPCONTAINER = "0";
try {
const manager = createBackendProcessManager({
log: vi.fn(),
getDefaultStorageDir: () => "C:\\Users\\test\\.reticulum-meshchatx",
getDefaultReticulumConfigDir: () => "C:\\Users\\test\\.reticulum",
getMainWindowPageKind: () => "loading",
isQuiting: () => false,
notifyRenderer: vi.fn(),
showCrashPage: vi.fn(),
spawn: spawnMock,
});
manager.setUserProvidedArguments([]);
await manager.spawnBackend("C:\\App\\ReticulumMeshChatX.exe", {
backend: { ok: true, issues: [] },
});
const args = spawnMock.mock.calls[0][1];
expect(args).not.toContain("--meshchatx-run-module");
expect(args[0]).toBe("--headless");
} finally {
Object.defineProperty(process, "platform", { value: previousPlatform });
if (previousEnv === undefined) {
delete process.env.MESHCHAT_APPCONTAINER;
} else {
process.env.MESHCHAT_APPCONTAINER = previousEnv;
}
}
});
});