import assert from "node:assert/strict"; import { Buffer } from "node:buffer"; import { execFile, spawn } from "node:child_process"; import fs from "node:fs/promises"; import net from "node:net"; import os from "node:os"; import path from "node:path"; import { promisify } from "node:util"; import WebSocket from "ws"; import { createClientChannel } from "@getpaseo/relay/e2ee"; const execFileAsync = promisify(execFile); const repoRoot = path.resolve(new URL("../../..", import.meta.url).pathname); const desktopRoot = path.join(repoRoot, "packages", "desktop"); const relayRoot = path.join(repoRoot, "packages", "relay"); const desktopPackageJson = JSON.parse( await fs.readFile(path.join(desktopRoot, "package.json"), "utf8") ); const currentRuntimeVersion = desktopPackageJson.version; const currentRuntimePointer = JSON.parse( await fs.readFile( path.join(desktopRoot, "src-tauri", "resources", "managed-runtime", "current-runtime.json"), "utf8" ) ); const currentRuntimeId = currentRuntimePointer.runtimeId; function resolvePackagedBinary() { if (process.platform === "darwin") { return path.join( desktopRoot, "src-tauri", "target", "release", "bundle", "macos", "Paseo.app", "Contents", "MacOS", "Paseo" ); } if (process.platform === "linux") { return path.join( desktopRoot, "src-tauri", "target", "release", "bundle", "appimage", `Paseo_${desktopPackageJson.version}_amd64.AppImage` ); } throw new Error(`Managed desktop smoke is not implemented for ${process.platform} yet.`); } async function pathExists(target) { try { await fs.access(target); return true; } catch { return false; } } async function snapshotTree(root) { if (!(await pathExists(root))) { return []; } const entries = []; async function walk(current) { const stat = await fs.stat(current); const relative = path.relative(root, current) || "."; entries.push({ relative, kind: stat.isDirectory() ? "dir" : "file", size: stat.size, mtimeMs: stat.mtimeMs, }); if (!stat.isDirectory()) { return; } const children = await fs.readdir(current); children.sort(); for (const child of children) { await walk(path.join(current, child)); } } await walk(root); entries.sort((left, right) => left.relative.localeCompare(right.relative)); return entries; } async function sleep(ms) { await new Promise((resolve) => setTimeout(resolve, ms)); } function escapeForRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } async function runBinary(binaryPath, args, env) { const { stdout, stderr } = await execFileAsync(binaryPath, args, { env, cwd: repoRoot, maxBuffer: 10 * 1024 * 1024, }); const trimmed = stdout.trim(); return { stdout, stderr, json: trimmed ? JSON.parse(trimmed) : null, }; } async function runWorkspaceCli(args, env) { const { stdout, stderr } = await execFileAsync( process.execPath, [path.join(repoRoot, "packages", "cli", "dist", "index.js"), ...args], { cwd: repoRoot, env: { ...process.env, ...env, }, maxBuffer: 10 * 1024 * 1024, }, ); let json = null; if (stdout.trim()) { try { json = JSON.parse(stdout.trim()); } catch { json = null; } } return { stdout, stderr, json }; } async function runBundledRuntimeCli(runtimeRoot, managedHome, args, env) { const manifest = JSON.parse( await fs.readFile(path.join(runtimeRoot, "runtime-manifest.json"), "utf8") ); const { stdout, stderr } = await execFileAsync( path.join(runtimeRoot, manifest.nodeRelativePath), [path.join(runtimeRoot, manifest.cliEntrypointRelativePath), ...args], { cwd: repoRoot, env: { ...process.env, ...env, PASEO_HOME: managedHome, }, maxBuffer: 10 * 1024 * 1024, } ); return { stdout, stderr }; } async function readDaemonStatus(home, env) { const result = await runWorkspaceCli(["daemon", "status", "--home", home, "--json"], env); return result.json ?? {}; } async function pidListeningOnPort(port) { try { const { stdout } = await execFileAsync("lsof", ["-ti", `tcp:${port}`], { maxBuffer: 1024 * 1024, }); const pid = Number.parseInt(stdout.trim().split("\n")[0] ?? "", 10); return Number.isFinite(pid) ? pid : null; } catch { return null; } } async function getAvailablePort() { return await new Promise((resolve, reject) => { const server = net.createServer(); server.unref(); server.on("error", reject); server.listen(0, "127.0.0.1", () => { const address = server.address(); if (!address || typeof address === "string") { server.close(() => reject(new Error("Failed to allocate an ephemeral test port."))); return; } const { port } = address; server.close((error) => { if (error) { reject(error); return; } resolve(port); }); }); }); } function buildRelayWebSocketUrl({ endpoint, serverId, role }) { const url = new URL(`ws://${endpoint}/ws`); url.searchParams.set("serverId", serverId); url.searchParams.set("role", role); url.searchParams.set("v", "2"); return url.toString(); } function parseOfferUrlFromCommandOutput(stdout) { const match = stdout.match(/https?:\/\/\S+#offer=\S+/); if (!match) { throw new Error(`Failed to find pairing URL in output: ${stdout}`); } return match[0]; } function decodeOfferFromFragmentUrl(url) { const marker = "#offer="; const index = url.indexOf(marker); if (index === -1) { throw new Error(`Pairing URL is missing ${marker}: ${url}`); } const encoded = url.slice(index + marker.length); const raw = Buffer.from(encoded, "base64url").toString("utf8"); const offer = JSON.parse(raw); if (offer?.v !== 2 || typeof offer?.serverId !== "string" || typeof offer?.daemonPublicKeyB64 !== "string") { throw new Error(`Unexpected relay offer payload: ${raw}`); } return offer; } async function waitForRelayWebSocketReady(endpoint, timeoutMs) { await waitFor(async () => { const probeServerId = `probe-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`; const url = buildRelayWebSocketUrl({ endpoint, serverId: probeServerId, role: "server", }); const opened = await new Promise((resolve) => { const ws = new WebSocket(url); const timer = setTimeout(() => { ws.terminate(); resolve(false); }, 5_000); ws.once("open", () => { clearTimeout(timer); ws.close(1000, "probe"); resolve(true); }); ws.once("error", () => { clearTimeout(timer); resolve(false); }); }); assert.equal(opened, true); }, timeoutMs, "relay websocket endpoint to accept connections"); } async function connectViaRelay(endpoint, offer) { const stableClientId = `cid-managed-smoke-${Date.now().toString(36)}`; const ws = new WebSocket( buildRelayWebSocketUrl({ endpoint, serverId: offer.serverId, role: "client", }) ); return await new Promise((resolve, reject) => { const timeout = setTimeout(() => { ws.close(); reject(new Error("Timed out waiting for managed relay pong.")); }, 20_000); const transport = { send: (data) => ws.send(data), close: (code, reason) => ws.close(code, reason), onmessage: null, onclose: null, onerror: null, }; ws.on("message", (data) => { transport.onmessage?.(typeof data === "string" ? data : data.toString()); }); ws.on("close", (code, reason) => { transport.onclose?.(code, reason.toString()); }); ws.on("error", (error) => { transport.onerror?.(error); }); ws.on("open", async () => { try { let pingSent = false; let channelRef = null; const channel = await createClientChannel(transport, offer.daemonPublicKeyB64, { onmessage: (data) => { try { const payload = typeof data === "string" ? JSON.parse(data) : data; if (payload?.type === "welcome") { if (!pingSent && channelRef) { pingSent = true; void channelRef.send(JSON.stringify({ type: "ping" })); } return; } if (payload?.type === "pong") { clearTimeout(timeout); resolve(payload); ws.close(); } } catch (error) { clearTimeout(timeout); reject(error); } }, onerror: (error) => { clearTimeout(timeout); reject(error); }, }); channelRef = channel; await channel.send( JSON.stringify({ type: "hello", clientId: stableClientId, clientType: "cli", protocolVersion: 1, }) ); } catch (error) { clearTimeout(timeout); reject(error); } }); }); } function assertNoForbiddenPathsOrPorts(value, forbidden) { const text = typeof value === "string" ? value : JSON.stringify(value, null, 2); for (const candidate of forbidden) { assert.ok( !text.includes(candidate), `Unexpected forbidden managed smoke reference: ${candidate}` ); } } async function ensurePackagedArtifact(binaryPath) { try { await execFileAsync("npm", ["run", "build"], { cwd: desktopRoot, env: process.env, maxBuffer: 20 * 1024 * 1024, }); } catch (error) { const combined = `${error.stdout ?? ""}\n${error.stderr ?? ""}`; const signingBlocked = combined.includes("TAURI_SIGNING_PRIVATE_KEY") && (await pathExists(binaryPath)); if (!signingBlocked) { throw error; } console.warn( "[managed-smoke] continuing after Tauri updater signing failure because the packaged app artifact was already produced" ); } } async function waitFor(assertion, timeoutMs, label) { const startedAt = Date.now(); while (Date.now() - startedAt < timeoutMs) { try { return await assertion(); } catch { await sleep(250); } } throw new Error(`Timed out waiting for ${label}`); } function logStep(label) { console.log(`\n[managed-smoke] ${label}`); } const packagedBinary = resolvePackagedBinary(); await ensurePackagedArtifact(packagedBinary); if (!(await pathExists(packagedBinary))) { throw new Error(`Packaged desktop artifact not found: ${packagedBinary}`); } const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), "paseo-desktop-smoke-")); const testRoot = path.join(tmpRoot, "managed-test-root"); const fakeHome = path.join(tmpRoot, "fake-home"); const fakePaseoHome = path.join(fakeHome, ".paseo"); const cliScratchHome = path.join(tmpRoot, "cli-scratch-home"); const externalHome = path.join(tmpRoot, "external-daemon-home"); const externalPort = await getAvailablePort(); const externalEndpoint = `127.0.0.1:${externalPort}`; const relayPort = await getAvailablePort(); const relayEndpoint = `127.0.0.1:${relayPort}`; const managedRuntimeDir = path.join(testRoot, "runtime"); await fs.mkdir(testRoot, { recursive: true }); await fs.mkdir(fakePaseoHome, { recursive: true }); await fs.mkdir(cliScratchHome, { recursive: true }); await fs.mkdir(externalHome, { recursive: true }); await fs.writeFile(path.join(fakePaseoHome, "sentinel.txt"), "do not touch\n", "utf8"); const fakePaseoSnapshotBefore = await snapshotTree(fakePaseoHome); const managedEnv = { ...process.env, HOME: fakeHome, PASEO_HOME: cliScratchHome, PASEO_DESKTOP_TEST_ROOT: testRoot, PASEO_LOCAL_SPEECH_AUTO_DOWNLOAD: "0", PASEO_DICTATION_ENABLED: "0", PASEO_VOICE_MODE_ENABLED: "0", PASEO_RELAY_ENABLED: "true", PASEO_RELAY_ENDPOINT: relayEndpoint, PASEO_RELAY_PUBLIC_ENDPOINT: relayEndpoint, PASEO_APP_BASE_URL: "https://app.paseo.test", PASEO_PRIMARY_LAN_IP: "192.168.1.12", CI: "true", }; let externalPid = null; let startedTemporaryExternalDaemon = false; let relayProcess = null; const forbiddenManagedReferences = ["127.0.0.1:6767", fakePaseoHome, managedRuntimeDir]; try { logStep(`Starting isolated local relay on ${relayEndpoint}`); relayProcess = spawn(process.platform === "win32" ? "npx.cmd" : "npx", [ "wrangler", "dev", "--local", "--ip", "127.0.0.1", "--port", String(relayPort), "--live-reload=false", "--show-interactive-dev-session=false", ], { cwd: relayRoot, env: process.env, stdio: ["ignore", "pipe", "pipe"], }); relayProcess.stdout?.on("data", (chunk) => { process.stdout.write(`[managed-smoke relay] ${chunk}`); }); relayProcess.stderr?.on("data", (chunk) => { process.stderr.write(`[managed-smoke relay] ${chunk}`); }); await waitFor(async () => { await execFileAsync(process.execPath, ["-e", `require("node:net").connect(${relayPort}, "127.0.0.1").on("connect", function () { this.end(); process.exit(0); }).on("error", () => process.exit(1));`]); }, 30_000, "relay HTTP endpoint to start"); await waitForRelayWebSocketReady(relayEndpoint, 60_000); logStep(`Starting isolated external daemon on ${externalEndpoint}`); startedTemporaryExternalDaemon = true; await runWorkspaceCli( ["start", "--home", externalHome, "--listen", externalEndpoint, "--no-relay"], managedEnv ); const externalStatus = await waitFor( async () => { const status = await readDaemonStatus(externalHome, managedEnv); assert.equal(status.status, "running"); assert.ok(typeof status.pid === "number" && status.pid > 0, "expected external daemon pid"); await runWorkspaceCli(["ls", "--host", externalEndpoint, "--json"], managedEnv); return status; }, 15_000, "external daemon to start" ); externalPid = externalStatus.pid; assert.ok(externalPid, "external daemon pid should be present"); logStep("Bootstrapping managed runtime from packaged desktop binary"); const runtimeStatus = await runBinary(packagedBinary, ["--managed-headless", "runtime-status"], managedEnv); assert.equal(runtimeStatus.json.runtimeId, currentRuntimeId); assert.equal(runtimeStatus.json.runtimeVersion, currentRuntimeVersion); assert.match(runtimeStatus.json.managedHome, new RegExp(`^${escapeForRegExp(testRoot)}`)); assert.match( runtimeStatus.json.runtimeRoot, new RegExp(escapeForRegExp(currentRuntimeId)), "runtime status should point into the bundled runtime selected by current-runtime.json" ); assert.equal( runtimeStatus.json.runtimeRoot.startsWith(testRoot), false, "runtime status should not resolve into managed app data" ); assert.ok(runtimeStatus.json.transportType === "socket" || runtimeStatus.json.transportType === "pipe"); assert.notEqual(runtimeStatus.json.transportPath, "127.0.0.1:6767"); assertNoForbiddenPathsOrPorts(runtimeStatus.json, forbiddenManagedReferences); assert.equal(await pathExists(managedRuntimeDir), false, "managed app data should not gain a runtime tree"); const stateAfterRuntimeStatus = JSON.parse( await fs.readFile(path.join(testRoot, "managed-state.json"), "utf8") ); assert.equal(stateAfterRuntimeStatus.runtimeId, currentRuntimeId); assert.equal(stateAfterRuntimeStatus.runtimeRoot, runtimeStatus.json.runtimeRoot); const managedStart = await runBinary(packagedBinary, ["--managed-headless", "bootstrap"], managedEnv); assert.equal(managedStart.json.daemonRunning, true); assert.ok(managedStart.json.daemonPid, "managed daemon pid should exist"); assert.equal(managedStart.json.runtimeRoot, runtimeStatus.json.runtimeRoot); assert.ok( managedStart.json.transportType === "socket" || managedStart.json.transportType === "pipe", "managed daemon should default to private IPC transport" ); assert.notEqual(managedStart.json.transportPath, "127.0.0.1:6767"); assertNoForbiddenPathsOrPorts(managedStart.json, forbiddenManagedReferences); assert.equal(await pathExists(managedRuntimeDir), false, "starting the daemon should not install a runtime copy"); const managedPid = managedStart.json.daemonPid; const stateFile = path.join(testRoot, "managed-state.json"); assert.equal(await pathExists(stateFile), true, "managed state file should be written"); logStep("Verifying the managed daemon stays alive after the packaged command exits"); await sleep(1_500); const persistedManagedStatus = await runBinary( packagedBinary, ["--managed-headless", "daemon-status"], managedEnv ); assert.equal(persistedManagedStatus.json.daemonPid, managedPid); assert.equal(persistedManagedStatus.json.daemonRunning, true); assert.equal(persistedManagedStatus.json.relayEnabled, true); assertNoForbiddenPathsOrPorts(persistedManagedStatus.json, forbiddenManagedReferences); logStep("Installing CLI shim and verifying the bundled CLI target"); const cliInstall = await runBinary( packagedBinary, ["--managed-headless", "install-cli-shim"], managedEnv ); const cliShimPath = cliInstall.json.path; assert.ok(cliShimPath, "CLI shim path should be returned"); const cliShimInstalled = cliInstall.json.installed === true && (await pathExists(cliShimPath)); if (!cliShimInstalled) { assert.ok(cliInstall.json.manualInstructions, "manual CLI install instructions should be returned"); assert.match( cliInstall.json.manualInstructions.commands, new RegExp(escapeForRegExp(runtimeStatus.json.runtimeRoot)), "manual CLI install instructions should point at the bundled runtime" ); assertNoForbiddenPathsOrPorts(cliInstall.json.manualInstructions, forbiddenManagedReferences); } const cliVersion = cliShimInstalled ? await execFileAsync(cliShimPath, ["--version"], { env: managedEnv, cwd: repoRoot, maxBuffer: 1024 * 1024, }) : await runBundledRuntimeCli( runtimeStatus.json.runtimeRoot, managedStart.json.managedHome, ["--version"], managedEnv ); assert.match(cliVersion.stdout.trim(), /^0\./); const shimStatus = cliShimInstalled ? await execFileAsync(cliShimPath, ["daemon", "status", "--json"], { env: managedEnv, cwd: repoRoot, maxBuffer: 1024 * 1024, }) : await runBundledRuntimeCli( runtimeStatus.json.runtimeRoot, managedStart.json.managedHome, ["daemon", "status", "--json"], managedEnv ); const shimDaemonStatus = JSON.parse(shimStatus.stdout.trim()); assert.equal(shimDaemonStatus.pid, managedPid); assertNoForbiddenPathsOrPorts(shimDaemonStatus, forbiddenManagedReferences); logStep("Verifying relay connectivity still works after the desktop command has exited"); const relayPairing = cliShimInstalled ? await execFileAsync( cliShimPath, ["daemon", "pair", "--home", managedStart.json.managedHome], { env: managedEnv, cwd: repoRoot, maxBuffer: 4 * 1024 * 1024, } ) : await runBundledRuntimeCli( runtimeStatus.json.runtimeRoot, managedStart.json.managedHome, ["daemon", "pair", "--home", managedStart.json.managedHome], managedEnv ); const relayOfferUrl = parseOfferUrlFromCommandOutput(relayPairing.stdout); const relayOffer = decodeOfferFromFragmentUrl(relayOfferUrl); assert.equal(relayOffer.relay?.endpoint, relayEndpoint); const relayPong = await connectViaRelay(relayEndpoint, relayOffer); assert.deepEqual(relayPong, { type: "pong" }); logStep("Reopening packaged desktop command path without spawning duplicate daemons"); const managedRestartless = await runBinary( packagedBinary, ["--managed-headless", "bootstrap"], managedEnv ); assert.equal(managedRestartless.json.daemonPid, managedPid); logStep("Verifying managed and external daemons coexist"); const externalStatusAfter = await readDaemonStatus(externalHome, managedEnv); const externalPidAfter = externalStatusAfter.pid ?? null; assert.equal(externalStatusAfter.status, "running"); assert.equal(externalPidAfter, externalPid); await runWorkspaceCli(["ls", "--host", externalEndpoint, "--json"], managedEnv); const managedStatus = await runBinary( packagedBinary, ["--managed-headless", "daemon-status"], managedEnv ); assert.ok(managedStatus.json.serverId, "managed daemon should expose a server id"); assertNoForbiddenPathsOrPorts(managedStatus.json, forbiddenManagedReferences); logStep("Enabling managed TCP exposure on an explicit non-default port"); const tcpEnabled = await runBinary( packagedBinary, [ "--managed-headless", "update-tcp", "--enabled", "true", "--host", "127.0.0.1", "--port", "7771", ], managedEnv ); assert.equal(tcpEnabled.json.tcpEnabled, true); assert.equal(tcpEnabled.json.transportType, "tcp"); assert.equal(tcpEnabled.json.transportPath, "127.0.0.1:7771"); assert.notEqual(tcpEnabled.json.transportPath, "127.0.0.1:6767"); assertNoForbiddenPathsOrPorts(tcpEnabled.json, [fakePaseoHome]); logStep("Disabling managed TCP exposure and returning to private transport"); const tcpDisabled = await runBinary( packagedBinary, [ "--managed-headless", "update-tcp", "--enabled", "false", "--host", "127.0.0.1", "--port", "7771", ], managedEnv ); assert.equal(tcpDisabled.json.tcpEnabled, false); assert.notEqual(tcpDisabled.json.transportType, "tcp"); assertNoForbiddenPathsOrPorts(tcpDisabled.json, forbiddenManagedReferences); logStep("Capturing diagnostics and verifying the fake ~/.paseo stayed untouched"); const fakePaseoSnapshotAfter = await snapshotTree(fakePaseoHome); assert.deepEqual(fakePaseoSnapshotAfter, fakePaseoSnapshotBefore); await fs.writeFile( path.join(testRoot, "managed-daemon-smoke-diagnostics.json"), JSON.stringify( { runtimeStatus: runtimeStatus.json, managedStart: managedStart.json, stateAfterRuntimeStatus, persistedManagedStatus: persistedManagedStatus.json, managedRestartless: managedRestartless.json, cliInstall: cliInstall.json, shimDaemonStatus, relayEndpoint, relayOfferUrl, relayPong, externalEndpoint, externalPid, externalPidAfter, managedStatus: managedStatus.json, tcpEnabled: tcpEnabled.json, tcpDisabled: tcpDisabled.json, }, null, 2 ) + "\n", "utf8" ); console.log(`\n[managed-smoke] PASS (${testRoot})`); } finally { try { await runBinary(packagedBinary, ["--managed-headless", "stop-daemon"], managedEnv); } catch {} try { await runBinary(packagedBinary, ["--managed-headless", "uninstall-cli-shim"], managedEnv); } catch {} try { if (startedTemporaryExternalDaemon) { await runWorkspaceCli(["daemon", "stop", "--home", externalHome, "--json"]); } } catch {} try { relayProcess?.kill("SIGTERM"); } catch {} }