CH-J Server Managerszerverkezelés SSH felett
Menü
Közzétett forrás

CH-J Server Manager

Könyvtárak és fájlok böngészése egy adott alkalmazás kiadásához.

Letöltés forrás ZIP
CH-J / Server Manager 377 fájlok
core-20261019T111616Z-160a00bfcddc Hozzáadva: · Beépített:
latencyMonitor.test.js Hozzáadva: Nem rögzített dátum 10,6 KB · 152 sorok
Kód Fájl letöltése
1"use strict";
3const test = require("node:test");
4const assert = require("node:assert/strict");
5const { EventEmitter } = require("node:events");
6const { setImmediate: nextTurn } = require("node:timers/promises");
7const { LatencyMonitor, measurePing, parsePing } = require("../src/main/sessions/latencyMonitor");
8const { SessionManager } = require("../src/main/sessions/sessionManager");
10test("ICMP reply parser reads every reply line on macOS, Linux and localized Windows, never statistics lines", () => {
11 const { parsePingReplies } = require("../src/main/sessions/latencyMonitor");
12 for (const reply of ["64 bytes from 192.0.2.1: icmp_seq=1 ttl=64 time=24.1 ms", "Antwort von 192.0.2.1: Bytes=32 Zeit=24,1ms TTL=64", "16 bytes from 2001:db8::1, icmp_seq=0 hlim=64 time=24.1 ms"]) assert.equal(parsePing(reply), 24.1);
13 for (const reply of ["100% packet loss", "Request timed out.", "Minimum = 5ms, Maximum = 6ms, Average = 5ms", "Reply from 192.0.2.1: bytes=32 time<1ms TTL=128"]) assert.equal(parsePing(reply), null);
14 const windowsCzech = "Odpověď od 192.0.2.1: bajty=32 čas=5ms TTL=128\r\nOdpověď od 192.0.2.1: bajty=32 čas=7ms TTL=128\r\nVypršel časový limit žádosti.\r\n Minimum = 5ms, Maximum = 7ms, Průměr = 6ms";
15 assert.deepEqual(parsePingReplies(windowsCzech).map((reply) => reply.ms), [5, 7]);
16 assert.deepEqual(parsePingReplies("Reply from 192.0.2.1: bytes=32 time<1ms TTL=128"), [{ ms: 1, lessThan: true }]);
17});
19test("ICMP runs 3 probes with fixed arguments and no shell; reports median, loss and concrete failure reasons", async () => {
20 for (const platform of ["win32", "darwin", "linux"]) {
21 for (const address of ["192.0.2.1", "2001:db8::1"]) {
22 let call;
23 const result = await measurePing(address, { platform, run(executable, args, options, callback) {
24 call = { executable, args, options };
25 callback(null, "64 bytes: ttl=64 time=31 ms\n64 bytes: ttl=64 time=40 ms\n64 bytes: ttl=64 time=20 ms\n");
26 } });
27 assert.equal(result.ms, 31); assert.equal(result.loss, 0); assert.equal(result.status, "ok"); assert.equal(result.family, address.includes(":") ? 6 : 4);
28 assert.equal(call.args.at(-1), address); assert.equal(call.options.shell, false); assert.ok(call.args.includes("3"));
29 if (platform === "darwin") assert.equal(call.executable, address.includes(":") ? "/sbin/ping6" : "/sbin/ping");
30 }
31 }
32 const partial = await measurePing("192.0.2.1", { platform: "linux", run(_e, _a, _o, cb) { cb(Object.assign(new Error("exit 1"), { code: 1 }), "64 bytes: ttl=64 time=10 ms\n"); } });
33 assert.equal(partial.ms, 10); assert.ok(Math.abs(partial.loss - 2 / 3) < 1e-9);
34 const untrusted = await measurePing("host;touch /tmp/unsafe", { run() { assert.fail("Untrusted command executed"); } });
35 assert.equal(untrusted.ms, null); assert.equal(untrusted.status, "error");
36 const cases = [
37 [Object.assign(new Error("exit 2"), { code: 2 }), "", "timeout"],
38 [Object.assign(new Error("spawn ping ENOENT"), { code: "ENOENT" }), "", "unsupported"],
39 [Object.assign(new Error("exit 68"), { code: 68 }), "ping: sendto: Operation not permitted", "denied"],
40 [Object.assign(new Error("exit 1"), { code: 1 }), "Reply from 192.0.2.254: Destination host unreachable.", "unreachable"]
41 ];
42 for (const [error, output, status] of cases) {
43 const result = await measurePing("192.0.2.1", { platform: "darwin", run(_e, _a, _o, cb) { cb(error, output, ""); } });
44 assert.equal(result.status, status); assert.equal(result.ms, null); assert.ok(result.reason.length > 10);
45 }
46});
48test("monitor takes several monotonic SSH samples, keeps ICMP independent, never overlaps and stops", async (t) => {
49 t.mock.timers.enable({ apis: ["setTimeout"] });
50 let now = 100, calls = 0;
51 const values = [];
52 const durations = [31.25, 10, 20];
53 const monitor = new LatencyMonitor({ now: () => now, ping: async () => ({ ms: null, status: "timeout", reason: "blocked" }), intervalMs: 10000 });
54 const stop = monitor.start({ address: "192.0.2.1" }, async () => { now += durations[calls % 3]; calls++; }, (v) => values.push(v));
55 for (let index = 0; index < 5; index += 1) await nextTurn();
56 assert.equal(calls, 3);
57 assert.equal(values[0].sshRttMs, 20, "median of three samples"); assert.equal(values[0].sshRttMethod, "command");
58 assert.equal(values[0].pingMs, null); assert.equal(values[0].pingStatus, "timeout");
59 t.mock.timers.tick(10000); for (let index = 0; index < 5; index += 1) await nextTurn();
60 assert.equal(calls, 6);
61 stop(); t.mock.timers.tick(60000); await nextTurn();
62 assert.equal(calls, 6); assert.equal(values.length, 2);
63});
65test("transport measurements are used directly and a failed SSH round trip does not hide ICMP", async () => {
66 const values = [], stops = [];
67 const monitor = new LatencyMonitor({ ping: async (address) => address === "192.0.2.1" ? { ms: 24, status: "ok", loss: 0, family: 4 } : { ms: null, status: "timeout" } });
68 stops.push(monitor.start({ address: "192.0.2.1" }, async () => { throw new Error("Timeout"); }, (v) => values.push(["a", v])));
69 stops.push(monitor.start({ address: "192.0.2.2" }, async () => ({ ms: 3.5, method: "transport" }), (v) => values.push(["b", v])));
70 for (let index = 0; index < 6; index += 1) await nextTurn();
71 stops.forEach((stop) => stop());
72 const a = values.find(([id]) => id === "a")[1]; const b = values.find(([id]) => id === "b")[1];
73 assert.equal(a.pingMs, 24); assert.equal(a.sshRttMs, null); assert.match(a.sshRttError, /Timeout/);
74 assert.equal(b.pingMs, null); assert.equal(b.sshRttMs, 3.5); assert.equal(b.sshRttMethod, "transport");
75});
77test("SSH round trip uses the transport keepalive request and falls back to a shell-neutral no-op", async () => {
78 const manager = new SessionManager({});
79 let pings = 0;
80 const client = { _callbacks: [], _protocol: { ping() { pings++; setTimeout(() => client._callbacks.shift()(true), 5); } } };
81 const result = await manager._sshRoundTrip({ client });
82 assert.equal(result.method, "transport"); assert.ok(result.ms >= 0); assert.equal(pings, 1);
83 let command;
84 const channel = new EventEmitter(); channel.stderr = new EventEmitter();
85 const fallback = manager._sshRoundTrip({ client: { exec(cmd, cb) { command = cmd; cb(null, channel); setImmediate(() => channel.emit("close", 0)); } } });
86 assert.equal((await fallback).method, "command"); assert.equal(command, "exit 0", "valid in sh, cmd.exe and PowerShell");
87});
89test("connected sessions start/stop monitoring and reject stale results after reconnect", async () => {
90 const fingerprint = "ab".repeat(32), callbacks = [], stops = [];
91 const manager = new SessionManager({
92 profileService: { get: () => ({ id: "p", host: "192.0.2.1", port: 22, username: "user", authMethod: "password" }), getHostKey: () => fingerprint, markUsed() {} },
93 latencyMonitor: { start(record, request, publish) { callbacks.push({ record, request, publish }); const stop = { count: 0 }; stops.push(stop); return () => { stop.count++; }; } },
94 clientFactory() {
95 const client = new EventEmitter();
96 client.connect = () => setImmediate(() => client.emit("ready")); client.end = () => {};
97 client.shell = (_opts, cb) => { const stream = new EventEmitter(); stream.end = () => {}; cb(null, stream); };
98 client.exec = (command, cb) => {
99 assert.equal(command, "exit 0"); const stream = new EventEmitter(); stream.stderr = new EventEmitter(); cb(null, stream); setImmediate(() => stream.emit("close", 0));
100 };
101 return client;
102 }
103 });
104 try {
105 await manager.connect({ sessionId: "s", profileId: "p", password: "secret" });
106 assert.equal(callbacks.length, 1); await callbacks[0].request();
107 callbacks[0].publish({ pingMs: 24, sshRttMs: 31 });
108 assert.equal(manager.list()[0].latency.sshRttMs, 31);
109 await manager.connect({ sessionId: "s", profileId: "p", password: "secret" });
110 assert.equal(stops[0].count, 1); assert.equal(manager.list()[0].latency.sshRttMs, null);
111 callbacks[0].publish({ pingMs: 999, sshRttMs: 999 });
112 assert.equal(manager.list()[0].latency.sshRttMs, null);
113 callbacks[1].publish({ pingMs: null, sshRttMs: 12 });
114 assert.equal(manager.list()[0].state, "connected");
115 } finally { await manager.disconnectAll(); }
116 assert.equal(stops[1].count, 1);
117});
119test("SSH request timeout closes the channel, releases pending work and accepts no late result", async (t) => {
120 t.mock.timers.enable({ apis: ["setTimeout"] });
121 let closed = 0;
122 const channel = new EventEmitter(); channel.stderr = new EventEmitter(); channel.close = () => { closed++; };
123 const record = { client: { exec(_cmd, cb) { cb(null, channel); } } };
124 const manager = new SessionManager({});
125 const result = assert.rejects(manager._execFixed(record, "true", 5000), { code: "REMOTE_COMMAND_TIMEOUT" });
126 t.mock.timers.tick(5000); await result;
127 assert.equal(closed, 1); assert.equal(record.pendingExec.size, 0);
128 channel.emit("close", 0);
129});
131test("terminal latency displays milliseconds, concrete unavailable reasons and resets on disconnect", () => {
132 const fs = require("node:fs"), vm = require("node:vm");
133 const source = fs.readFileSync(require.resolve("../src/renderer/app.js"), "utf8");
134 const fn = source.slice(source.indexOf("function renderTerminalLatency()"), source.indexOf("async function connectTerminal"));
135 const element = {}, state = { terminalStateKind: "connected", terminalLatency: { pingMs: 24, pingStatus: "ok", pingLoss: 1 / 3, pingFamily: 6, sshRttMs: 31.25, sshRttMethod: "transport", measuredAt: "x" } };
136 const { createI18n } = require("../src/renderer/i18n");
137 for (const language of ["cs", "de", "en"]) {
138 const i18n = createI18n(language);
139 vm.runInNewContext(fn + "\nrenderTerminalLatency();", { state, $: () => element, t: (key, values) => i18n.t(key, values) });
140 assert.match(element.textContent, /24\.0 ms IPv6/); assert.match(element.textContent, /33 %/); assert.match(element.textContent, /31\.3 ms/); assert.equal(element.hidden, false);
141 }
142 const en = createI18n("en");
143 state.terminalLatency = { pingMs: null, pingStatus: "timeout", sshRttMs: 4.2, measuredAt: "x" };
144 vm.runInNewContext(fn + "\nrenderTerminalLatency();", { state, $: () => element, t: (key, values) => en.t(key, values) });
145 assert.match(element.textContent, /firewall may block ICMP/); assert.match(element.textContent, /4\.2 ms/, "SSH RTT stays visible when ICMP is blocked");
146 state.terminalLatency = null;
147 vm.runInNewContext(fn + "\nrenderTerminalLatency();", { state, $: () => element, t: (key, values) => en.t(key, values) });
148 assert.match(element.textContent, /measuring/);
149 state.terminalStateKind = "disconnected";
150 vm.runInNewContext(fn + "\nrenderTerminalLatency();", { state, $: () => element, t: (key, values) => en.t(key, values) });
151 assert.equal(element.hidden, true);
152});
SHA-256024443c13a757f6845e4bc95fa0d725b5525c2b0b9cd583608fc0298a8cc98a2

CH-J Proprietary Software License 1.14 · A forrás a CH-J Proprietary Software License 1.14 alatt található. Rendelkezése nem változtatja meg a licenc feltételeit, és nem ad további jogokat.

Archive SHA-256a9605c0870a223b6a8542bb0705edd51d8b04f3069582653417a8397fabcc09a
Fájlok