import { createHash } from "node:crypto"; import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { describe, expect, test } from "bun:test"; import type { UniDeskConfig } from "./config"; import { createPosixApplyPatchFileSystem, createSshStderrForwarder, createSshStdoutForwarder, formatSshStdoutTruncationHint, formatSshTruncationCompletionSummary, parseSshInvocation, remoteCommandForRoute, sshTruncationCompletionSummary, sshCaptureBackendPlan, sshStderrStreamMaxBytes, sshStdoutStreamMaxBytes, sshStdoutTruncationHint, sshWindowsPlaneHint, sshWindowsPowerShellPipelineHint, windowsFsReadOnlyScript, windowsPowerShellScriptPrelude, } from "./ssh"; import { readCliOutputPolicy } from "./output"; function sha256Hex(text: string): string { return createHash("sha256").update(text, "utf8").digest("hex"); } function decodedWindowsPowerShellCommand(remoteCommand: string | null): string { const encoded = remoteCommand?.match(/'([A-Za-z0-9+/=]+)'$/u)?.[1]; if (encoded === undefined) throw new Error("Windows PowerShell command did not end in an encoded payload"); return Buffer.from(encoded, "base64").toString("utf16le"); } function minimalConfig(publicHost = "203.0.113.10"): UniDeskConfig { return { network: { publicHost }, } as UniDeskConfig; } describe("ssh windows PowerShell safety prelude", () => { test("shadows ConvertTo-Json and strips filesystem ETS metadata", () => { const prelude = windowsPowerShellScriptPrelude("C:\\test"); expect(prelude).toContain("function ConvertTo-UniDeskPlainJsonValue"); expect(prelude).toContain("function ConvertTo-Json"); expect(prelude).toContain("'PSPath','PSParentPath','PSChildName','PSDrive','PSProvider','ReadCount'"); expect(prelude).toContain("Microsoft.PowerShell.Utility\\ConvertTo-Json"); expect(prelude).toContain("$PSDefaultParameterValues['Get-Content:Encoding'] = 'utf8'"); expect(prelude).toContain("Set-Location -LiteralPath 'C:\\test'"); }); }); describe("ssh windows fs read-only operations", () => { test("routes common read-only commands through the Windows fs backend", () => { const invocation = parseSshInvocation("D601:win/c/test", ["cat", "hello.md"]); const rgInvocation = parseSshInvocation("D601:win/c/test", ["rg", "-i", "needle", "."]); expect(invocation.route.plane).toBe("win"); expect(invocation.route.workspace).toBe("C:\\test"); expect(invocation.parsed.invocationKind).toBe("helper"); expect(invocation.parsed.requiresStdin).toBe(false); expect(invocation.parsed.remoteCommand).toContain("powershell.exe"); expect(invocation.parsed.remoteCommand).toContain("-EncodedCommand"); expect(rgInvocation.parsed.invocationKind).toBe("helper"); expect(rgInvocation.parsed.requiresStdin).toBe(false); }); test("routes git status, diff, and commit through Windows cmd convenience", () => { const statusInvocation = parseSshInvocation("D601:win/F/Work/ConStart", ["git", "status", "--short", "--branch"]); const diffInvocation = parseSshInvocation("D601:win/F/Work/ConStart", ["git", "diff", "--check"]); const commitInvocation = parseSshInvocation("D601:win/F/Work/ConStart", ["git", "commit", "-m", "fix: update docs"]); expect(statusInvocation.route.plane).toBe("win"); expect(statusInvocation.route.workspace).toBe("F:\\Work\\ConStart"); expect(statusInvocation.parsed.invocationKind).toBe("argv"); expect(statusInvocation.parsed.requiresStdin).toBe(false); expect(statusInvocation.parsed.remoteCommand).toContain("powershell.exe"); expect(diffInvocation.parsed.invocationKind).toBe("argv"); expect(diffInvocation.parsed.requiresStdin).toBe(false); expect(commitInvocation.parsed.invocationKind).toBe("argv"); expect(commitInvocation.parsed.requiresStdin).toBe(false); }); test("builds controlled exact-commit and object status helpers", () => { const status = parseSshInvocation("D601:win/F/Work/ConStart", ["git", "exact-commit", "status"]); const plan = parseSshInvocation("D601:win/F/Work/ConStart", ["git", "exact-commit", "plan", "--path", "src/main.c"]); const run = parseSshInvocation("D601:win/F/Work/ConStart", ["git", "exact-commit", "run", "--path", "src/main.c", "--message", "fix: exact", "--confirm"]); const decoded = decodedWindowsPowerShellCommand(run.parsed.remoteCommand); expect(status.parsed.invocationKind).toBe("helper"); expect(plan.parsed.invocationKind).toBe("helper"); expect(decoded).toContain("GIT_INDEX_FILE"); expect(decoded).toContain("update-ref"); expect(decoded).toContain("unselected worktree/index fingerprint"); expect(decoded).toContain("old-value CAS"); expect(decoded).toContain("$ErrorActionPreference='Continue'"); expect(decoded).toContain("2>&1"); expect(decoded).toContain("Management.Automation.ErrorRecord"); expect(decoded).toContain("warnings=@($gitWarnings)"); expect(decoded.trimEnd()).toEndWith("exit 0"); expect(() => parseSshInvocation("D601:win/F/Work/ConStart", ["git", "exact-commit", "run", "--path", "src/main.c", "--message", "fix: exact"])).toThrow("requires --confirm"); expect(() => parseSshInvocation("D601:win/F/Work/ConStart", ["git", "exact-commit", "plan", "--path", "../secret"])).toThrow("must stay inside"); }); test("adds a bounded hint for the PowerShell statement-to-pipeline parser boundary", () => { const invocation = parseSshInvocation("D601:win/c/test", ["ps", "foreach ($x in $xs) { $x } | Format-List"]); const hint = sshWindowsPowerShellPipelineHint(invocation, 1, "An empty pipe element is not allowed"); expect(hint).toContain("windows-powershell-statement-pipeline-boundary"); expect(hint).toContain("@(foreach (...) { ... }) | Format-List"); expect(hint).not.toContain("$x in $xs"); expect(sshWindowsPowerShellPipelineHint(invocation, 0, "An empty pipe element is not allowed")).toBe(""); }); test("builds bounded UTF-8 scripts for cat, ls, and rg", () => { const catScript = windowsFsReadOnlyScript("F:\\Work\\demo", "cat", ["--max-bytes", "4096", "中文.md"]); const lsScript = windowsFsReadOnlyScript("F:\\Work\\demo", "ls", ["-la", "--limit=5"]); const rgScript = windowsFsReadOnlyScript("F:\\Work\\demo", "rg", ["-i", "--max-count=5", "needle", "."]); expect(catScript).toContain("$operation = 'cat';"); expect(catScript).toContain("F:\\Work\\demo"); expect(catScript).toContain("binary file refused by windows fs read operation"); expect(catScript).toContain("file is not valid UTF-8"); expect(catScript).toContain("file too large for windows fs read operation"); expect(lsScript).toContain("$operation = 'ls';"); expect(lsScript).toContain("UNIDESK_WINDOWS_FS_TRUNCATED"); expect(lsScript).toContain("TYPE BYTES UPDATED NAME"); expect(rgScript).toContain("$operation = 'rg';"); expect(rgScript).toContain("unsupported rg option on Windows route"); expect(rgScript).toContain("UNIDESK_WINDOWS_RG_SUMMARY"); expect(rgScript).toContain("UNIDESK_WINDOWS_RG_HINT code=timeout exitCode=124 action=narrow-scope-or-add-glob"); expect(rgScript).toContain("engine=native-rg"); expect(rgScript).toContain("config/unidesk-cli.yaml#trans.windowsFs.rg"); expect(rgScript).toContain("--files"); expect(rgScript).toContain("--glob"); expect(rgScript).toContain("--with-filename"); expect(rgScript).toContain("encoding="); }); test("supports repeated rg patterns and streams large-file tail", () => { const rgScript = windowsFsReadOnlyScript("F:\\Work\\demo", "rg", ["-n", "-e", "alpha", "--regexp", "beta", "src/test.c"]); const tailScript = windowsFsReadOnlyScript("F:\\Work\\demo", "tail", ["-n", "40", ".state/request_history.jsonl"]); expect(rgScript).toContain("$patterns.Add($toolArgs[$i])"); expect(rgScript).toContain("$nativeArgs.Add('--regexp')"); expect(rgScript).toContain("fileCount="); expect(tailScript).toContain("[IO.StreamReader]::new"); expect(tailScript).toContain("[Collections.Generic.Queue[string]]::new()"); expect(tailScript).not.toContain("Read-StrictText (Resolve-UnideskPath $paths[0]) 1048576"); }); test("supports wc flags, exact skill lookup, and Windows cmd argv boundaries", () => { const wcScript = windowsFsReadOnlyScript("F:\\Work\\demo", "wc", ["-l", "docs/read me.md"]); const skills = parseSshInvocation("D601:win", ["skills", "--scope", "agents", "--name", "mdtodo-edit"]); const cmd = parseSshInvocation("D601:win/F/Work/demo", ["cmd", "python", "tool.py", "71-FILTER 高频输出精度与频率跳变改进"]); expect(wcScript).toContain("$arg -eq '-l'"); expect(wcScript).toContain("unsupported wc option on Windows route"); expect(decodedWindowsPowerShellCommand(skills.parsed.remoteCommand)).toContain("$exactName = 'mdtodo-edit'"); expect(decodedWindowsPowerShellCommand(cmd.parsed.remoteCommand)).toContain('python tool.py "71-FILTER 高频输出精度与频率跳变改进"'); }); test("supports GNU head/tail counts and preserves PowerShell native argv", () => { const headScript = windowsFsReadOnlyScript("F:\\Work\\demo", "head", ["-35", "docs/read me.md"]); const tailScript = windowsFsReadOnlyScript("F:\\Work\\demo", "tail", ["-20", "docs/read me.md"]); const pythonCode = 'import pathlib; print({"utf8": pathlib.Path(r"docs/报告.md").read_text(encoding="utf-8") != ""})'; const python = parseSshInvocation("D601:win/F/Work/demo", ["ps", "python", "-c", pythonCode]); const launcher = decodedWindowsPowerShellCommand(python.parsed.remoteCommand); const encodedPayload = launcher.match(/\$payloadJsonB64 = ''([A-Za-z0-9+/=]+)'';/u)?.[1]; expect(headScript).toContain("$arg -match '^-(\\d+)$'"); expect(tailScript).toContain("$arg -match '^-(\\d+)$'"); expect(python.parsed.invocationKind).toBe("argv"); expect(encodedPayload).toBeDefined(); expect(JSON.parse(Buffer.from(encodedPayload ?? "", "base64").toString("utf8"))).toEqual({ command: "python", args: ["-c", pythonCode], nativeArguments: `"-c" "${pythonCode.replaceAll('"', '\\"')}"`, }); }); test("rejects unsupported Windows read operations instead of treating them as POSIX", () => { expect(() => parseSshInvocation("D601:win/c/test", ["sed", "-n", "1p", "hello.md"])).toThrow("unsupported ssh win operation: sed"); }); test("reports route-aware hints for repeated win operation and interactive git commit", () => { expect(() => parseSshInvocation("D601:win/F/Work/ConStart", ["win", "ps"])).toThrow("route D601:win/F/Work/ConStart already selects the Windows plane"); expect(() => parseSshInvocation("D601:win/F/Work/ConStart", ["git", "commit"])).toThrow("ssh win git commit would open an editor"); }); test("diagnoses locally expanded PowerShell variables before remote execution", () => { try { parseSshInvocation("G14-WSL:win/d/Work/demo", ["ps", "= Start-Process python -PassThru; .Id"]); throw new Error("expected local PowerShell expansion diagnosis"); } catch (error) { expect(error).toBeInstanceOf(Error); const payload = error as Error & { code?: string; replacementExamples?: { oneLine?: string; pipeline?: string; stdin?: string }; migrationHint?: string; }; expect(payload.code).toBe("ssh-windows-powershell-local-expansion"); expect(payload.replacementExamples?.oneLine).toContain("ps '$p = Start-Process python -PassThru; $p.Id'"); expect(payload.replacementExamples?.pipeline).toContain("Where-Object { $_.CPU -gt 0 }"); expect(payload.replacementExamples?.stdin).toContain("ps <<'PS'"); expect(payload.migrationHint).toContain("local single quotes"); } expect(() => parseSshInvocation("G14-WSL:win/d/Work/demo", ["ps", "$p = Start-Process python -PassThru; $p.Id"])).not.toThrow(); }); }); describe("ssh direct argv boundaries and plane diagnostics", () => { test("preserves a shell-formed argv token containing spaces and Chinese", () => { const invocation = parseSshInvocation("G14-WSL:/mnt/d/Work/CONSTAR_workspace", [ "python3", "/tmp/mdtodo-edit-cli.py", "add", "docs/MDTODO/example.md", "71-FILTER 高频输出精度与频率跳变改进", ]); expect(invocation.parsed.remoteCommand).toContain("'python3' '/tmp/mdtodo-edit-cli.py' 'add' 'docs/MDTODO/example.md' '71-FILTER 高频输出精度与频率跳变改进'"); }); test("suggests the matching Windows plane after WSL command-not-found", () => { const invocation = parseSshInvocation("G14-WSL:/mnt/d/Work/CONSTAR_workspace", ["python", "--version"]); const hint = sshWindowsPlaneHint(invocation, 127, "sh: python: command not found\n"); expect(hint).toContain("wsl-command-not-found-check-windows-plane"); expect(hint).toContain("G14-WSL:win/d/Work/CONSTAR_workspace"); expect(sshWindowsPlaneHint(invocation, 1, "no match")).toBe(""); }); }); describe("ssh k3s file transfer command builder", () => { test("routes download stream commands through the shared k3s target builder", () => { const invocation = parseSshInvocation( "NC01:k3s:hwlab:deployment:hwlab-cloud-api", ["download", "/tmp/trace.json", "/tmp/trace.json"], ); const remoteCommand = remoteCommandForRoute( invocation.route, ["sh", "-c", "cat -- \"$1\"", "unidesk-download", "/tmp/trace.json"], ); expect(remoteCommand).toContain("'KUBECONFIG=/etc/rancher/k3s/k3s.yaml'"); expect(remoteCommand).toContain("'kubectl' 'exec' '-n' 'hwlab'"); expect(remoteCommand).toContain("'deployment/hwlab-cloud-api'"); expect(remoteCommand).toContain("'cat -- \"$1\"'"); expect(remoteCommand).toContain("'/tmp/trace.json'"); }); }); describe("ssh host apply-patch fs backend", () => { test("uses POSIX bulk fs operations for host routes", async () => { const invocation = parseSshInvocation("D601:/mnt/f/Work/ConStart", ["apply-patch"]); const text = "alpha\nold\nomega\n"; const calls: Array<{ operation: string; args: string[]; input?: string }> = []; const fs = createPosixApplyPatchFileSystem(invocation, async (command, input) => { const operation = command[4] ?? ""; const args = command.slice(5); calls.push({ operation, args, input }); if (operation === "read-bulk-b64") { return { exitCode: 0, stderr: "", stdout: [ "UNIDESK_APPLY_PATCH_V2_BULK_READ 1", [ Buffer.from("docs/test.md", "utf8").toString("base64"), String(Buffer.byteLength(text, "utf8")), sha256Hex(text), Buffer.from(text, "utf8").toString("base64"), ].join(" "), "", ].join("\n"), }; } if (operation === "apply-replacements-bulk-stdin") { expect(input).toContain(Buffer.from("docs/test.md", "utf8").toString("base64")); return { exitCode: 0, stdout: "", stderr: "" }; } throw new Error(`unexpected operation ${operation}`); }); const files = await fs.readFiles!(["docs/test.md"]); await fs.applyReplacementsBulk!(["docs/test.md"], new Map([[ "docs/test.md", { path: "docs/test.md", originalBytes: Buffer.byteLength(text, "utf8"), originalSha256: sha256Hex(text), finalBytes: Buffer.byteLength("alpha\nnew\nomega\n", "utf8"), finalSha256: sha256Hex("alpha\nnew\nomega\n"), newlineStyle: "lf", encodingStyle: "utf8", replacements: [[1, 1, ["new"]]], }, ]])); expect(files.get("docs/test.md")?.content).toBe(text); expect(calls.map((call) => call.operation)).toEqual(["read-bulk-b64", "apply-replacements-bulk-stdin"]); expect(calls[0]?.args).toEqual(["docs/test.md"]); expect(calls[1]?.args).toEqual(["1"]); }); }); describe("ssh WSL mounted workspace ownership", () => { test("bridges the mounted workspace owner to Git for direct and child shell commands", () => { const direct = parseSshInvocation("G14-WSL:/mnt/d/Work/demo", ["git", "rev-parse", "--show-toplevel"]); const childShell = parseSshInvocation("G14-WSL:/mnt/d/Work/demo", ["bash", "--", "git status --short"]); for (const invocation of [direct, childShell]) { expect(invocation.parsed.remoteCommand).toContain("UNIDESK_TRANS_HOST_WORKSPACE_OWNER_UID"); expect(invocation.parsed.remoteCommand).toContain('export SUDO_UID="$UNIDESK_TRANS_HOST_WORKSPACE_OWNER_UID"'); expect(invocation.parsed.remoteCommand).toContain('stat -c %u .'); } }); test("does not change ownership trust for ordinary Linux workspaces", () => { const invocation = parseSshInvocation("D601:/home/ubuntu/workspace/demo", ["git", "status", "--short"]); expect(invocation.parsed.remoteCommand).not.toContain("UNIDESK_TRANS_HOST_WORKSPACE_OWNER_UID"); expect(invocation.parsed.remoteCommand).not.toContain("SUDO_UID"); }); }); describe("ssh stdout bounded streaming", () => { test("uses YAML output policy defaults and clamps env override", () => { const policy = readCliOutputPolicy(); expect(sshStdoutStreamMaxBytes({} as NodeJS.ProcessEnv)).toBe(policy.maxStdoutBytes); expect(sshStderrStreamMaxBytes({} as NodeJS.ProcessEnv)).toBe(policy.maxStdoutBytes); expect(sshStdoutStreamMaxBytes({ UNIDESK_SSH_STDOUT_STREAM_MAX_BYTES: "8192" } as NodeJS.ProcessEnv)).toBe(8192); expect(sshStderrStreamMaxBytes({ UNIDESK_SSH_STDERR_STREAM_MAX_BYTES: "8192" } as NodeJS.ProcessEnv)).toBe(8192); expect(sshStdoutStreamMaxBytes({ UNIDESK_SSH_STDOUT_STREAM_MAX_BYTES: "1" } as NodeJS.ProcessEnv)).toBe(4096); expect(sshStdoutStreamMaxBytes({ UNIDESK_SSH_STDOUT_STREAM_MAX_BYTES: String(64 * 1024 * 1024) } as NodeJS.ProcessEnv)).toBe(16 * 1024 * 1024); }); test("formats truncation hint without echoing remote command", () => { const invocation = parseSshInvocation("D601:win", ["ps"]); expect(invocation.parsed.remoteCommand).not.toBeNull(); const hint = sshStdoutTruncationHint({ invocation, transport: "backend-core-broker", thresholdBytes: 4096, observedBytesAtTruncation: 5000, dumpPath: "/tmp/unidesk-cli-output/sample.stdout.bin", }); const formatted = formatSshStdoutTruncationHint(hint); expect(formatted.startsWith("UNIDESK_SSH_STDOUT_TRUNCATED ")).toBe(true); const payload = JSON.parse(formatted.slice("UNIDESK_SSH_STDOUT_TRUNCATED ".length)) as Record; expect(payload.code).toBe("ssh-stdout-truncated"); expect(payload.providerId).toBe("D601"); expect(payload.route).toBe("D601:win"); expect(payload.thresholdBytes).toBe(4096); expect(payload.disclosurePolicy).toMatchObject({ name: "unified-cli-dump-preview", configPath: "config/unidesk-cli.yaml", }); expect(formatted).not.toContain("Get-Content"); }); test("forwarder bounds stdout and emits one truncation hint", () => { const invocation = parseSshInvocation("D601:win", ["ps"]); const forwarded: Buffer[] = []; const stdout = { write(chunk: string | Buffer): boolean { forwarded.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); return true; }, } as NodeJS.WritableStream; const forwarder = createSshStdoutForwarder({ invocation, transport: "frontend-websocket", maxBytes: 5, stdout, }); expect(forwarder.write(Buffer.from("abc"))).toBeNull(); const hint = forwarder.write(Buffer.from("defgh")); expect(hint).not.toBeNull(); expect(forwarder.write(Buffer.from("ijkl"))).toBeNull(); expect(Buffer.concat(forwarded).toString("utf8")).toBe("abcde"); expect(hint).toContain("UNIDESK_SSH_STDOUT_TRUNCATED"); expect(hint).toContain("\"transport\":\"frontend-websocket\""); expect(hint).toContain("\"forwardedBytes\":5"); const payload = JSON.parse(hint!.slice("UNIDESK_SSH_STDOUT_TRUNCATED ".length)) as { dumpPath: string }; expect(readFileSync(payload.dumpPath, "utf8")).toBe("abcdefghijkl"); const summary = sshTruncationCompletionSummary({ invocation, transport: "frontend-websocket", exitCode: 0, timedOut: false, startedAtMs: Date.now() - 1234, stdout: forwarder.summary(), stderr: null, }); const formattedSummary = formatSshTruncationCompletionSummary(summary); expect(formattedSummary).toContain("UNIDESK_SSH_TRUNCATION_SUMMARY"); expect(formattedSummary).toContain("\"exitCode\":0"); expect(formattedSummary).toContain("\"commandOmitted\":true"); expect(formattedSummary).toContain("\"dumpPath\""); rmSync(payload.dumpPath, { force: true }); }); test("stderr forwarder uses the same dump guard and marker", () => { const invocation = parseSshInvocation("D601:win", ["ps"]); const forwarded: Buffer[] = []; const stderr = { write(chunk: string | Buffer): boolean { forwarded.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); return true; }, } as NodeJS.WritableStream; const forwarder = createSshStderrForwarder({ invocation, transport: "backend-core-broker", maxBytes: 4, stderr, }); const hint = forwarder.write(Buffer.from("abcdef")); expect(Buffer.concat(forwarded).toString("utf8")).toBe("abcd"); expect(hint).toContain("UNIDESK_SSH_STDERR_TRUNCATED"); expect(hint).toContain("\"stream\":\"stderr\""); }); }); describe("ssh backend selection", () => { test("uses trans YAML backend-core preference without runtime docker detection", () => { const dir = mkdtempSync(join(tmpdir(), "unidesk-trans-config-")); const configPath = join(dir, "trans.ymal"); writeFileSync(configPath, [ "version: 1", "kind: UniDeskTransConfig", "ssh:", " backend: backend-core", "", ].join("\n")); try { const plan = sshCaptureBackendPlan(minimalConfig(), { UNIDESK_TRANS_CONFIG_PATH: configPath } as NodeJS.ProcessEnv); expect(plan.backend).toBe("local-backend-core-broker"); expect(plan.remoteHost).toBeNull(); expect(plan.reason).toBe(`configured:${configPath}#ssh.backend`); expect(plan.localBackendCore.backendCoreContainer).toBe(true); expect(plan.localBackendCore.source).toBe(`${configPath}#ssh.backend`); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("uses trans YAML frontend websocket preference when configured", () => { const dir = mkdtempSync(join(tmpdir(), "unidesk-trans-config-")); const configPath = join(dir, "trans.ymal"); writeFileSync(configPath, [ "version: 1", "kind: UniDeskTransConfig", "ssh:", " backend: frontend-websocket", "", ].join("\n")); try { const plan = sshCaptureBackendPlan(minimalConfig("198.51.100.4"), { UNIDESK_TRANS_CONFIG_PATH: configPath, UNIDESK_MAIN_SERVER_IP: "198.51.100.4", } as NodeJS.ProcessEnv); expect(plan.backend).toBe("remote-frontend-websocket"); expect(plan.remoteHost).toBe("198.51.100.4"); expect(plan.reason).toBe(`configured:${configPath}#ssh.backend`); } finally { rmSync(dir, { recursive: true, force: true }); } }); }); describe("ssh removed shell aliases", () => { test("reports route-aware replacement examples for trans script", () => { const previousEntrypoint = process.env.UNIDESK_SSH_ENTRYPOINT; process.env.UNIDESK_SSH_ENTRYPOINT = "trans"; try { parseSshInvocation("D601:/tmp", ["script", "--", "pwd"]); throw new Error("expected script alias to fail"); } catch (error) { expect(error).toBeInstanceOf(Error); const payload = error as Error & { code?: string; entrypoint?: string; route?: string; operation?: string; replacementExamples?: { posixSh?: string; bash?: string; }; }; expect(payload.code).toBe("ssh-removed-shell-alias"); expect(payload.entrypoint).toBe("trans"); expect(payload.route).toBe("D601:/tmp"); expect(payload.operation).toBe("script"); expect(payload.replacementExamples?.posixSh).toBe("trans D601:/tmp sh -- 'pwd'"); expect(payload.replacementExamples?.bash).toBe("trans D601:/tmp bash -- 'pwd'"); } finally { if (previousEntrypoint === undefined) { delete process.env.UNIDESK_SSH_ENTRYPOINT; } else { process.env.UNIDESK_SSH_ENTRYPOINT = previousEntrypoint; } } }); });