From d53d4dc22fbfd46d9f7994b16572bfdc34dad1af Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 13 Mar 2026 20:15:02 +0000 Subject: [PATCH] refactor: share zalouser group gating helpers --- .../zalouser/src/monitor.group-gating.test.ts | 329 ++++++++---------- 1 file changed, 154 insertions(+), 175 deletions(-) diff --git a/extensions/zalouser/src/monitor.group-gating.test.ts b/extensions/zalouser/src/monitor.group-gating.test.ts index 379c7a354b3..ca42edde43a 100644 --- a/extensions/zalouser/src/monitor.group-gating.test.ts +++ b/extensions/zalouser/src/monitor.group-gating.test.ts @@ -254,57 +254,152 @@ describe("zalouser monitor group mention gating", () => { sendSeenZalouserMock.mockClear(); }); - it("skips unmentioned group messages when requireMention=true", async () => { - const { dispatchReplyWithBufferedBlockDispatcher } = installRuntime({ - commandAuthorized: false, - }); + async function processMessageWithDefaults(params: { + message: ZaloInboundMessage; + account?: ResolvedZalouserAccount; + historyState?: { + historyLimit: number; + groupHistories: Map< + string, + Array<{ sender: string; body: string; timestamp?: number; messageId?: string }> + >; + }; + }) { await __testing.processMessage({ - message: createGroupMessage(), - account: createAccount(), + message: params.message, + account: params.account ?? createAccount(), config: createConfig(), runtime: createRuntimeEnv(), + historyState: params.historyState, }); + } - expect(dispatchReplyWithBufferedBlockDispatcher).not.toHaveBeenCalled(); - expect(sendTypingZalouserMock).not.toHaveBeenCalled(); - }); - - it("fails closed when requireMention=true but mention detection is unavailable", async () => { + async function expectSkippedGroupMessage(message?: Partial) { const { dispatchReplyWithBufferedBlockDispatcher } = installRuntime({ commandAuthorized: false, }); - await __testing.processMessage({ - message: createGroupMessage({ - canResolveExplicitMention: false, - hasAnyMention: false, - wasExplicitlyMentioned: false, - }), - account: createAccount(), - config: createConfig(), - runtime: createRuntimeEnv(), + await processMessageWithDefaults({ + message: createGroupMessage(message), }); - expect(dispatchReplyWithBufferedBlockDispatcher).not.toHaveBeenCalled(); expect(sendTypingZalouserMock).not.toHaveBeenCalled(); - }); + } - it("dispatches explicitly-mentioned group messages and marks WasMentioned", async () => { + async function expectGroupCommandAuthorizers(params: { + accountConfig: ResolvedZalouserAccount["config"]; + expectedAuthorizers: Array<{ configured: boolean; allowed: boolean }>; + }) { + const { dispatchReplyWithBufferedBlockDispatcher, resolveCommandAuthorizedFromAuthorizers } = + installGroupCommandAuthRuntime(); + await processGroupControlCommand({ + account: { + ...createAccount(), + config: params.accountConfig, + }, + }); + expect(dispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalledTimes(1); + const authCall = resolveCommandAuthorizedFromAuthorizers.mock.calls[0]?.[0]; + expect(authCall?.authorizers).toEqual(params.expectedAuthorizers); + } + + async function processOpenDmMessage(params?: { + message?: Partial; + readSessionUpdatedAt?: (input?: { + storePath: string; + sessionKey: string; + }) => number | undefined; + }) { + const runtime = installRuntime({ + commandAuthorized: false, + }); + if (params?.readSessionUpdatedAt) { + runtime.readSessionUpdatedAt.mockImplementation(params.readSessionUpdatedAt); + } + const account = createAccount(); + await processMessageWithDefaults({ + message: createDmMessage(params?.message), + account: { + ...account, + config: { + ...account.config, + dmPolicy: "open", + }, + }, + }); + return runtime; + } + + async function expectDangerousNameMatching(params: { + dangerouslyAllowNameMatching?: boolean; + expectedDispatches: number; + }) { const { dispatchReplyWithBufferedBlockDispatcher } = installRuntime({ commandAuthorized: false, }); - await __testing.processMessage({ + await processMessageWithDefaults({ message: createGroupMessage({ + threadId: "g-attacker-001", + groupName: "Trusted Team", + senderId: "666", hasAnyMention: true, wasExplicitlyMentioned: true, content: "ping @bot", }), - account: createAccount(), - config: createConfig(), - runtime: createRuntimeEnv(), + account: { + ...createAccount(), + config: { + ...createAccount().config, + ...(params.dangerouslyAllowNameMatching ? { dangerouslyAllowNameMatching: true } : {}), + groupPolicy: "allowlist", + groupAllowFrom: ["*"], + groups: { + "group:g-trusted-001": { allow: true }, + "Trusted Team": { allow: true }, + }, + }, + }, }); + expect(dispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalledTimes( + params.expectedDispatches, + ); + return dispatchReplyWithBufferedBlockDispatcher; + } + async function dispatchGroupMessage(params: { + commandAuthorized: boolean; + message: Partial; + }) { + const { dispatchReplyWithBufferedBlockDispatcher } = installRuntime({ + commandAuthorized: params.commandAuthorized, + }); + await processMessageWithDefaults({ + message: createGroupMessage(params.message), + }); expect(dispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalledTimes(1); - const callArg = dispatchReplyWithBufferedBlockDispatcher.mock.calls[0]?.[0]; + return dispatchReplyWithBufferedBlockDispatcher.mock.calls[0]?.[0]; + } + + it("skips unmentioned group messages when requireMention=true", async () => { + await expectSkippedGroupMessage(); + }); + + it("fails closed when requireMention=true but mention detection is unavailable", async () => { + await expectSkippedGroupMessage({ + canResolveExplicitMention: false, + hasAnyMention: false, + wasExplicitlyMentioned: false, + }); + }); + + it("dispatches explicitly-mentioned group messages and marks WasMentioned", async () => { + const callArg = await dispatchGroupMessage({ + commandAuthorized: false, + message: { + hasAnyMention: true, + wasExplicitlyMentioned: true, + content: "ping @bot", + }, + }); expect(callArg?.ctx?.WasMentioned).toBe(true); expect(callArg?.ctx?.To).toBe("zalouser:group:g-1"); expect(callArg?.ctx?.OriginatingTo).toBe("zalouser:group:g-1"); @@ -315,22 +410,14 @@ describe("zalouser monitor group mention gating", () => { }); it("allows authorized control commands to bypass mention gating", async () => { - const { dispatchReplyWithBufferedBlockDispatcher } = installRuntime({ + const callArg = await dispatchGroupMessage({ commandAuthorized: true, - }); - await __testing.processMessage({ - message: createGroupMessage({ + message: { content: "/status", hasAnyMention: false, wasExplicitlyMentioned: false, - }), - account: createAccount(), - config: createConfig(), - runtime: createRuntimeEnv(), + }, }); - - expect(dispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalledTimes(1); - const callArg = dispatchReplyWithBufferedBlockDispatcher.mock.calls[0]?.[0]; expect(callArg?.ctx?.WasMentioned).toBe(true); }); @@ -371,46 +458,30 @@ describe("zalouser monitor group mention gating", () => { }); it("uses commandContent for mention-prefixed control commands", async () => { - const { dispatchReplyWithBufferedBlockDispatcher } = installRuntime({ + const callArg = await dispatchGroupMessage({ commandAuthorized: true, - }); - await __testing.processMessage({ - message: createGroupMessage({ + message: { content: "@Bot /new", commandContent: "/new", hasAnyMention: true, wasExplicitlyMentioned: true, - }), - account: createAccount(), - config: createConfig(), - runtime: createRuntimeEnv(), + }, }); - - expect(dispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalledTimes(1); - const callArg = dispatchReplyWithBufferedBlockDispatcher.mock.calls[0]?.[0]; expect(callArg?.ctx?.CommandBody).toBe("/new"); expect(callArg?.ctx?.BodyForCommands).toBe("/new"); }); it("allows group control commands when only allowFrom is configured", async () => { - const { dispatchReplyWithBufferedBlockDispatcher, resolveCommandAuthorizedFromAuthorizers } = - installGroupCommandAuthRuntime(); - await processGroupControlCommand({ - account: { - ...createAccount(), - config: { - ...createAccount().config, - allowFrom: ["123"], - }, + await expectGroupCommandAuthorizers({ + accountConfig: { + ...createAccount().config, + allowFrom: ["123"], }, + expectedAuthorizers: [ + { configured: true, allowed: true }, + { configured: true, allowed: true }, + ], }); - - expect(dispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalledTimes(1); - const authCall = resolveCommandAuthorizedFromAuthorizers.mock.calls[0]?.[0]; - expect(authCall?.authorizers).toEqual([ - { configured: true, allowed: true }, - { configured: true, allowed: true }, - ]); }); it("blocks group messages when sender is not in groupAllowFrom/allowFrom", async () => { @@ -439,112 +510,35 @@ describe("zalouser monitor group mention gating", () => { }); it("does not accept a different group id by matching only the mutable group name by default", async () => { - const { dispatchReplyWithBufferedBlockDispatcher } = installRuntime({ - commandAuthorized: false, - }); - await __testing.processMessage({ - message: createGroupMessage({ - threadId: "g-attacker-001", - groupName: "Trusted Team", - senderId: "666", - hasAnyMention: true, - wasExplicitlyMentioned: true, - content: "ping @bot", - }), - account: { - ...createAccount(), - config: { - ...createAccount().config, - groupPolicy: "allowlist", - groupAllowFrom: ["*"], - groups: { - "group:g-trusted-001": { allow: true }, - "Trusted Team": { allow: true }, - }, - }, - }, - config: createConfig(), - runtime: createRuntimeEnv(), - }); - - expect(dispatchReplyWithBufferedBlockDispatcher).not.toHaveBeenCalled(); + await expectDangerousNameMatching({ expectedDispatches: 0 }); }); it("accepts mutable group-name matches only when dangerouslyAllowNameMatching is enabled", async () => { - const { dispatchReplyWithBufferedBlockDispatcher } = installRuntime({ - commandAuthorized: false, + const dispatchReplyWithBufferedBlockDispatcher = await expectDangerousNameMatching({ + dangerouslyAllowNameMatching: true, + expectedDispatches: 1, }); - await __testing.processMessage({ - message: createGroupMessage({ - threadId: "g-attacker-001", - groupName: "Trusted Team", - senderId: "666", - hasAnyMention: true, - wasExplicitlyMentioned: true, - content: "ping @bot", - }), - account: { - ...createAccount(), - config: { - ...createAccount().config, - dangerouslyAllowNameMatching: true, - groupPolicy: "allowlist", - groupAllowFrom: ["*"], - groups: { - "group:g-trusted-001": { allow: true }, - "Trusted Team": { allow: true }, - }, - }, - }, - config: createConfig(), - runtime: createRuntimeEnv(), - }); - - expect(dispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalledTimes(1); const callArg = dispatchReplyWithBufferedBlockDispatcher.mock.calls[0]?.[0]; expect(callArg?.ctx?.To).toBe("zalouser:group:g-attacker-001"); }); it("allows group control commands when sender is in groupAllowFrom", async () => { - const { dispatchReplyWithBufferedBlockDispatcher, resolveCommandAuthorizedFromAuthorizers } = - installGroupCommandAuthRuntime(); - await processGroupControlCommand({ - account: { - ...createAccount(), - config: { - ...createAccount().config, - allowFrom: ["999"], - groupAllowFrom: ["123"], - }, + await expectGroupCommandAuthorizers({ + accountConfig: { + ...createAccount().config, + allowFrom: ["999"], + groupAllowFrom: ["123"], }, + expectedAuthorizers: [ + { configured: true, allowed: false }, + { configured: true, allowed: true }, + ], }); - - expect(dispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalledTimes(1); - const authCall = resolveCommandAuthorizedFromAuthorizers.mock.calls[0]?.[0]; - expect(authCall?.authorizers).toEqual([ - { configured: true, allowed: false }, - { configured: true, allowed: true }, - ]); }); it("routes DM messages with direct peer kind", async () => { const { dispatchReplyWithBufferedBlockDispatcher, resolveAgentRoute, buildAgentSessionKey } = - installRuntime({ - commandAuthorized: false, - }); - const account = createAccount(); - await __testing.processMessage({ - message: createDmMessage(), - account: { - ...account, - config: { - ...account.config, - dmPolicy: "open", - }, - }, - config: createConfig(), - runtime: createRuntimeEnv(), - }); + await processOpenDmMessage(); expect(resolveAgentRoute).toHaveBeenCalledWith( expect.objectContaining({ @@ -562,24 +556,9 @@ describe("zalouser monitor group mention gating", () => { }); it("reuses the legacy DM session key when only the old group-shaped session exists", async () => { - const { dispatchReplyWithBufferedBlockDispatcher, readSessionUpdatedAt } = installRuntime({ - commandAuthorized: false, - }); - readSessionUpdatedAt.mockImplementation((input?: { storePath: string; sessionKey: string }) => - input?.sessionKey === "agent:main:zalouser:group:321" ? 123 : undefined, - ); - const account = createAccount(); - await __testing.processMessage({ - message: createDmMessage(), - account: { - ...account, - config: { - ...account.config, - dmPolicy: "open", - }, - }, - config: createConfig(), - runtime: createRuntimeEnv(), + const { dispatchReplyWithBufferedBlockDispatcher } = await processOpenDmMessage({ + readSessionUpdatedAt: (input?: { storePath: string; sessionKey: string }) => + input?.sessionKey === "agent:main:zalouser:group:321" ? 123 : undefined, }); const callArg = dispatchReplyWithBufferedBlockDispatcher.mock.calls[0]?.[0];