Files
NetMesh/components/terminal/runtime/createTerminalSessionStarters.telnet.test.ts
zhaolei 3c72efcb7f
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
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

1644 lines
48 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import {
createTerminalSessionStarters,
splitStartupCommandLines,
normalizeStartupCommandDelay,
} from "./createTerminalSessionStarters";
const noop = () => undefined;
const ENCRYPTED_CREDENTIAL_PLACEHOLDER = "enc:v1:djEwdGVzdAAAAAAAAAAAAAAAAA==";
test("startTelnet rejects missing saved proxy profiles", async () => {
let started = false;
let error = "";
const terminalBackend = {
backendAvailable: () => true,
telnetAvailable: () => true,
moshAvailable: () => true,
localAvailable: () => true,
serialAvailable: () => true,
execAvailable: () => true,
startSSHSession: async () => "ssh-session",
startTelnetSession: async () => {
started = true;
return "telnet-session";
},
startMoshSession: async () => "mosh-session",
startLocalSession: async () => "local-session",
startSerialSession: async () => "serial-session",
execCommand: async () => ({}),
onSessionData: () => noop,
onSessionExit: () => noop,
onChainProgress: () => noop,
writeToSession: noop,
resizeSession: noop,
};
const ctx = {
host: {
id: "host-1",
label: "Example",
hostname: "example.test",
username: "alice",
telnetPort: 2323,
proxyProfileId: "missing-proxy",
},
keys: [],
resolvedChainHosts: [],
sessionId: "session-1",
terminalSettings: {},
terminalBackend,
sessionRef: { current: null },
hasConnectedRef: { current: false },
hasRunStartupCommandRef: { current: false },
disposeDataRef: { current: null },
disposeExitRef: { current: null },
fitAddonRef: { current: null },
serializeAddonRef: { current: null },
pendingAuthRef: { current: null },
updateStatus: noop,
setStatus: noop,
setError: (message: string) => { error = message; },
setNeedsAuth: noop,
setAuthRetryMessage: noop,
setAuthPassword: noop,
setProgressLogs: noop,
setProgressValue: noop,
setChainProgress: noop,
};
const term = {
cols: 120,
rows: 32,
write: noop,
writeln: noop,
scrollToBottom: noop,
};
await createTerminalSessionStarters(ctx as never).startTelnet(term as never);
assert.equal(started, false);
assert.match(error, /Saved proxy/);
});
test("startTelnet rejects missing proxy identities before connecting", async () => {
let started = false;
let error = "";
const terminalBackend = {
backendAvailable: () => true,
telnetAvailable: () => true,
moshAvailable: () => true,
localAvailable: () => true,
serialAvailable: () => true,
execAvailable: () => true,
startSSHSession: async () => "ssh-session",
startTelnetSession: async () => {
started = true;
return "telnet-session";
},
startMoshSession: async () => "mosh-session",
startLocalSession: async () => "local-session",
startSerialSession: async () => "serial-session",
execCommand: async () => ({}),
onSessionData: () => noop,
onSessionExit: () => noop,
onChainProgress: () => noop,
writeToSession: noop,
resizeSession: noop,
};
const ctx = {
host: {
id: "host-1",
label: "Example",
hostname: "example.test",
username: "alice",
telnetPort: 2323,
proxyConfig: {
type: "http",
host: "proxy.example.test",
port: 3128,
identityId: "missing-identity",
},
},
keys: [],
identities: [],
resolvedChainHosts: [],
sessionId: "session-1",
terminalSettings: {},
terminalBackend,
sessionRef: { current: null },
hasConnectedRef: { current: false },
hasRunStartupCommandRef: { current: false },
disposeDataRef: { current: null },
disposeExitRef: { current: null },
fitAddonRef: { current: null },
serializeAddonRef: { current: null },
pendingAuthRef: { current: null },
updateStatus: noop,
setStatus: noop,
setError: (message: string) => { error = message; },
setNeedsAuth: noop,
setAuthRetryMessage: noop,
setAuthPassword: noop,
setProgressLogs: noop,
setProgressValue: noop,
setChainProgress: noop,
};
const term = {
cols: 120,
rows: 32,
write: noop,
writeln: noop,
scrollToBottom: noop,
};
await createTerminalSessionStarters(ctx as never).startTelnet(term as never);
assert.equal(started, false);
assert.match(error, /Proxy identity/);
assert.match(error, /Example/);
});
test("startTelnet passes saved telnet credentials without falling back after explicit clears", async () => {
let capturedOptions: Record<string, unknown> | null = null;
const terminalBackend = {
backendAvailable: () => true,
telnetAvailable: () => true,
moshAvailable: () => true,
localAvailable: () => true,
serialAvailable: () => true,
execAvailable: () => true,
startSSHSession: async () => "ssh-session",
startTelnetSession: async (options: Record<string, unknown>) => {
capturedOptions = options;
return "telnet-session";
},
startMoshSession: async () => "mosh-session",
startLocalSession: async () => "local-session",
startSerialSession: async () => "serial-session",
execCommand: async () => ({}),
onSessionData: () => noop,
onSessionExit: () => noop,
onChainProgress: () => noop,
writeToSession: noop,
resizeSession: noop,
};
const ctx = {
host: {
id: "host-1",
label: "Example",
hostname: "example.test",
username: "ssh-user",
password: "ssh-password",
telnetUsername: "",
telnetPassword: "telnet-password",
telnetPort: 2323,
},
keys: [],
resolvedChainHosts: [],
sessionId: "session-1",
terminalSettings: {},
terminalBackend,
sessionRef: { current: null },
hasConnectedRef: { current: false },
hasRunStartupCommandRef: { current: false },
disposeDataRef: { current: null },
disposeExitRef: { current: null },
fitAddonRef: { current: null },
serializeAddonRef: { current: null },
pendingAuthRef: { current: null },
updateStatus: noop,
setStatus: noop,
setError: noop,
setNeedsAuth: noop,
setAuthRetryMessage: noop,
setAuthPassword: noop,
setProgressLogs: noop,
setProgressValue: noop,
setChainProgress: noop,
};
const term = {
cols: 120,
rows: 32,
write: noop,
writeln: noop,
scrollToBottom: noop,
};
await createTerminalSessionStarters(ctx as never).startTelnet(term as never);
assert.ok(capturedOptions);
assert.equal(capturedOptions.username, "");
assert.equal(capturedOptions.password, "telnet-password");
assert.equal(capturedOptions.port, 2323);
});
test("startTelnet preserves an explicitly cleared telnet password", async () => {
let capturedOptions: Record<string, unknown> | null = null;
const terminalBackend = {
backendAvailable: () => true,
telnetAvailable: () => true,
moshAvailable: () => true,
localAvailable: () => true,
serialAvailable: () => true,
execAvailable: () => true,
startSSHSession: async () => "ssh-session",
startTelnetSession: async (options: Record<string, unknown>) => {
capturedOptions = options;
return "telnet-session";
},
startMoshSession: async () => "mosh-session",
startLocalSession: async () => "local-session",
startSerialSession: async () => "serial-session",
execCommand: async () => ({}),
onSessionData: () => noop,
onSessionExit: () => noop,
onChainProgress: () => noop,
writeToSession: noop,
resizeSession: noop,
};
const ctx = {
host: {
id: "host-1",
label: "Example",
hostname: "example.test",
username: "ssh-user",
password: "ssh-password",
telnetUsername: "telnet-user",
telnetPassword: "",
telnetPort: 2323,
},
keys: [],
resolvedChainHosts: [],
sessionId: "session-1",
terminalSettings: {},
terminalBackend,
sessionRef: { current: null },
hasConnectedRef: { current: false },
hasRunStartupCommandRef: { current: false },
disposeDataRef: { current: null },
disposeExitRef: { current: null },
fitAddonRef: { current: null },
serializeAddonRef: { current: null },
pendingAuthRef: { current: null },
updateStatus: noop,
setStatus: noop,
setError: noop,
setNeedsAuth: noop,
setAuthRetryMessage: noop,
setAuthPassword: noop,
setProgressLogs: noop,
setProgressValue: noop,
setChainProgress: noop,
};
const term = {
cols: 120,
rows: 32,
write: noop,
writeln: noop,
scrollToBottom: noop,
};
await createTerminalSessionStarters(ctx as never).startTelnet(term as never);
assert.ok(capturedOptions);
assert.equal(capturedOptions.username, "telnet-user");
assert.equal(capturedOptions.password, "");
});
test("startTelnet uses selected telnet identity credentials when telnet fields are unset", async () => {
let capturedOptions: Record<string, unknown> | null = null;
const terminalBackend = {
backendAvailable: () => true,
telnetAvailable: () => true,
moshAvailable: () => true,
localAvailable: () => true,
serialAvailable: () => true,
execAvailable: () => true,
startSSHSession: async () => "ssh-session",
startTelnetSession: async (options: Record<string, unknown>) => {
capturedOptions = options;
return "telnet-session";
},
startMoshSession: async () => "mosh-session",
startLocalSession: async () => "local-session",
startSerialSession: async () => "serial-session",
execCommand: async () => ({}),
onSessionData: () => noop,
onSessionExit: () => noop,
onChainProgress: () => noop,
writeToSession: noop,
resizeSession: noop,
};
const ctx = {
host: {
id: "host-1",
label: "Example",
hostname: "example.test",
username: "ssh-user",
identityId: "ssh-identity",
telnetIdentityId: "telnet-identity",
telnetUsername: undefined,
telnetPassword: undefined,
telnetPort: 2323,
},
keys: [],
identities: [{
id: "ssh-identity",
label: "SSH login",
username: "ssh-identity-user",
authMethod: "password",
password: "ssh-identity-password",
created: 1,
}, {
id: "telnet-identity",
label: "Network login",
username: "telnet-identity-user",
authMethod: "password",
password: "telnet-identity-password",
created: 2,
}],
resolvedChainHosts: [],
sessionId: "session-1",
terminalSettings: {},
terminalBackend,
sessionRef: { current: null },
hasConnectedRef: { current: false },
hasRunStartupCommandRef: { current: false },
disposeDataRef: { current: null },
disposeExitRef: { current: null },
fitAddonRef: { current: null },
serializeAddonRef: { current: null },
pendingAuthRef: { current: null },
updateStatus: noop,
setStatus: noop,
setError: noop,
setNeedsAuth: noop,
setAuthRetryMessage: noop,
setAuthPassword: noop,
setProgressLogs: noop,
setProgressValue: noop,
setChainProgress: noop,
};
const term = {
cols: 120,
rows: 32,
write: noop,
writeln: noop,
scrollToBottom: noop,
};
await createTerminalSessionStarters(ctx as never).startTelnet(term as never);
assert.ok(capturedOptions);
assert.equal(capturedOptions.username, "telnet-identity-user");
assert.equal(capturedOptions.password, "telnet-identity-password");
});
test("startTelnet does not fall back to ssh identity credentials when telnet identity is unset", async () => {
let capturedOptions: Record<string, unknown> | null = null;
const terminalBackend = {
backendAvailable: () => true,
telnetAvailable: () => true,
moshAvailable: () => true,
localAvailable: () => true,
serialAvailable: () => true,
execAvailable: () => true,
startSSHSession: async () => "ssh-session",
startTelnetSession: async (options: Record<string, unknown>) => {
capturedOptions = options;
return "telnet-session";
},
startMoshSession: async () => "mosh-session",
startLocalSession: async () => "local-session",
startSerialSession: async () => "serial-session",
execCommand: async () => ({}),
onSessionData: () => noop,
onSessionExit: () => noop,
onChainProgress: () => noop,
writeToSession: noop,
resizeSession: noop,
};
const ctx = {
host: {
id: "host-1",
label: "Example",
hostname: "example.test",
username: "manual-host-user",
identityId: "ssh-identity",
telnetUsername: undefined,
telnetPassword: undefined,
telnetPort: 2323,
},
keys: [],
identities: [{
id: "ssh-identity",
label: "SSH login",
username: "ssh-identity-user",
authMethod: "password",
password: "ssh-identity-password",
created: 1,
}],
resolvedChainHosts: [],
sessionId: "session-1",
terminalSettings: {},
terminalBackend,
sessionRef: { current: null },
hasConnectedRef: { current: false },
hasRunStartupCommandRef: { current: false },
disposeDataRef: { current: null },
disposeExitRef: { current: null },
fitAddonRef: { current: null },
serializeAddonRef: { current: null },
pendingAuthRef: { current: null },
updateStatus: noop,
setStatus: noop,
setError: noop,
setNeedsAuth: noop,
setAuthRetryMessage: noop,
setAuthPassword: noop,
setProgressLogs: noop,
setProgressValue: noop,
setChainProgress: noop,
};
const term = {
cols: 120,
rows: 32,
write: noop,
writeln: noop,
scrollToBottom: noop,
};
await createTerminalSessionStarters(ctx as never).startTelnet(term as never);
assert.ok(capturedOptions);
assert.equal(capturedOptions.username, "manual-host-user");
assert.equal(capturedOptions.password, undefined);
});
test("startTelnet rejects missing telnet identities before connecting", async () => {
let didConnect = false;
let error: string | null = null;
const terminalBackend = {
backendAvailable: () => true,
telnetAvailable: () => true,
moshAvailable: () => true,
localAvailable: () => true,
serialAvailable: () => true,
execAvailable: () => true,
startSSHSession: async () => "ssh-session",
startTelnetSession: async () => {
didConnect = true;
return "telnet-session";
},
startMoshSession: async () => "mosh-session",
startLocalSession: async () => "local-session",
startSerialSession: async () => "serial-session",
execCommand: async () => ({}),
onSessionData: () => noop,
onSessionExit: () => noop,
onChainProgress: () => noop,
writeToSession: noop,
resizeSession: noop,
};
const ctx = {
host: {
id: "host-1",
label: "Example",
hostname: "example.test",
username: "manual-host-user",
telnetIdentityId: "missing-telnet-identity",
telnetPort: 2323,
},
keys: [],
identities: [],
resolvedChainHosts: [],
sessionId: "session-1",
terminalSettings: {},
terminalBackend,
sessionRef: { current: null },
hasConnectedRef: { current: false },
hasRunStartupCommandRef: { current: false },
disposeDataRef: { current: null },
disposeExitRef: { current: null },
fitAddonRef: { current: null },
serializeAddonRef: { current: null },
pendingAuthRef: { current: null },
updateStatus: noop,
setStatus: noop,
setError: (message: string) => {
error = message;
},
setNeedsAuth: noop,
setAuthRetryMessage: noop,
setAuthPassword: noop,
setProgressLogs: noop,
setProgressValue: noop,
setChainProgress: noop,
};
const term = {
cols: 120,
rows: 32,
write: noop,
writeln: noop,
scrollToBottom: noop,
};
await createTerminalSessionStarters(ctx as never).startTelnet(term as never);
assert.equal(didConnect, false);
assert.match(error ?? "", /Telnet identity is missing/);
});
test("startTelnet rejects telnet identities without passwords before connecting", async () => {
let didConnect = false;
let error: string | null = null;
const terminalBackend = {
backendAvailable: () => true,
telnetAvailable: () => true,
moshAvailable: () => true,
localAvailable: () => true,
serialAvailable: () => true,
execAvailable: () => true,
startSSHSession: async () => "ssh-session",
startTelnetSession: async () => {
didConnect = true;
return "telnet-session";
},
startMoshSession: async () => "mosh-session",
startLocalSession: async () => "local-session",
startSerialSession: async () => "serial-session",
execCommand: async () => ({}),
onSessionData: () => noop,
onSessionExit: () => noop,
onChainProgress: () => noop,
writeToSession: noop,
resizeSession: noop,
};
const ctx = {
host: {
id: "host-1",
label: "Example",
hostname: "example.test",
username: "manual-host-user",
telnetIdentityId: "passwordless-telnet-identity",
telnetPort: 2323,
},
keys: [],
identities: [{
id: "passwordless-telnet-identity",
label: "Passwordless",
username: "telnet-user",
authMethod: "key",
created: 1,
}],
resolvedChainHosts: [],
sessionId: "session-1",
terminalSettings: {},
terminalBackend,
sessionRef: { current: null },
hasConnectedRef: { current: false },
hasRunStartupCommandRef: { current: false },
disposeDataRef: { current: null },
disposeExitRef: { current: null },
fitAddonRef: { current: null },
serializeAddonRef: { current: null },
pendingAuthRef: { current: null },
updateStatus: noop,
setStatus: noop,
setError: (message: string) => {
error = message;
},
setNeedsAuth: noop,
setAuthRetryMessage: noop,
setAuthPassword: noop,
setProgressLogs: noop,
setProgressValue: noop,
setChainProgress: noop,
};
const term = {
cols: 120,
rows: 32,
write: noop,
writeln: noop,
scrollToBottom: noop,
};
await createTerminalSessionStarters(ctx as never).startTelnet(term as never);
assert.equal(didConnect, false);
assert.match(error ?? "", /Telnet identity must include a username and password/);
});
test("startTelnet marks the session connected before the server sends output", async () => {
let status = "connecting";
let progressValue = 0;
const terminalBackend = {
backendAvailable: () => true,
telnetAvailable: () => true,
moshAvailable: () => true,
localAvailable: () => true,
serialAvailable: () => true,
execAvailable: () => true,
startSSHSession: async () => "ssh-session",
startTelnetSession: async () => "telnet-session",
startMoshSession: async () => "mosh-session",
startLocalSession: async () => "local-session",
startSerialSession: async () => "serial-session",
execCommand: async () => ({}),
onSessionData: () => noop,
onSessionExit: () => noop,
onChainProgress: () => noop,
writeToSession: noop,
resizeSession: noop,
};
const ctx = {
host: {
id: "host-1",
label: "Example",
hostname: "example.test",
telnetPort: 23,
},
keys: [],
resolvedChainHosts: [],
sessionId: "session-1",
terminalSettings: {},
terminalBackend,
sessionRef: { current: null },
hasConnectedRef: { current: false },
hasRunStartupCommandRef: { current: false },
disposeDataRef: { current: null },
disposeExitRef: { current: null },
fitAddonRef: { current: null },
serializeAddonRef: { current: null },
pendingAuthRef: { current: null },
updateStatus: (next: string) => { status = next; },
setStatus: noop,
setError: noop,
setNeedsAuth: noop,
setAuthRetryMessage: noop,
setAuthPassword: noop,
setProgressLogs: noop,
setProgressValue: (value: number) => { progressValue = value; },
setChainProgress: noop,
};
const term = {
cols: 120,
rows: 32,
write: noop,
writeln: noop,
scrollToBottom: noop,
};
await createTerminalSessionStarters(ctx as never).startTelnet(term as never);
assert.equal(status, "connected");
assert.equal(progressValue, 100);
assert.equal(ctx.sessionRef.current, "telnet-session");
});
test("startTelnet rejects unreadable saved telnet passwords before connecting", async () => {
let started = false;
let error = "";
let needsAuth = true;
let retryMessage: string | null = "previous";
let status = "";
const writes: string[] = [];
const terminalBackend = {
backendAvailable: () => true,
telnetAvailable: () => true,
moshAvailable: () => true,
localAvailable: () => true,
serialAvailable: () => true,
execAvailable: () => true,
startSSHSession: async () => "ssh-session",
startTelnetSession: async () => {
started = true;
return "telnet-session";
},
startMoshSession: async () => "mosh-session",
startLocalSession: async () => "local-session",
startSerialSession: async () => "serial-session",
execCommand: async () => ({}),
onSessionData: () => noop,
onSessionExit: () => noop,
onChainProgress: () => noop,
writeToSession: noop,
resizeSession: noop,
};
const ctx = {
host: {
id: "host-1",
label: "Example",
hostname: "example.test",
username: "ssh-user",
password: "ssh-password",
telnetUsername: "telnet-user",
telnetPassword: ENCRYPTED_CREDENTIAL_PLACEHOLDER,
telnetPort: 2323,
},
keys: [],
resolvedChainHosts: [],
sessionId: "session-1",
terminalSettings: {},
terminalBackend,
sessionRef: { current: null },
hasConnectedRef: { current: false },
hasRunStartupCommandRef: { current: false },
disposeDataRef: { current: null },
disposeExitRef: { current: null },
fitAddonRef: { current: null },
serializeAddonRef: { current: null },
pendingAuthRef: { current: null },
updateStatus: (next: string) => { status = next; },
setStatus: noop,
setError: (message: string) => { error = message; },
setNeedsAuth: (value: boolean) => { needsAuth = value; },
setAuthRetryMessage: (message: string | null) => { retryMessage = message; },
setAuthPassword: noop,
setProgressLogs: noop,
setProgressValue: noop,
setChainProgress: noop,
};
const term = {
cols: 120,
rows: 32,
write: (data: string) => { writes.push(data); },
writeln: (data: string) => { writes.push(data); },
scrollToBottom: noop,
};
await createTerminalSessionStarters(ctx as never).startTelnet(term as never);
assert.equal(started, false);
assert.equal(needsAuth, false);
assert.equal(retryMessage, null);
assert.equal(status, "disconnected");
assert.match(error, /Saved credentials cannot be decrypted/);
assert.match(writes.join("\n"), /Saved credentials cannot be decrypted/);
});
test("startTelnet waits for auto-login before running the startup command", async () => {
const writtenCommands: string[] = [];
const executedCommands: string[] = [];
let capturedOptions: Record<string, unknown> | null = null;
let autoLoginComplete: ((evt: { sessionId: string }) => void) | null = null;
let disposedAutoLoginCancelListener = false;
let resolveCommand: (() => void) | null = null;
const commandWritten = new Promise<void>((resolve) => {
resolveCommand = resolve;
});
const terminalBackend = {
backendAvailable: () => true,
telnetAvailable: () => true,
moshAvailable: () => true,
localAvailable: () => true,
serialAvailable: () => true,
execAvailable: () => true,
startSSHSession: async () => "ssh-session",
startTelnetSession: async (options: Record<string, unknown>) => {
capturedOptions = options;
return "telnet-session";
},
startMoshSession: async () => "mosh-session",
startLocalSession: async () => "local-session",
startSerialSession: async () => "serial-session",
execCommand: async () => ({}),
onSessionData: () => noop,
onSessionExit: () => noop,
onTelnetAutoLoginComplete: (sessionId: string, cb: (evt: { sessionId: string }) => void) => {
assert.equal(sessionId, "session-1");
autoLoginComplete = cb;
return noop;
},
onTelnetAutoLoginCancelled: () => () => {
disposedAutoLoginCancelListener = true;
},
onChainProgress: () => noop,
writeToSession: (_sessionId: string, data: string) => {
writtenCommands.push(data);
resolveCommand?.();
},
resizeSession: noop,
};
const ctx = {
host: {
id: "host-1",
label: "Example",
hostname: "example.test",
username: "ssh-user",
telnetUsername: "telnet-user",
telnetPassword: "",
telnetPort: 2323,
startupCommand: "show version",
},
keys: [],
resolvedChainHosts: [],
sessionId: "session-1",
terminalSettings: {},
terminalBackend,
sessionRef: { current: null },
hasConnectedRef: { current: false },
hasRunStartupCommandRef: { current: false },
disposeDataRef: { current: null },
disposeExitRef: { current: null },
fitAddonRef: { current: null },
serializeAddonRef: { current: null },
pendingAuthRef: { current: null },
updateStatus: noop,
setStatus: noop,
setError: noop,
setNeedsAuth: noop,
setAuthRetryMessage: noop,
setAuthPassword: noop,
setProgressLogs: noop,
setProgressValue: noop,
setChainProgress: noop,
onCommandExecuted: (command: string) => {
executedCommands.push(command);
},
};
const term = {
cols: 120,
rows: 32,
write: noop,
writeln: noop,
scrollToBottom: noop,
};
await createTerminalSessionStarters(ctx as never).startTelnet(term as never);
assert.ok(capturedOptions);
assert.ok(autoLoginComplete);
await new Promise((resolve) => setTimeout(resolve, 700));
assert.deepEqual(writtenCommands, []);
assert.deepEqual(executedCommands, []);
autoLoginComplete({ sessionId: "session-1" });
await Promise.race([
commandWritten,
new Promise((_, reject) => setTimeout(() => reject(new Error("Timed out waiting for startup command")), 1000)),
]);
assert.deepEqual(writtenCommands, ["show version\r"]);
assert.deepEqual(executedCommands, ["show version"]);
assert.equal(disposedAutoLoginCancelListener, true);
});
test("startTelnet keeps Telnet echo subscription after auto-login is cancelled", async () => {
let autoLoginCancelled: ((evt: { sessionId: string }) => void) | null = null;
let echoCallback: ((evt: { sessionId: string; remoteEcho: boolean; localEcho: boolean }) => void) | null = null;
let echoDisposals = 0;
const terminalBackend = {
backendAvailable: () => true,
telnetAvailable: () => true,
moshAvailable: () => true,
localAvailable: () => true,
serialAvailable: () => true,
execAvailable: () => true,
startSSHSession: async () => "ssh-session",
startTelnetSession: async () => "session-1",
startMoshSession: async () => "mosh-session",
startLocalSession: async () => "local-session",
startSerialSession: async () => "serial-session",
execCommand: async () => ({}),
onSessionData: () => noop,
onSessionExit: () => noop,
onTelnetAutoLoginComplete: () => noop,
onTelnetAutoLoginCancelled: (_sessionId: string, cb: (evt: { sessionId: string }) => void) => {
autoLoginCancelled = cb;
return noop;
},
onTelnetEchoMode: (_sessionId: string, cb: (evt: { sessionId: string; remoteEcho: boolean; localEcho: boolean }) => void) => {
echoCallback = cb;
return () => {
echoDisposals += 1;
};
},
onChainProgress: () => noop,
writeToSession: noop,
resizeSession: noop,
};
const telnetLocalEchoRef = { current: false };
const disposeTelnetEchoModeRef = { current: null as (() => void) | null };
const ctx = {
host: {
id: "host-1",
protocol: "telnet",
label: "Example",
hostname: "example.test",
telnetUsername: "admin",
startupCommand: "show version",
},
keys: [],
resolvedChainHosts: [],
sessionId: "session-1",
terminalSettings: {},
terminalBackend,
telnetLocalEchoRef,
disposeTelnetEchoModeRef,
sessionRef: { current: null },
hasConnectedRef: { current: false },
hasRunStartupCommandRef: { current: false },
disposeDataRef: { current: null },
disposeExitRef: { current: null },
fitAddonRef: { current: null },
serializeAddonRef: { current: null },
pendingAuthRef: { current: null },
updateStatus: noop,
setStatus: noop,
setError: noop,
setNeedsAuth: noop,
setAuthRetryMessage: noop,
setAuthPassword: noop,
setProgressLogs: noop,
setProgressValue: noop,
setChainProgress: noop,
};
const term = {
cols: 120,
rows: 32,
write: noop,
writeln: noop,
scrollToBottom: noop,
};
await createTerminalSessionStarters(ctx as never).startTelnet(term as never);
assert.ok(autoLoginCancelled);
assert.equal(typeof disposeTelnetEchoModeRef.current, "function");
autoLoginCancelled({ sessionId: "session-1" });
assert.equal(echoDisposals, 0);
echoCallback?.({ sessionId: "session-1", remoteEcho: false, localEcho: true });
assert.equal(telnetLocalEchoRef.current, true);
});
test("startTelnet ignores stale replacement failures", async () => {
let error: string | null = null;
const statuses: string[] = [];
const terminalBackend = {
backendAvailable: () => true,
telnetAvailable: () => true,
moshAvailable: () => true,
localAvailable: () => true,
serialAvailable: () => true,
execAvailable: () => true,
startSSHSession: async () => "ssh-session",
startTelnetSession: async () => {
throw new Error("Error invoking remote method 'netcatty:telnet:start': Error: Telnet session start was replaced");
},
startMoshSession: async () => "mosh-session",
startLocalSession: async () => "local-session",
startSerialSession: async () => "serial-session",
execCommand: async () => ({}),
onSessionData: () => noop,
onSessionExit: () => noop,
onTelnetEchoMode: () => noop,
onChainProgress: () => noop,
writeToSession: noop,
resizeSession: noop,
};
const ctx = {
host: {
id: "host-1",
protocol: "telnet",
label: "Example",
hostname: "example.test",
},
keys: [],
resolvedChainHosts: [],
sessionId: "session-1",
terminalSettings: {},
terminalBackend,
telnetLocalEchoRef: { current: false },
disposeTelnetEchoModeRef: { current: null },
sessionRef: { current: null },
hasConnectedRef: { current: false },
hasRunStartupCommandRef: { current: false },
disposeDataRef: { current: null },
disposeExitRef: { current: null },
fitAddonRef: { current: null },
serializeAddonRef: { current: null },
pendingAuthRef: { current: null },
updateStatus: (next: string) => { statuses.push(next); },
setStatus: noop,
setError: (message: string | null) => { error = message; },
setNeedsAuth: noop,
setAuthRetryMessage: noop,
setAuthPassword: noop,
setProgressLogs: noop,
setProgressValue: noop,
setChainProgress: noop,
};
const term = {
cols: 120,
rows: 32,
write: noop,
writeln: noop,
scrollToBottom: noop,
};
await createTerminalSessionStarters(ctx as never).startTelnet(term as never);
assert.equal(error, null);
assert.deepEqual(statuses, []);
});
test("startTelnet runs a multi-line startup command in sequence when requested", async () => {
const writtenCommands: string[] = [];
const executedCommands: string[] = [];
let capturedOptions: Record<string, unknown> | null = null;
let autoLoginComplete: ((evt: { sessionId: string }) => void) | null = null;
let disposedAutoLoginCancelListener = false;
const terminalBackend = {
backendAvailable: () => true,
telnetAvailable: () => true,
moshAvailable: () => true,
localAvailable: () => true,
serialAvailable: () => true,
execAvailable: () => true,
startSSHSession: async () => "ssh-session",
startTelnetSession: async (options: Record<string, unknown>) => {
capturedOptions = options;
return "telnet-session";
},
startMoshSession: async () => "mosh-session",
startLocalSession: async () => "local-session",
startSerialSession: async () => "serial-session",
execCommand: async () => ({}),
onSessionData: () => noop,
onSessionExit: () => noop,
onTelnetAutoLoginComplete: (sessionId: string, cb: (evt: { sessionId: string }) => void) => {
assert.equal(sessionId, "session-1");
autoLoginComplete = cb;
return noop;
},
onTelnetAutoLoginCancelled: () => () => {
disposedAutoLoginCancelListener = true;
},
onChainProgress: () => noop,
writeToSession: (_sessionId: string, data: string) => {
writtenCommands.push(data);
},
resizeSession: noop,
};
const ctx = {
host: {
id: "host-1",
label: "Example",
hostname: "example.test",
username: "ssh-user",
telnetUsername: "telnet-user",
telnetPassword: "",
telnetPort: 2323,
startupCommand: "first cmd\nsecond cmd",
startupCommandRunMode: "lineDelay",
},
keys: [],
resolvedChainHosts: [],
sessionId: "session-1",
terminalSettings: { startupCommandDelayMs: 20 },
multiLineRunMode: "lineDelay",
terminalBackend,
sessionRef: { current: null },
hasConnectedRef: { current: false },
hasRunStartupCommandRef: { current: false },
disposeDataRef: { current: null },
disposeExitRef: { current: null },
fitAddonRef: { current: null },
serializeAddonRef: { current: null },
pendingAuthRef: { current: null },
updateStatus: noop,
setStatus: noop,
setError: noop,
setNeedsAuth: noop,
setAuthRetryMessage: noop,
setAuthPassword: noop,
setProgressLogs: noop,
setProgressValue: noop,
setChainProgress: noop,
onCommandExecuted: (command: string) => {
executedCommands.push(command);
},
};
const term = {
cols: 120,
rows: 32,
write: noop,
writeln: noop,
scrollToBottom: noop,
};
await createTerminalSessionStarters(ctx as never).startTelnet(term as never);
assert.ok(capturedOptions);
assert.ok(autoLoginComplete);
await new Promise((resolve) => setTimeout(resolve, 700));
assert.deepEqual(writtenCommands, []);
assert.deepEqual(executedCommands, []);
autoLoginComplete({ sessionId: "session-1" });
// Wait long enough for both lines (delay before first + delay between).
await new Promise((resolve) => setTimeout(resolve, 200));
assert.deepEqual(writtenCommands, ["first cmd\r", "second cmd\r"]);
assert.deepEqual(executedCommands, ["first cmd", "second cmd"]);
assert.equal(disposedAutoLoginCancelListener, true);
});
test("startTelnet cancels pending startup command when user takes over", async () => {
const writtenCommands: string[] = [];
let capturedOptions: Record<string, unknown> | null = null;
let autoLoginComplete: ((evt: { sessionId: string }) => void) | null = null;
let autoLoginCancelled: ((evt: { sessionId: string }) => void) | null = null;
const terminalBackend = {
backendAvailable: () => true,
telnetAvailable: () => true,
moshAvailable: () => true,
localAvailable: () => true,
serialAvailable: () => true,
execAvailable: () => true,
startSSHSession: async () => "ssh-session",
startTelnetSession: async (options: Record<string, unknown>) => {
capturedOptions = options;
return "telnet-session";
},
startMoshSession: async () => "mosh-session",
startLocalSession: async () => "local-session",
startSerialSession: async () => "serial-session",
execCommand: async () => ({}),
onSessionData: () => noop,
onSessionExit: () => noop,
onTelnetAutoLoginComplete: (_sessionId: string, cb: (evt: { sessionId: string }) => void) => {
autoLoginComplete = cb;
return noop;
},
onTelnetAutoLoginCancelled: (_sessionId: string, cb: (evt: { sessionId: string }) => void) => {
autoLoginCancelled = cb;
return noop;
},
onChainProgress: () => noop,
writeToSession: (_sessionId: string, data: string) => {
writtenCommands.push(data);
},
resizeSession: noop,
};
const ctx = {
host: {
id: "host-1",
label: "Example",
hostname: "example.test",
telnetUsername: "telnet-user",
telnetPassword: "secret",
startupCommand: "show version",
},
keys: [],
resolvedChainHosts: [],
sessionId: "session-1",
terminalSettings: {},
terminalBackend,
sessionRef: { current: null },
hasConnectedRef: { current: false },
hasRunStartupCommandRef: { current: false },
disposeDataRef: { current: null },
disposeExitRef: { current: null },
fitAddonRef: { current: null },
serializeAddonRef: { current: null },
pendingAuthRef: { current: null },
updateStatus: noop,
setStatus: noop,
setError: noop,
setNeedsAuth: noop,
setAuthRetryMessage: noop,
setAuthPassword: noop,
setProgressLogs: noop,
setProgressValue: noop,
setChainProgress: noop,
};
const term = {
cols: 120,
rows: 32,
write: noop,
writeln: noop,
scrollToBottom: noop,
};
await createTerminalSessionStarters(ctx as never).startTelnet(term as never);
assert.ok(capturedOptions);
assert.ok(autoLoginComplete);
assert.ok(autoLoginCancelled);
autoLoginComplete({ sessionId: "session-1" });
autoLoginCancelled({ sessionId: "session-1" });
await new Promise((resolve) => setTimeout(resolve, 700));
assert.deepEqual(writtenCommands, []);
});
test("startTelnet does not run startup command if auto-login never completes", async () => {
const writtenCommands: string[] = [];
const executedCommands: string[] = [];
let capturedOptions: Record<string, unknown> | null = null;
let autoLoginComplete: ((evt: { sessionId: string }) => void) | null = null;
let sessionExit: ((evt: { reason?: "closed" }) => void) | null = null;
let disposedAutoLoginListener = false;
const terminalBackend = {
backendAvailable: () => true,
telnetAvailable: () => true,
moshAvailable: () => true,
localAvailable: () => true,
serialAvailable: () => true,
execAvailable: () => true,
startSSHSession: async () => "ssh-session",
startTelnetSession: async (options: Record<string, unknown>) => {
capturedOptions = options;
return "telnet-session";
},
startMoshSession: async () => "mosh-session",
startLocalSession: async () => "local-session",
startSerialSession: async () => "serial-session",
execCommand: async () => ({}),
onSessionData: () => noop,
onSessionExit: (_sessionId: string, cb: (evt: { reason?: "closed" }) => void) => {
sessionExit = cb;
return noop;
},
onTelnetAutoLoginComplete: (_sessionId: string, cb: (evt: { sessionId: string }) => void) => {
autoLoginComplete = cb;
return () => {
disposedAutoLoginListener = true;
};
},
onChainProgress: () => noop,
writeToSession: (_sessionId: string, data: string) => {
writtenCommands.push(data);
},
resizeSession: noop,
};
const ctx = {
host: {
id: "host-1",
label: "Example",
hostname: "example.test",
username: "ssh-user",
telnetUsername: "telnet-user",
telnetPassword: "",
telnetPort: 2323,
startupCommand: "show version",
},
keys: [],
resolvedChainHosts: [],
sessionId: "session-1",
terminalSettings: {},
terminalBackend,
sessionRef: { current: null },
hasConnectedRef: { current: false },
hasRunStartupCommandRef: { current: false },
disposeDataRef: { current: null },
disposeExitRef: { current: null },
fitAddonRef: { current: null },
serializeAddonRef: { current: null },
pendingAuthRef: { current: null },
updateStatus: noop,
setStatus: noop,
setError: noop,
setNeedsAuth: noop,
setAuthRetryMessage: noop,
setAuthPassword: noop,
setProgressLogs: noop,
setProgressValue: noop,
setChainProgress: noop,
onCommandExecuted: (command: string) => {
executedCommands.push(command);
},
};
const term = {
cols: 120,
rows: 32,
write: noop,
writeln: noop,
scrollToBottom: noop,
};
await createTerminalSessionStarters(ctx as never).startTelnet(term as never);
assert.ok(capturedOptions);
assert.ok(autoLoginComplete);
await new Promise((resolve) => setTimeout(resolve, 700));
assert.deepEqual(writtenCommands, []);
assert.deepEqual(executedCommands, []);
assert.ok(sessionExit);
sessionExit({ reason: "closed" });
assert.equal(disposedAutoLoginListener, true);
});
test("startTelnet does not run startup command during manual login", async () => {
const writtenCommands: string[] = [];
let capturedOptions: Record<string, unknown> | null = null;
const terminalBackend = {
backendAvailable: () => true,
telnetAvailable: () => true,
moshAvailable: () => true,
localAvailable: () => true,
serialAvailable: () => true,
execAvailable: () => true,
startSSHSession: async () => "ssh-session",
startTelnetSession: async (options: Record<string, unknown>) => {
capturedOptions = options;
return "telnet-session";
},
startMoshSession: async () => "mosh-session",
startLocalSession: async () => "local-session",
startSerialSession: async () => "serial-session",
execCommand: async () => ({}),
onSessionData: () => noop,
onSessionExit: () => noop,
onChainProgress: () => noop,
writeToSession: (_sessionId: string, data: string) => {
writtenCommands.push(data);
},
resizeSession: noop,
};
const ctx = {
host: {
id: "host-1",
label: "Example",
hostname: "example.test",
username: undefined,
password: undefined,
telnetUsername: undefined,
telnetPassword: undefined,
port: 2222,
startupCommand: "show version",
},
keys: [],
resolvedChainHosts: [],
sessionId: "session-1",
terminalSettings: {},
terminalBackend,
sessionRef: { current: null },
hasConnectedRef: { current: false },
hasRunStartupCommandRef: { current: false },
disposeDataRef: { current: null },
disposeExitRef: { current: null },
fitAddonRef: { current: null },
serializeAddonRef: { current: null },
pendingAuthRef: { current: null },
updateStatus: noop,
setStatus: noop,
setError: noop,
setNeedsAuth: noop,
setAuthRetryMessage: noop,
setAuthPassword: noop,
setProgressLogs: noop,
setProgressValue: noop,
setChainProgress: noop,
};
const term = {
cols: 120,
rows: 32,
write: noop,
writeln: noop,
scrollToBottom: noop,
};
await createTerminalSessionStarters(ctx as never).startTelnet(term as never);
await new Promise((resolve) => setTimeout(resolve, 700));
assert.ok(capturedOptions);
assert.equal(capturedOptions.port, 23);
assert.deepEqual(writtenCommands, []);
});
test("startTelnet rejects configured proxies instead of connecting directly", async () => {
let started = false;
let error = "";
const terminalBackend = {
backendAvailable: () => true,
telnetAvailable: () => true,
moshAvailable: () => true,
localAvailable: () => true,
serialAvailable: () => true,
execAvailable: () => true,
startSSHSession: async () => "ssh-session",
startTelnetSession: async () => {
started = true;
return "telnet-session";
},
startMoshSession: async () => "mosh-session",
startLocalSession: async () => "local-session",
startSerialSession: async () => "serial-session",
execCommand: async () => ({}),
onSessionData: () => noop,
onSessionExit: () => noop,
onChainProgress: () => noop,
writeToSession: noop,
resizeSession: noop,
};
const ctx = {
host: {
id: "host-1",
label: "Example",
hostname: "example.test",
username: "alice",
telnetPort: 2323,
proxyProfileId: "proxy-1",
proxyConfig: { type: "http", host: "proxy.example.com", port: 3128 },
},
keys: [],
resolvedChainHosts: [],
sessionId: "session-1",
terminalSettings: {},
terminalBackend,
sessionRef: { current: null },
hasConnectedRef: { current: false },
hasRunStartupCommandRef: { current: false },
disposeDataRef: { current: null },
disposeExitRef: { current: null },
fitAddonRef: { current: null },
serializeAddonRef: { current: null },
pendingAuthRef: { current: null },
updateStatus: noop,
setStatus: noop,
setError: (message: string) => { error = message; },
setNeedsAuth: noop,
setAuthRetryMessage: noop,
setAuthPassword: noop,
setProgressLogs: noop,
setProgressValue: noop,
setChainProgress: noop,
};
const term = {
cols: 120,
rows: 32,
write: noop,
writeln: noop,
scrollToBottom: noop,
};
await createTerminalSessionStarters(ctx as never).startTelnet(term as never);
assert.equal(started, false);
assert.match(error, /Telnet does not support proxy/);
});
test("reattachSession refreshes Telnet echo-mode subscription", () => {
let echoCallback: ((evt: { sessionId: string; remoteEcho: boolean; localEcho: boolean }) => void) | null = null;
let echoDisposals = 0;
const echoSessionIds: string[] = [];
const terminalBackend = {
onSessionData: () => noop,
onSessionExit: () => noop,
onTelnetEchoMode: (sessionId: string, cb: (evt: { sessionId: string; remoteEcho: boolean; localEcho: boolean }) => void) => {
echoSessionIds.push(sessionId);
echoCallback = cb;
return () => {
echoDisposals += 1;
};
},
writeToSession: noop,
resizeSession: noop,
};
const telnetLocalEchoRef = { current: false };
const disposeTelnetEchoModeRef = { current: null as (() => void) | null };
const ctx = {
host: {
id: "host-1",
protocol: "telnet",
label: "Example",
hostname: "example.test",
},
keys: [],
resolvedChainHosts: [],
sessionId: "session-1",
terminalSettings: {},
terminalBackend,
telnetLocalEchoRef,
disposeTelnetEchoModeRef,
sessionRef: { current: "telnet-session" },
hasConnectedRef: { current: false },
hasRunStartupCommandRef: { current: false },
disposeDataRef: { current: null },
disposeExitRef: { current: null },
fitAddonRef: { current: null },
serializeAddonRef: { current: null },
pendingAuthRef: { current: null },
updateStatus: noop,
setStatus: noop,
setError: noop,
setNeedsAuth: noop,
setAuthRetryMessage: noop,
setAuthPassword: noop,
setProgressLogs: noop,
setProgressValue: noop,
setChainProgress: noop,
};
const term = {
cols: 120,
rows: 32,
write: noop,
writeln: noop,
scrollToBottom: noop,
};
const starters = createTerminalSessionStarters(ctx as never);
assert.equal(starters.reattachSession(term as never), true);
assert.deepEqual(echoSessionIds, ["telnet-session"]);
assert.equal(typeof disposeTelnetEchoModeRef.current, "function");
echoCallback?.({ sessionId: "telnet-session", remoteEcho: false, localEcho: true });
assert.equal(telnetLocalEchoRef.current, true);
assert.equal(starters.reattachSession(term as never), true);
assert.deepEqual(echoSessionIds, ["telnet-session", "telnet-session"]);
assert.equal(echoDisposals, 1);
assert.equal(telnetLocalEchoRef.current, true);
echoCallback?.({ sessionId: "telnet-session", remoteEcho: true, localEcho: false });
assert.equal(telnetLocalEchoRef.current, false);
});
test("splitStartupCommandLines drops blank lines but keeps content verbatim", () => {
assert.deepEqual(splitStartupCommandLines("sudo -i\nalias dc=\"docker compose\""), [
"sudo -i",
'alias dc="docker compose"',
]);
// Single-line content is preserved verbatim (leading/trailing spaces kept).
assert.deepEqual(splitStartupCommandLines(" cd /app "), [" cd /app "]);
assert.deepEqual(splitStartupCommandLines("a\n\n \nb"), ["a", "b"]);
assert.deepEqual(splitStartupCommandLines("echo hi\r\nwhoami"), ["echo hi", "whoami"]);
assert.deepEqual(splitStartupCommandLines(""), []);
assert.deepEqual(splitStartupCommandLines(" "), []);
});
test("normalizeStartupCommandDelay defaults and clamps", () => {
assert.equal(normalizeStartupCommandDelay(undefined), 600);
assert.equal(normalizeStartupCommandDelay(Number.NaN), 600);
assert.equal(normalizeStartupCommandDelay(0), 0);
assert.equal(normalizeStartupCommandDelay(1500), 1500);
assert.equal(normalizeStartupCommandDelay(-50), 0);
assert.equal(normalizeStartupCommandDelay(999999), 10000);
});