Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
491 lines
12 KiB
TypeScript
491 lines
12 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import type { GroupConfig, Host, ProxyProfile, TerminalSession } from "./models";
|
|
import {
|
|
resolveTerminalChainHosts,
|
|
resolveTerminalSessionHost,
|
|
} from "./terminalHostResolution";
|
|
|
|
const baseSession: TerminalSession = {
|
|
id: "session-1",
|
|
hostId: "target",
|
|
hostLabel: "Target",
|
|
hostname: "target.example.test",
|
|
username: "alice",
|
|
port: 22,
|
|
protocol: "ssh",
|
|
status: "connected",
|
|
createdAt: 1,
|
|
};
|
|
|
|
const proxyProfiles: ProxyProfile[] = [{
|
|
id: "proxy-1",
|
|
label: "Office proxy",
|
|
config: {
|
|
type: "http",
|
|
host: "proxy.example.test",
|
|
port: 3128,
|
|
username: "proxy-user",
|
|
},
|
|
createdAt: 1,
|
|
}];
|
|
|
|
test("resolveTerminalSessionHost materializes a saved proxy profile for popup terminals", () => {
|
|
const host: Host = {
|
|
id: "target",
|
|
label: "Target",
|
|
hostname: "target.example.test",
|
|
username: "alice",
|
|
port: 22,
|
|
protocol: "ssh",
|
|
tags: [],
|
|
os: "linux",
|
|
proxyProfileId: "proxy-1",
|
|
};
|
|
|
|
const resolved = resolveTerminalSessionHost({
|
|
session: baseSession,
|
|
hosts: [host],
|
|
groupConfigs: [],
|
|
proxyProfiles,
|
|
localOs: "linux",
|
|
});
|
|
|
|
assert.equal(resolved.proxyProfileId, "proxy-1");
|
|
assert.deepEqual(resolved.proxyConfig, proxyProfiles[0].config);
|
|
});
|
|
|
|
test("resolveTerminalSessionHost applies group default proxy profiles before opening popup terminals", () => {
|
|
const host: Host = {
|
|
id: "target",
|
|
label: "Target",
|
|
hostname: "target.example.test",
|
|
username: "alice",
|
|
port: 22,
|
|
protocol: "ssh",
|
|
tags: [],
|
|
os: "linux",
|
|
group: "prod/web",
|
|
};
|
|
const groupConfigs: GroupConfig[] = [
|
|
{ path: "prod", proxyProfileId: "proxy-1" },
|
|
];
|
|
|
|
const resolved = resolveTerminalSessionHost({
|
|
session: baseSession,
|
|
hosts: [host],
|
|
groupConfigs,
|
|
proxyProfiles,
|
|
localOs: "linux",
|
|
});
|
|
|
|
assert.equal(resolved.proxyProfileId, "proxy-1");
|
|
assert.deepEqual(resolved.proxyConfig, proxyProfiles[0].config);
|
|
});
|
|
|
|
test("resolveTerminalSessionHost defaults missing saved remote sessions to SSH", () => {
|
|
const resolved = resolveTerminalSessionHost({
|
|
session: {
|
|
...baseSession,
|
|
protocol: undefined,
|
|
},
|
|
hosts: [],
|
|
groupConfigs: [],
|
|
proxyProfiles,
|
|
localOs: "macos",
|
|
});
|
|
|
|
assert.equal(resolved.protocol, "ssh");
|
|
assert.equal(resolved.hostname, "target.example.test");
|
|
assert.equal(resolved.username, "alice");
|
|
assert.equal(resolved.os, "linux");
|
|
});
|
|
|
|
test("resolveTerminalSessionHost keeps explicit missing local sessions local", () => {
|
|
const resolved = resolveTerminalSessionHost({
|
|
session: {
|
|
...baseSession,
|
|
protocol: "local",
|
|
hostname: "localhost",
|
|
username: "local",
|
|
},
|
|
hosts: [],
|
|
groupConfigs: [],
|
|
proxyProfiles,
|
|
localOs: "macos",
|
|
});
|
|
|
|
assert.equal(resolved.protocol, "local");
|
|
assert.equal(resolved.os, "macos");
|
|
});
|
|
|
|
test("resolveTerminalSessionHost restores a missing plugin host from the session snapshot", () => {
|
|
const providerId = "com.example.transport.connection";
|
|
const pluginConnection = {
|
|
providerId,
|
|
configuration: { endpoint: "saved.example", mode: "opaque" },
|
|
};
|
|
const resolved = resolveTerminalSessionHost({
|
|
session: {
|
|
...baseSession,
|
|
hostId: "missing-plugin-host",
|
|
protocol: `plugin:${providerId}`,
|
|
pluginConnection,
|
|
},
|
|
hosts: [],
|
|
groupConfigs: [],
|
|
proxyProfiles,
|
|
localOs: "macos",
|
|
});
|
|
|
|
assert.equal(resolved.protocol, `plugin:${providerId}`);
|
|
assert.deepEqual(resolved.pluginConnection, pluginConnection);
|
|
});
|
|
|
|
test("resolveTerminalSessionHost keeps the session plugin configuration when the Vault host changes", () => {
|
|
const providerId = "com.example.transport.connection";
|
|
const protocol = `plugin:${providerId}` as const;
|
|
const resolved = resolveTerminalSessionHost({
|
|
session: {
|
|
...baseSession,
|
|
protocol,
|
|
pluginConnection: { providerId, configuration: { endpoint: "session.example" } },
|
|
},
|
|
hosts: [{
|
|
id: "target",
|
|
label: "Target",
|
|
hostname: providerId,
|
|
username: "",
|
|
tags: [],
|
|
os: "linux",
|
|
protocol,
|
|
pluginConnection: { providerId, configuration: { endpoint: "vault.example" } },
|
|
}],
|
|
groupConfigs: [],
|
|
proxyProfiles,
|
|
localOs: "linux",
|
|
});
|
|
|
|
assert.deepEqual(resolved.pluginConnection, {
|
|
providerId,
|
|
configuration: { endpoint: "session.example" },
|
|
});
|
|
});
|
|
|
|
test("resolveTerminalSessionHost carries local session start directory into fallback host", () => {
|
|
const resolved = resolveTerminalSessionHost({
|
|
session: {
|
|
...baseSession,
|
|
protocol: "local",
|
|
hostname: "localhost",
|
|
username: "local",
|
|
localStartDir: "/Users/alice/project",
|
|
},
|
|
hosts: [],
|
|
groupConfigs: [],
|
|
proxyProfiles,
|
|
localOs: "macos",
|
|
});
|
|
|
|
assert.equal(resolved.protocol, "local");
|
|
assert.equal(resolved.localStartDir, "/Users/alice/project");
|
|
});
|
|
|
|
test("resolveTerminalSessionHost carries serial Ctrl-H backspace behavior into quick sessions", () => {
|
|
const resolved = resolveTerminalSessionHost({
|
|
session: {
|
|
...baseSession,
|
|
hostId: "serial-session-1",
|
|
hostLabel: "Serial: COM3",
|
|
hostname: "COM3",
|
|
username: "",
|
|
protocol: "serial",
|
|
serialConfig: {
|
|
path: "COM3",
|
|
baudRate: 115200,
|
|
backspaceBehavior: "ctrl-h",
|
|
},
|
|
},
|
|
hosts: [],
|
|
groupConfigs: [],
|
|
proxyProfiles,
|
|
localOs: "windows",
|
|
});
|
|
|
|
assert.equal(resolved.protocol, "serial");
|
|
assert.equal(resolved.backspaceBehavior, "ctrl-h");
|
|
});
|
|
|
|
test("resolveTerminalSessionHost applies serial Ctrl-H backspace behavior from saved hosts", () => {
|
|
const host: Host = {
|
|
id: "target",
|
|
label: "Serial: COM3",
|
|
hostname: "COM3",
|
|
username: "",
|
|
port: 115200,
|
|
protocol: "serial",
|
|
tags: [],
|
|
os: "linux",
|
|
serialConfig: {
|
|
path: "COM3",
|
|
baudRate: 115200,
|
|
backspaceBehavior: "ctrl-h",
|
|
},
|
|
};
|
|
|
|
const resolved = resolveTerminalSessionHost({
|
|
session: {
|
|
...baseSession,
|
|
hostLabel: "Serial: COM3",
|
|
hostname: "COM3",
|
|
username: "",
|
|
port: undefined,
|
|
protocol: "serial",
|
|
},
|
|
hosts: [host],
|
|
groupConfigs: [],
|
|
proxyProfiles,
|
|
localOs: "windows",
|
|
});
|
|
|
|
assert.equal(resolved.protocol, "serial");
|
|
assert.equal(resolved.backspaceBehavior, "ctrl-h");
|
|
});
|
|
|
|
test("resolveTerminalSessionHost preserves inherited Ctrl-H for legacy restored serial sessions", () => {
|
|
const host: Host = {
|
|
id: "target",
|
|
label: "Serial: COM3",
|
|
hostname: "COM3",
|
|
username: "",
|
|
port: 115200,
|
|
protocol: "serial",
|
|
tags: [],
|
|
os: "linux",
|
|
group: "serial-devices",
|
|
serialConfig: {
|
|
path: "COM3",
|
|
baudRate: 115200,
|
|
},
|
|
};
|
|
|
|
const resolved = resolveTerminalSessionHost({
|
|
session: {
|
|
...baseSession,
|
|
hostLabel: "Serial: COM3",
|
|
hostname: "COM3",
|
|
username: "",
|
|
port: undefined,
|
|
protocol: "serial",
|
|
serialConfig: {
|
|
path: "COM3",
|
|
baudRate: 115200,
|
|
},
|
|
},
|
|
hosts: [host],
|
|
groupConfigs: [{ path: "serial-devices", backspaceBehavior: "ctrl-h" }],
|
|
proxyProfiles,
|
|
localOs: "windows",
|
|
});
|
|
|
|
assert.equal(resolved.backspaceBehavior, "ctrl-h");
|
|
});
|
|
|
|
test("resolveTerminalSessionHost keeps the serial Backspace behavior captured by the session", () => {
|
|
const host: Host = {
|
|
id: "target",
|
|
label: "Serial: COM3",
|
|
hostname: "COM3",
|
|
username: "",
|
|
port: 115200,
|
|
protocol: "serial",
|
|
tags: [],
|
|
os: "linux",
|
|
serialConfig: {
|
|
path: "COM3",
|
|
baudRate: 115200,
|
|
backspaceBehavior: "default",
|
|
},
|
|
};
|
|
|
|
const ctrlHSession = resolveTerminalSessionHost({
|
|
session: {
|
|
...baseSession,
|
|
hostname: "COM3",
|
|
username: "",
|
|
protocol: "serial",
|
|
serialConfig: {
|
|
path: "COM3",
|
|
baudRate: 115200,
|
|
backspaceBehavior: "ctrl-h",
|
|
},
|
|
},
|
|
hosts: [host],
|
|
groupConfigs: [],
|
|
proxyProfiles,
|
|
localOs: "windows",
|
|
});
|
|
|
|
const defaultSession = resolveTerminalSessionHost({
|
|
session: {
|
|
...baseSession,
|
|
hostname: "COM3",
|
|
username: "",
|
|
protocol: "serial",
|
|
serialConfig: {
|
|
path: "COM3",
|
|
baudRate: 115200,
|
|
backspaceBehavior: "default",
|
|
},
|
|
},
|
|
hosts: [{
|
|
...host,
|
|
serialConfig: {
|
|
...host.serialConfig!,
|
|
backspaceBehavior: "ctrl-h",
|
|
},
|
|
}],
|
|
groupConfigs: [],
|
|
proxyProfiles,
|
|
localOs: "windows",
|
|
});
|
|
|
|
assert.equal(ctrlHSession.backspaceBehavior, "ctrl-h");
|
|
assert.equal(defaultSession.backspaceBehavior, undefined);
|
|
});
|
|
|
|
test("resolveTerminalSessionHost suppresses inherited network device mode for Mosh sessions", () => {
|
|
const host: Host = {
|
|
id: "target",
|
|
label: "Target",
|
|
hostname: "target.example.test",
|
|
username: "alice",
|
|
port: 22,
|
|
protocol: "ssh",
|
|
tags: [],
|
|
os: "linux",
|
|
group: "prod/web",
|
|
};
|
|
const groupConfigs: GroupConfig[] = [
|
|
{ path: "prod", deviceType: "network" },
|
|
{ path: "prod/web", moshEnabled: true },
|
|
];
|
|
|
|
const resolved = resolveTerminalSessionHost({
|
|
session: baseSession,
|
|
hosts: [host],
|
|
groupConfigs,
|
|
proxyProfiles,
|
|
localOs: "linux",
|
|
});
|
|
|
|
assert.equal(resolved.moshEnabled, true);
|
|
assert.equal(resolved.deviceType, undefined);
|
|
});
|
|
|
|
test("resolveTerminalSessionHost suppresses explicit network device mode for ET session overrides", () => {
|
|
const host: Host = {
|
|
id: "target",
|
|
label: "Target",
|
|
hostname: "target.example.test",
|
|
username: "alice",
|
|
port: 22,
|
|
protocol: "ssh",
|
|
tags: [],
|
|
os: "linux",
|
|
deviceType: "network",
|
|
};
|
|
|
|
const resolved = resolveTerminalSessionHost({
|
|
session: {
|
|
...baseSession,
|
|
etEnabled: true,
|
|
},
|
|
hosts: [host],
|
|
groupConfigs: [],
|
|
proxyProfiles,
|
|
localOs: "linux",
|
|
});
|
|
|
|
assert.equal(resolved.etEnabled, true);
|
|
assert.equal(resolved.deviceType, undefined);
|
|
assert.equal(host.deviceType, "network");
|
|
});
|
|
|
|
test("resolveTerminalChainHosts materializes proxy profiles on jump hosts", () => {
|
|
const target: Host = {
|
|
id: "target",
|
|
label: "Target",
|
|
hostname: "target.example.test",
|
|
username: "alice",
|
|
port: 22,
|
|
protocol: "ssh",
|
|
tags: [],
|
|
os: "linux",
|
|
hostChain: { hostIds: ["jump-1"] },
|
|
};
|
|
const jumpHost: Host = {
|
|
id: "jump-1",
|
|
label: "Jump",
|
|
hostname: "jump.example.test",
|
|
username: "jump",
|
|
port: 22,
|
|
protocol: "ssh",
|
|
tags: [],
|
|
os: "linux",
|
|
proxyProfileId: "proxy-1",
|
|
};
|
|
|
|
const resolved = resolveTerminalChainHosts({
|
|
host: target,
|
|
hosts: [target, jumpHost],
|
|
groupConfigs: [],
|
|
proxyProfiles,
|
|
});
|
|
|
|
assert.equal(resolved.length, 1);
|
|
assert.equal(resolved[0]?.id, "jump-1");
|
|
assert.deepEqual(resolved[0]?.proxyConfig, proxyProfiles[0].config);
|
|
});
|
|
|
|
test("resolveTerminalChainHosts applies group default proxy profiles to jump hosts", () => {
|
|
const target: Host = {
|
|
id: "target",
|
|
label: "Target",
|
|
hostname: "target.example.test",
|
|
username: "alice",
|
|
port: 22,
|
|
protocol: "ssh",
|
|
tags: [],
|
|
os: "linux",
|
|
hostChain: { hostIds: ["jump-1"] },
|
|
};
|
|
const jumpHost: Host = {
|
|
id: "jump-1",
|
|
label: "Jump",
|
|
hostname: "jump.example.test",
|
|
username: "jump",
|
|
port: 22,
|
|
protocol: "ssh",
|
|
tags: [],
|
|
os: "linux",
|
|
group: "bastion",
|
|
};
|
|
const groupConfigs: GroupConfig[] = [
|
|
{ path: "bastion", proxyProfileId: "proxy-1" },
|
|
];
|
|
|
|
const resolved = resolveTerminalChainHosts({
|
|
host: target,
|
|
hosts: [target, jumpHost],
|
|
groupConfigs,
|
|
proxyProfiles,
|
|
});
|
|
|
|
assert.equal(resolved.length, 1);
|
|
assert.equal(resolved[0]?.id, "jump-1");
|
|
assert.equal(resolved[0]?.proxyProfileId, "proxy-1");
|
|
assert.deepEqual(resolved[0]?.proxyConfig, proxyProfiles[0].config);
|
|
});
|