Files

178 lines
7.0 KiB
TypeScript

import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { access, chmod, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { createServer } from "node:net";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { allocateFreePort } from "@openwork/cdp";
import { electronProfilePaths, electronSurfaceEnv, freePort, resolveChromeBinary, stopOwnedElectronSurface } from "../src/local.ts";
const ENV_KEYS = [
"APPDATA",
"HOME",
"LOCALAPPDATA",
"OPENCODE_CONFIG_DIR",
"OPENWORK_DATA_DIR",
"OPENWORK_DESKTOP_BOOTSTRAP_PATH",
"OPENWORK_DESKTOP_DISABLE_WORKSPACE_RECOVERY",
"OPENWORK_DEV_MODE",
"OPENWORK_ELECTRON_APP_IDENTIFIER",
"OPENWORK_ELECTRON_APP_NAME",
"OPENWORK_ELECTRON_DISABLE_PROTOCOL_REGISTRATION",
"OPENWORK_ELECTRON_REMOTE_DEBUG_PORT",
"OPENWORK_ELECTRON_SKIP_SHARED_PREPARE",
"OPENWORK_ELECTRON_USE_MOCK_KEYCHAIN",
"OPENWORK_ELECTRON_USERDATA",
"OPENWORK_ENV_STORE",
"PORT",
"VITE_DISABLE_OPENWORK_MODELS",
"XDG_CACHE_HOME",
"XDG_CONFIG_HOME",
"XDG_DATA_HOME",
"XDG_STATE_HOME",
].sort();
test("electronProfilePaths returns all expected paths under the profile root", () => {
const root = join(tmpdir(), "openwork-local-host-profile");
const paths = electronProfilePaths(root);
assert.deepEqual(Object.keys(paths).sort(), [
"appDataDir",
"bootstrapPath",
"cacheHome",
"configHome",
"dataDir",
"dataHome",
"envStorePath",
"homeDir",
"localAppDataDir",
"opencodeConfigDir",
"root",
"stateHome",
"userDataDir",
].sort());
for (const value of Object.values(paths)) {
assert(value === root || value.startsWith(`${root}/`), `${value} should be under ${root}`);
}
assert.equal(paths.userDataDir, join(root, "electron-userdata"));
assert.equal(paths.bootstrapPath, join(root, "bootstrap.json"));
});
test("electronSurfaceEnv matches the isolated Electron demo contract", () => {
const root = join(tmpdir(), "openwork-local-host-env");
const paths = electronProfilePaths(root);
const env = electronSurfaceEnv(paths, {
appName: "OpenWork Eval probe",
appIdentifier: "com.differentai.openwork.eval.probe",
port: 5123,
cdpPort: 9123,
});
assert.deepEqual(Object.keys(env).filter((key) => key !== "PNPM_HOME").sort(), ENV_KEYS);
// The one deliberate hole in the isolation: pnpm's version redirection must
// stay warm, or every spawn re-downloads the pinned pnpm from the network.
if (process.platform === "darwin" || process.platform === "linux") {
assert(env.PNPM_HOME, "PNPM_HOME should point at the host's pnpm home");
assert(!env.PNPM_HOME.startsWith(root), "PNPM_HOME must not be inside the isolated profile");
}
assert.equal(env.APPDATA, paths.appDataDir);
assert.equal(env.HOME, paths.homeDir);
assert.equal(env.LOCALAPPDATA, paths.localAppDataDir);
assert.equal(env.OPENWORK_DATA_DIR, paths.dataDir);
assert.equal(env.OPENWORK_DESKTOP_BOOTSTRAP_PATH, paths.bootstrapPath);
assert.equal(env.OPENWORK_ENV_STORE, paths.envStorePath);
assert.equal(env.OPENCODE_CONFIG_DIR, paths.opencodeConfigDir);
assert.equal(env.OPENWORK_ELECTRON_USERDATA, paths.userDataDir);
assert.equal(env.PORT, "5123");
assert.equal(env.OPENWORK_ELECTRON_REMOTE_DEBUG_PORT, "9123");
assert.equal(env.OPENWORK_ELECTRON_APP_NAME, "OpenWork Eval probe");
assert.equal(env.OPENWORK_ELECTRON_APP_IDENTIFIER, "com.differentai.openwork.eval.probe");
assert.equal(env.OPENWORK_ELECTRON_SKIP_SHARED_PREPARE, "1");
assert.equal(env.OPENWORK_ELECTRON_USE_MOCK_KEYCHAIN, "1");
assert.equal(env.XDG_CACHE_HOME, paths.cacheHome);
assert.equal(env.XDG_CONFIG_HOME, paths.configHome);
assert.equal(env.XDG_DATA_HOME, paths.dataHome);
assert.equal(env.XDG_STATE_HOME, paths.stateHome);
});
test("stopOwnedElectronSurface verifies profile ownership before removing it", async () => {
const profileDir = await mkdtemp(join(tmpdir(), "openwork-owned-electron-"));
const userDataDir = join(profileDir, "electron-userdata");
await mkdir(userDataDir, { recursive: true });
const child = spawn(process.execPath, ["-e", "setInterval(() => {}, 1000)"], {
detached: true,
env: { ...process.env, OPENWORK_ELECTRON_USERDATA: userDataDir },
stdio: "ignore",
});
child.unref();
if (!child.pid) throw new Error("Owned Electron fixture did not start.");
await new Promise((resolve) => setTimeout(resolve, 100));
await stopOwnedElectronSurface(child.pid, profileDir);
await assert.rejects(access(profileDir));
});
test("resolveChromeBinary honors CHROME_BIN before platform defaults", () => {
assert.equal(resolveChromeBinary({ CHROME_BIN: "/custom/chrome" }, "linux"), "/custom/chrome");
assert.equal(resolveChromeBinary({ CHROME_BIN: "C:\\Chrome.exe" }, "win32"), "C:\\Chrome.exe");
});
test("resolveChromeBinary returns the macOS default path", () => {
assert.equal(resolveChromeBinary({}, "darwin"), "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome");
});
test("resolveChromeBinary finds Linux Chrome on PATH and reports a helpful error otherwise", async () => {
const binDir = await mkdtemp(join(tmpdir(), "openwork-chrome-bin-"));
const chromePath = join(binDir, "google-chrome");
try {
await writeFile(chromePath, "#!/bin/sh\nexit 0\n", "utf8");
await chmod(chromePath, 0o755);
assert.equal(resolveChromeBinary({ PATH: binDir }, "linux"), chromePath);
assert.throws(
() => resolveChromeBinary({ PATH: "" }, "linux"),
/Could not resolve Chrome binary on linux.*CHROME_BIN/,
);
assert.throws(
() => resolveChromeBinary({}, "freebsd"),
/unsupported platform freebsd/,
);
} finally {
await rm(binDir, { recursive: true, force: true });
}
});
test("freePort kills a real child listener and releases its port", {
skip: process.platform !== "darwin" && process.platform !== "linux",
}, async () => {
const port = await allocateFreePort();
const child = spawn(process.execPath, [
"-e",
"require('node:net').createServer().listen(Number(process.argv[1]), '127.0.0.1', () => process.stdout.write('ready\\n'))",
String(port),
], { detached: true, stdio: ["ignore", "pipe", "inherit"] });
const logs: string[] = [];
try {
await new Promise<void>((resolve, reject) => {
const timer = setTimeout(() => reject(new Error(`Listener child did not bind port ${port}.`)), 5_000);
child.once("error", reject);
child.stdout?.once("data", () => {
clearTimeout(timer);
resolve();
});
});
await freePort(port, { log: (message) => logs.push(message) });
assert(logs.some((message) => message.includes(`Port ${port}`) && message.includes(String(child.pid))));
await new Promise<void>((resolve, reject) => {
const probe = createServer();
probe.once("error", reject);
probe.listen(port, "127.0.0.1", () => probe.close((error) => error ? reject(error) : resolve()));
});
} finally {
if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL");
}
});