Files
NetMesh/electron/deepLink.test.cjs
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

517 lines
16 KiB
JavaScript

const test = require("node:test");
const assert = require("node:assert/strict");
const {
applyInitialJmsDeepLinkPreference,
applyInitialSshDeepLinkPreference,
applyJmsProtocolClientPreference,
applySshProtocolClientPreference,
getSshDeepLinkRendererReadyTimeoutMs,
collectJmsDeepLinkUrls,
collectPuttyStyleDeepLinkUrls,
collectSshDeepLinkQueueItems,
collectSshDeepLinkUrls,
collectTelnetDeepLinkUrls,
isJmsDeepLinkUrl,
isSshDeepLinkUrl,
isTelnetDeepLinkUrl,
readJmsDeepLinkEnabledPreference,
readSshDeepLinkEnabledPreference,
shouldDeliverJmsDeepLink,
shouldDeliverSshDeepLink,
shouldRequeueFailedSshDeepLinkDelivery,
updateJmsDeepLinkEnabledPreference,
updateSshDeepLinkEnabledPreference,
writeJmsDeepLinkEnabledPreference,
writeSshDeepLinkEnabledPreference,
} = require("./deepLink.cjs");
test("isSshDeepLinkUrl accepts only ssh URLs", () => {
assert.equal(isSshDeepLinkUrl("ssh://alice@example.com:2200"), true);
assert.equal(isSshDeepLinkUrl("SSH://alice@example.com"), true);
assert.equal(isSshDeepLinkUrl("ssh://example.com:99999"), true);
assert.equal(isSshDeepLinkUrl("https://example.com"), false);
assert.equal(isSshDeepLinkUrl("--flag"), false);
});
test("collectSshDeepLinkUrls extracts ssh URLs from process arguments", () => {
assert.deepEqual(
collectSshDeepLinkUrls([
"/Applications/Netcatty.app/Contents/MacOS/Netcatty",
"--flag",
"ssh://alice@example.com",
"file:///tmp/example",
"ssh://bob@example.net:2222",
]),
["ssh://alice@example.com", "ssh://bob@example.net:2222"],
);
});
test("collectPuttyStyleDeepLinkUrls converts PuTTY argv when no ssh:// token is present", () => {
assert.deepEqual(
collectPuttyStyleDeepLinkUrls([
String.raw`C:\Program Files\Netcatty\Netcatty.exe`,
"-ssh",
"alice@10.0.0.8",
"-P",
"2222",
"-pw",
"s3cret",
]),
{ ssh: ["ssh://alice:s3cret@10.0.0.8:2222"], telnet: [] },
);
});
test("collectPuttyStyleDeepLinkUrls leaves ssh:// tokens to the existing collector", () => {
assert.deepEqual(
collectPuttyStyleDeepLinkUrls([
"Netcatty.exe",
"-url",
"ssh://alice@example.com",
"-ssh",
"ignored@host",
]),
{ ssh: [], telnet: [] },
);
});
test("collectSshDeepLinkQueueItems keeps PuTTY CLI launches when scheme URLs are disabled", () => {
// Warm second-instance case: the running app forwarded another launch's
// argv while the ssh:// protocol-client preference is disabled.
assert.deepEqual(
collectSshDeepLinkQueueItems([
String.raw`C:\Program Files\Netcatty\Netcatty.exe`,
"-ssh",
"alice@10.0.0.8",
"-P",
"2222",
"-pw",
"s3cret",
], { includeSchemeUrls: false }),
{ ssh: [{ rawUrl: "ssh://alice:s3cret@10.0.0.8:2222", viaCommandLine: true }], telnet: [] },
);
});
test("collectSshDeepLinkQueueItems keeps scheme URL gating separate from CLI launches", () => {
const queueItems = collectSshDeepLinkQueueItems(
["/Applications/Netcatty.app/Contents/MacOS/Netcatty", "ssh://alice@example.com"],
{ includeSchemeUrls: true },
);
assert.deepEqual(queueItems.ssh, [{ rawUrl: "ssh://alice@example.com", viaCommandLine: false }]);
assert.deepEqual(queueItems.telnet, []);
const disabledQueueItems = collectSshDeepLinkQueueItems(
["/Applications/Netcatty.app/Contents/MacOS/Netcatty", "ssh://alice@example.com"],
{ includeSchemeUrls: false },
);
assert.deepEqual(disabledQueueItems.ssh, []);
assert.deepEqual(disabledQueueItems.telnet, []);
});
test("applySshProtocolClientPreference registers or removes ssh and telnet handlers", () => {
const calls = [];
const app = {
setAsDefaultProtocolClient: (...args) => {
calls.push(["set", ...args]);
return true;
},
removeAsDefaultProtocolClient: (...args) => {
calls.push(["remove", ...args]);
return true;
},
};
assert.equal(applySshProtocolClientPreference({ app, enabled: true, isDev: false }), true);
assert.equal(applySshProtocolClientPreference({ app, enabled: false, isDev: false }), true);
assert.deepEqual(calls, [
["set", "ssh"],
["set", "telnet"],
["remove", "ssh"],
["remove", "telnet"],
]);
});
test("applySshProtocolClientPreference keeps ssh successful when telnet registration fails", () => {
const calls = [];
const app = {
setAsDefaultProtocolClient: (protocol) => {
calls.push(["set", protocol]);
return protocol === "ssh";
},
removeAsDefaultProtocolClient: (protocol) => {
calls.push(["remove", protocol]);
return protocol === "ssh";
},
};
assert.equal(applySshProtocolClientPreference({ app, enabled: true, isDev: false }), true);
assert.equal(applySshProtocolClientPreference({ app, enabled: false, isDev: false }), true);
assert.deepEqual(calls, [
["set", "ssh"],
["set", "telnet"],
["remove", "ssh"],
["remove", "telnet"],
]);
});
test("applySshProtocolClientPreference rolls back telnet registration when ssh enable fails", () => {
const calls = [];
const app = {
setAsDefaultProtocolClient: (protocol) => {
calls.push(["set", protocol]);
return protocol === "telnet";
},
removeAsDefaultProtocolClient: (protocol) => {
calls.push(["remove", protocol]);
return true;
},
};
assert.equal(applySshProtocolClientPreference({ app, enabled: true, isDev: false }), false);
assert.deepEqual(calls, [
["set", "ssh"],
["set", "telnet"],
["remove", "telnet"],
]);
});
test("applySshProtocolClientPreference restores telnet registration when ssh disable fails", () => {
const calls = [];
const app = {
setAsDefaultProtocolClient: (protocol) => {
calls.push(["set", protocol]);
return true;
},
removeAsDefaultProtocolClient: (protocol) => {
calls.push(["remove", protocol]);
return protocol === "telnet";
},
};
assert.equal(applySshProtocolClientPreference({ app, enabled: false, isDev: false }), false);
assert.deepEqual(calls, [
["remove", "ssh"],
["remove", "telnet"],
["set", "telnet"],
]);
});
test("isTelnetDeepLinkUrl accepts only telnet URLs", () => {
assert.equal(isTelnetDeepLinkUrl("telnet://example.com:2001"), true);
assert.equal(isTelnetDeepLinkUrl("TELNET://example.com:2001"), true);
assert.equal(isTelnetDeepLinkUrl("ssh://alice@example.com"), false);
assert.equal(isTelnetDeepLinkUrl("--flag"), false);
});
test("collectTelnetDeepLinkUrls extracts telnet URLs from process arguments", () => {
assert.deepEqual(
collectTelnetDeepLinkUrls([
"/Applications/Netcatty.app/Contents/MacOS/Netcatty",
"--flag",
"telnet://example.com:2001",
"file:///tmp/example",
"telnet://router.local:23",
]),
["telnet://example.com:2001", "telnet://router.local:23"],
);
});
test("ssh deep link enabled preference persists outside renderer localStorage", () => {
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "netcatty-deeplink-"));
const app = { getPath: () => userDataDir };
assert.equal(readSshDeepLinkEnabledPreference({ app }), true);
assert.equal(writeSshDeepLinkEnabledPreference({ app, enabled: false }), true);
assert.equal(readSshDeepLinkEnabledPreference({ app }), false);
assert.equal(writeSshDeepLinkEnabledPreference({ app, enabled: true }), true);
assert.equal(readSshDeepLinkEnabledPreference({ app }), true);
});
test("updateSshDeepLinkEnabledPreference keeps the previous state when the system change fails", () => {
const writes = [];
const result = updateSshDeepLinkEnabledPreference({
currentEnabled: true,
enabled: false,
applyPreference: () => false,
writePreference: (enabled) => {
writes.push(enabled);
return true;
},
});
assert.deepEqual(result, { enabled: true, success: false });
assert.deepEqual(writes, []);
});
test("updateSshDeepLinkEnabledPreference clears queued links after disabling succeeds", () => {
const writes = [];
let cleared = false;
const result = updateSshDeepLinkEnabledPreference({
currentEnabled: true,
enabled: false,
applyPreference: () => true,
writePreference: (enabled) => {
writes.push(enabled);
return true;
},
clearPending: () => {
cleared = true;
},
});
assert.deepEqual(result, { enabled: false, success: true });
assert.deepEqual(writes, [false]);
assert.equal(cleared, true);
});
test("updateSshDeepLinkEnabledPreference rolls back when saving the setting fails", () => {
const applied = [];
const result = updateSshDeepLinkEnabledPreference({
currentEnabled: true,
enabled: false,
applyPreference: (enabled) => {
applied.push(enabled);
return true;
},
writePreference: () => false,
});
assert.deepEqual(result, { enabled: true, success: false });
assert.deepEqual(applied, [false, true]);
});
test("updateSshDeepLinkEnabledPreference clears links when save and rollback both fail disabled", () => {
const applied = [];
let cleared = false;
const result = updateSshDeepLinkEnabledPreference({
currentEnabled: true,
enabled: false,
applyPreference: (enabled) => {
applied.push(enabled);
return enabled === false;
},
writePreference: () => false,
clearPending: () => {
cleared = true;
},
});
assert.deepEqual(result, { enabled: false, success: false });
assert.deepEqual(applied, [false, true]);
assert.equal(cleared, true);
});
test("shouldDeliverSshDeepLink drops stale deliveries after the setting changes", () => {
assert.equal(shouldDeliverSshDeepLink({
enabled: true,
deliveryGeneration: 1,
expectedGeneration: 1,
}), true);
assert.equal(shouldDeliverSshDeepLink({
enabled: false,
deliveryGeneration: 1,
expectedGeneration: 1,
}), false);
assert.equal(shouldDeliverSshDeepLink({
enabled: true,
deliveryGeneration: 2,
expectedGeneration: 1,
}), false);
});
test("getSshDeepLinkRendererReadyTimeoutMs waits indefinitely so slow unlock keeps links", () => {
assert.equal(getSshDeepLinkRendererReadyTimeoutMs({ isDev: false }), 0);
assert.equal(getSshDeepLinkRendererReadyTimeoutMs({ isDev: true }), 0);
});
test("shouldRequeueFailedSshDeepLinkDelivery keeps valid links after readiness failures", () => {
assert.equal(shouldRequeueFailedSshDeepLinkDelivery({
enabled: true,
deliveryGeneration: 1,
expectedGeneration: 1,
result: { success: false, reason: "Renderer did not report ready before timeout." },
}), true);
assert.equal(shouldRequeueFailedSshDeepLinkDelivery({
enabled: true,
deliveryGeneration: 1,
expectedGeneration: 1,
result: { success: false, reason: "ssh-deep-link-disabled" },
cancelReason: "ssh-deep-link-disabled",
}), false);
assert.equal(shouldRequeueFailedSshDeepLinkDelivery({
enabled: true,
deliveryGeneration: 1,
expectedGeneration: 1,
result: { success: true },
}), false);
assert.equal(shouldRequeueFailedSshDeepLinkDelivery({
enabled: false,
deliveryGeneration: 1,
expectedGeneration: 1,
result: { success: false, reason: "window closed" },
}), false);
});
test("applyInitialSshDeepLinkPreference disables handling when startup registration fails", () => {
let cleared = false;
const warnings = [];
const result = applyInitialSshDeepLinkPreference({
enabled: true,
applyPreference: () => false,
clearPending: () => {
cleared = true;
},
logWarn: (message) => warnings.push(message),
});
assert.deepEqual(result, { enabled: false, success: false });
assert.equal(cleared, true);
assert.equal(warnings.length, 1);
});
test("isJmsDeepLinkUrl accepts only jms URLs", () => {
assert.equal(isJmsDeepLinkUrl("jms://payload"), true);
assert.equal(isJmsDeepLinkUrl("JMS://payload"), true);
assert.equal(isJmsDeepLinkUrl("ssh://alice@example.com"), false);
assert.equal(isJmsDeepLinkUrl("--flag"), false);
});
test("collectJmsDeepLinkUrls extracts jms URLs from process arguments", () => {
assert.deepEqual(
collectJmsDeepLinkUrls([
"/Applications/Netcatty.app/Contents/MacOS/Netcatty",
"--flag",
"jms://payload-one",
"file:///tmp/example",
"jms://payload-two",
]),
["jms://payload-one", "jms://payload-two"],
);
});
test("applyJmsProtocolClientPreference registers or removes the jms handler", () => {
const calls = [];
const app = {
setAsDefaultProtocolClient: (...args) => {
calls.push(["set", ...args]);
return true;
},
removeAsDefaultProtocolClient: (...args) => {
calls.push(["remove", ...args]);
return true;
},
};
assert.equal(applyJmsProtocolClientPreference({ app, enabled: true, isDev: false }), true);
assert.equal(applyJmsProtocolClientPreference({ app, enabled: false, isDev: false }), true);
assert.deepEqual(calls, [
["set", "jms"],
["remove", "jms"],
]);
});
test("jms deep link enabled preference defaults to disabled", () => {
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "netcatty-jms-deeplink-"));
const app = { getPath: () => userDataDir };
assert.equal(readJmsDeepLinkEnabledPreference({ app }), false);
assert.equal(writeJmsDeepLinkEnabledPreference({ app, enabled: true }), true);
assert.equal(readJmsDeepLinkEnabledPreference({ app }), true);
assert.equal(writeJmsDeepLinkEnabledPreference({ app, enabled: false }), true);
assert.equal(readJmsDeepLinkEnabledPreference({ app }), false);
});
test("updateJmsDeepLinkEnabledPreference keeps the previous state when the system change fails", () => {
const writes = [];
const result = updateJmsDeepLinkEnabledPreference({
currentEnabled: false,
enabled: true,
applyPreference: () => false,
writePreference: (enabled) => {
writes.push(enabled);
return true;
},
});
assert.deepEqual(result, { enabled: false, success: false });
assert.deepEqual(writes, []);
});
test("updateJmsDeepLinkEnabledPreference clears queued links after disabling succeeds", () => {
const writes = [];
let cleared = false;
const result = updateJmsDeepLinkEnabledPreference({
currentEnabled: true,
enabled: false,
applyPreference: () => true,
writePreference: (enabled) => {
writes.push(enabled);
return true;
},
clearPending: () => {
cleared = true;
},
});
assert.deepEqual(result, { enabled: false, success: true });
assert.deepEqual(writes, [false]);
assert.equal(cleared, true);
});
test("updateJmsDeepLinkEnabledPreference rolls back when saving the setting fails", () => {
const applied = [];
const result = updateJmsDeepLinkEnabledPreference({
currentEnabled: false,
enabled: true,
applyPreference: (enabled) => {
applied.push(enabled);
return true;
},
writePreference: () => false,
});
assert.deepEqual(result, { enabled: false, success: false });
assert.deepEqual(applied, [true, false]);
});
test("shouldDeliverJmsDeepLink drops stale deliveries after the setting changes", () => {
assert.equal(shouldDeliverJmsDeepLink({
enabled: true,
deliveryGeneration: 1,
expectedGeneration: 1,
}), true);
assert.equal(shouldDeliverJmsDeepLink({
enabled: false,
deliveryGeneration: 1,
expectedGeneration: 1,
}), false);
assert.equal(shouldDeliverJmsDeepLink({
enabled: true,
deliveryGeneration: 2,
expectedGeneration: 1,
}), false);
});
test("applyInitialJmsDeepLinkPreference does not warn when disabled startup removal fails", () => {
let cleared = false;
const warnings = [];
const result = applyInitialJmsDeepLinkPreference({
enabled: false,
applyPreference: () => false,
clearPending: () => {
cleared = true;
},
logWarn: (message) => warnings.push(message),
});
assert.deepEqual(result, { enabled: false, success: false });
assert.equal(cleared, false);
assert.equal(warnings.length, 0);
});