From 31d38307917797488c85e7c29ce0ef54eae1d739 Mon Sep 17 00:00:00 2001 From: saopig1 <4x7sw862st@gmail.com> Date: Sun, 28 Jun 2026 23:40:30 +0800 Subject: [PATCH] test(ts-protocol): smoke-test getClientServerGroups query string + client_servergroups parse --- src/ts-protocol/client-groups.test.ts | 85 +++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/ts-protocol/client-groups.test.ts diff --git a/src/ts-protocol/client-groups.test.ts b/src/ts-protocol/client-groups.test.ts new file mode 100644 index 0000000..261a3d6 --- /dev/null +++ b/src/ts-protocol/client-groups.test.ts @@ -0,0 +1,85 @@ +import { describe, it, expect, vi } from "vitest"; +import pino from "pino"; +import { TS3Client } from "./client.js"; + +/** + * Integration "smoke test" for the admin-command gate's group resolution. + * + * It drives the REAL TS3Client.getClientServerGroups → library getClientInfo + * path against a stubbed underlying client, so it exercises the actual + * `clientinfo clid=` query string and the real `client_servergroups` + * parsing — the pieces that were previously only verified by reading the code. + * + * What this CANNOT cover (inherently server-side, needs a live TS server): + * whether a real server returns groups for a client in a DIFFERENT channel. + * The stub models the server-wide answer (groups returned regardless of + * channel); the failure modes below confirm we fail closed when it doesn't. + */ +function makeClient(): TS3Client { + return new TS3Client( + { host: "localhost", port: 9987, queryPort: 10011, nickname: "TestBot" }, + pino({ level: "silent" }), + ); +} + +/** Inject a fake low-level client carrying a canned clientinfo response. */ +function withFakeClient( + ts: TS3Client, + respond: (cmd: string) => Record[] | Promise[]>, +): string[] { + const calls: string[] = []; + const fake = { + execCommandWithResponse: vi.fn(async (cmd: string) => { + calls.push(cmd); + return respond(cmd); + }), + }; + (ts as unknown as { client: unknown }).client = fake; + return calls; +} + +describe("TS3Client.getClientServerGroups — live query + parse smoke test", () => { + it("issues `clientinfo clid=` and parses comma-separated client_servergroups", async () => { + const ts = makeClient(); + const calls = withFakeClient(ts, () => [ + { client_nickname: "Alice", cid: "99", client_servergroups: "6,8" }, + ]); + + const groups = await ts.getClientServerGroups(5); + + expect(groups).toEqual(["6", "8"]); + // Exact query the bot sends to resolve a sender's groups, by client id. + expect(calls[0]).toBe("clientinfo clid=5"); + }); + + it("parses a single-group response", async () => { + const ts = makeClient(); + withFakeClient(ts, () => [{ client_servergroups: "6" }]); + expect(await ts.getClientServerGroups(5)).toEqual(["6"]); + }); + + it("returns [] when the client carries no server groups (empty field)", async () => { + const ts = makeClient(); + withFakeClient(ts, () => [{ client_nickname: "Bob", client_servergroups: "" }]); + expect(await ts.getClientServerGroups(7)).toEqual([]); + }); + + it("returns [] when the server-groups field is absent", async () => { + const ts = makeClient(); + withFakeClient(ts, () => [{ client_nickname: "Carol" }]); + expect(await ts.getClientServerGroups(7)).toEqual([]); + }); + + it("fails closed (returns []) when the query throws / client id is unknown", async () => { + const ts = makeClient(); + withFakeClient(ts, () => { + throw new Error("invalid clientID"); + }); + expect(await ts.getClientServerGroups(999)).toEqual([]); + }); + + it("returns [] when not connected (no underlying client)", async () => { + const ts = makeClient(); + expect(await ts.getClientServerGroups(5)).toEqual([]); + }); +});