Files
ssh-dynamic-mcp/test/status-collector.test.js
T
Max Shcheglov b889154bb3 feat: add dynamic per-call SSH connections and secret redaction
- execute-command/upload/download accept host/port/username/password inline
  to open an ephemeral connection for a single call
- add redactSecret/redactSecrets to mask credentials in logs and errors
- logger log()/handleError() accept optional secrets[] for redaction
- add SSH_CONFIG_MISSING error code and --password-from-env CLI flag
- dynamic-mode startup: server runs with no static config
2026-09-01 10:54:20 +07:00

160 lines
5.0 KiB
JavaScript

import { describe, it } from 'node:test';
import assert from 'node:assert';
import { collectSystemStatus } from '../build/utils/status-collector.js';
const PROBE_PATTERN = /printf '\\n(__MCP_FIELD_\w+_)(\w+)\\n'/g;
/** Field names in the order the script probes them, plus the shared marker. */
function readProbes(script) {
const probes = [...script.matchAll(PROBE_PATTERN)];
return {
marker: probes[0]?.[1],
fields: probes.map((probe) => probe[2]),
};
}
/**
* Stand-in for the remote shell: emits the marker line for every probe the
* script declares, followed by whatever `values` supplies for it.
*/
function fakeRemote(values, { lineEnding = '\n' } = {}) {
return (script) => {
const { marker, fields } = readProbes(script);
const output = fields
.map((field) => `${marker}${field}${lineEnding}${values[field] ?? ''}`)
.join(lineEnding);
return Promise.resolve(lineEnding + output);
};
}
describe('status collector', () => {
it('所有探针合并为一条单行命令', async () => {
const scripts = [];
await collectSystemStatus((script) => {
scripts.push(script);
return Promise.resolve('');
}, 'dev');
assert.strictEqual(scripts.length, 1);
// 多行脚本会被 commandTemplate 的引号包裹破坏
assert.ok(!scripts[0].includes('\n'));
// 每个探针都被隔离,单个失败不影响其余,且整体以成功退出
assert.ok(scripts[0].endsWith('; true'));
const { fields } = readProbes(scripts[0]);
assert.ok(fields.includes('hostname'));
assert.ok(fields.includes('servicesInstalled'));
assert.strictEqual(new Set(fields).size, fields.length);
});
it('按 marker 还原各字段', async () => {
const status = await collectSystemStatus(
fakeRemote({
hostname: 'web-01',
osName: 'Linux',
kernelVersion: '6.1.0',
memory: 'free:2.1G total:7.7G',
processes: '181',
threads: '901',
}),
'dev',
);
assert.strictEqual(status.reachable, true);
assert.strictEqual(status.hostname, 'web-01');
assert.strictEqual(status.osName, 'Linux');
assert.strictEqual(status.kernelVersion, '6.1.0');
assert.deepStrictEqual(status.memory, { free: '2.1G', total: '7.7G' });
// 解析时会各减去一行表头
assert.deepStrictEqual(status.processes, { running: 180, threads: 900 });
});
// 多行字段是 CRLF 归一化真正起作用的地方:逐行拆分的值不会再单独 trim,
// 残留的 \r 会直接进入结果。
it('pty 的 CRLF 输出同样能解析', async () => {
const status = await collectSystemStatus(
fakeRemote(
{
hostname: 'web-02',
ipAddresses: '10.0.0.7\r\n192.168.1.5',
drives: '/dev/sda1|50G|20G|30G|40%|/',
},
{ lineEnding: '\r\n' },
),
'dev',
);
assert.strictEqual(status.hostname, 'web-02');
assert.deepStrictEqual(status.ipAddresses, ['10.0.0.7', '192.168.1.5']);
assert.deepStrictEqual(status.drives, [
{
device: '/dev/sda1',
total: '50G',
used: '20G',
free: '30G',
usagePercent: '40%',
mountPoint: '/',
},
]);
});
it('空探针不影响其它字段', async () => {
const status = await collectSystemStatus(
fakeRemote({ hostname: 'web-03', ipAddresses: '', osName: 'Linux' }),
'dev',
);
assert.strictEqual(status.hostname, 'web-03');
assert.strictEqual(status.osName, 'Linux');
assert.strictEqual(status.ipAddresses, undefined);
});
// 命令白名单会拒绝这条探针脚本;那种情况下字段留空即可,
// 不能把主机报成不可达。
it('命令被拒绝时字段留空但仍视为可达', async () => {
const status = await collectSystemStatus(
() => Promise.reject(new Error('Command validation failed')),
'dev',
);
assert.strictEqual(status.reachable, true);
assert.strictEqual(status.hostname, undefined);
assert.ok(status.lastUpdated);
});
it('先逐条授权探针,再只批量执行允许的命令', async () => {
const scripts = [];
const recordingRemote = fakeRemote({ hostname: 'allowed-host' });
const status = await collectSystemStatus(
(script) => {
scripts.push(script);
return recordingRemote(script);
},
'dev',
(command) => command === 'hostname',
);
assert.strictEqual(status.hostname, 'allowed-host');
assert.strictEqual(scripts.length, 1);
assert.match(scripts[0], /hostname/);
assert.ok(!scripts[0].includes('uname -s'));
assert.ok(!scripts[0].includes('cat /etc/os-release'));
});
it('没有获准探针时不执行远端脚本', async () => {
let calls = 0;
const status = await collectSystemStatus(
async () => {
calls += 1;
return '';
},
'dev',
() => false,
);
assert.strictEqual(calls, 0);
assert.strictEqual(status.reachable, true);
assert.strictEqual(status.hostname, undefined);
});
});