ts6-botflow/events.test.mjs
Bxio 1be8a45dde
All checks were successful
Testar e publicar imagem Docker / publish (push) Successful in 44s
Expand flow editor with TeamSpeak events, permissions and execution controls
2026-10-10 20:38:31 +01:00

187 lines
5.7 KiB
JavaScript

import { test } from "node:test";
import assert from "node:assert/strict";
import { generateKeyPairSync } from "node:crypto";
import ssh2 from "ssh2";
import {
createEventListener,
parseQuery,
escapeQuery,
unescapeQuery,
} from "./query-events.mjs";
test("Query escaping preserves pipes, newlines, Unicode and backslashes", () => {
const text = "Olá Pessoa | texto\\p\nlinha";
assert.equal(unescapeQuery(escapeQuery(text)), text);
assert.deepEqual(
parseQuery(
"clid=1 client_nickname=Daniel\\sCastro|clid=2 client_nickname=A\\pB",
),
[
{ clid: "1", client_nickname: "Daniel Castro" },
{ clid: "2", client_nickname: "A|B" },
],
);
});
test("SSH listener authenticates, registers notifications and parses fragmented events", async () => {
const privateKey = generateKeyPairSync("rsa", {
modulusLength: 2048,
}).privateKey.export({ type: "pkcs1", format: "pem" });
const commands = [],
events = [],
states = [],
clients = new Set();
let channel, listener;
const server = new ssh2.Server({ hostKeys: [privateKey] }, (client) => {
clients.add(client);
client.on("error", () => {});
client.on("close", () => clients.delete(client));
client.on("authentication", (ctx) =>
ctx.method === "password" &&
ctx.username === "query" &&
ctx.password === "ssh-secret"
? ctx.accept()
: ctx.reject(),
);
client.on("ready", () =>
client.on("session", (accept) => {
const session = accept();
session.on("shell", (accept) => {
channel = accept();
channel.write(
"TS3\nWelcome to the TeamSpeak ServerQuery interface.\n",
);
let buffer = "";
channel.on("data", (data) => {
buffer += data.toString();
let index;
while ((index = buffer.indexOf("\n")) >= 0) {
const command = buffer.slice(0, index).trim();
buffer = buffer.slice(index + 1);
commands.push(command);
if (command === "whoami")
channel.write("client_id=99 client_channel_id=1\n");
channel.write("error id=0 msg=ok\n");
}
});
});
}),
);
});
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
try {
let connectedResolve;
const connected = new Promise((resolve) => (connectedResolve = resolve));
listener = createEventListener(
{
url: "http://localhost:10080",
serverId: 1,
sshHost: "127.0.0.1",
sshPort: server.address().port,
sshUsername: "query",
sshPassword: "ssh-secret",
},
(name, data) => events.push({ name, data }),
(state) => {
states.push(state);
if (state.status === "connected") connectedResolve();
},
);
await Promise.race([
connected,
new Promise((_, reject) => {
const timer = setTimeout(
() => reject(new Error("SSH test timed out")),
8000,
);
timer.unref();
}),
]);
assert.equal(listener.ready, true);
assert.match(states.at(-1).fingerprint, /^[a-f0-9]{64}$/);
assert.ok(commands.includes("use sid=1"));
assert.ok(commands.includes("servernotifyregister event=server"));
assert.ok(commands.includes("servernotifyregister event=channel id=0"));
assert.ok(commands.includes("servernotifyregister event=textprivate"));
channel.write("notifycliententerview clid=4 client_nick");
await new Promise((r) => setTimeout(r, 10));
channel.write("name=Ol");
channel.write(
"á\\sPessoa client_type=0\nnotifytextmessage invokerid=4 msg=!info\\steste\n",
);
for (let i = 0; i < 50 && events.length < 2; i++)
await new Promise((r) => setTimeout(r, 10));
assert.equal(events.length, 2);
assert.equal(events[0].data.client_nickname, "Olá Pessoa");
assert.equal(events[1].data.msg, "!info teste");
assert.equal(events[1].data.__listenerChannel, "0");
listener.close();
listener = null;
let readyResolve;
const ready = new Promise((resolve) => (readyResolve = resolve));
listener = createEventListener(
{
url: "http://localhost:10080",
serverId: 1,
sshHost: "127.0.0.1",
sshPort: server.address().port,
sshUsername: "query",
sshPassword: "ssh-secret",
},
() => {},
(state) => {
if (state.status === "connected") readyResolve();
},
{ channelId: 21 },
);
await Promise.race([
ready,
new Promise((_, reject) => {
const timer = setTimeout(
() => reject(new Error("Channel SSH test timed out")),
8000,
);
timer.unref();
}),
]);
assert.ok(commands.includes("clientmove clid=99 cid=21"));
assert.ok(commands.includes("servernotifyregister event=textchannel"));
} finally {
listener?.close();
for (const client of clients) client.end();
await new Promise((resolve) => server.close(resolve));
}
});
test("SSH listener rejects a changed fingerprint and exposes the error", async () => {
let options,
destroyed = false;
class FakeClient {
on() {
return this;
}
connect(value) {
options = value;
}
destroy() {
destroyed = true;
}
}
const states = [],
listener = createEventListener(
{
url: "http://localhost",
sshUsername: "query",
sshPassword: "test",
sshFingerprint: "a".repeat(64),
},
() => {},
(state) => states.push(state),
{ Client: FakeClient },
);
assert.equal(options.hostVerifier("b".repeat(64)), false);
assert.match(states.at(-1).error, /mudou/);
listener.close();
assert.equal(destroyed, true);
});