From d90e9f561f0639d69de57c78639bb9bd4407c7cd Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 23 Feb 2026 05:19:23 +0000 Subject: [PATCH] test: merge overlapping trigger-handling suites --- ...proved-sender-toggle-elevated-mode.test.ts | 53 ++++++ ...levated-off-groups-without-mention.test.ts | 71 -------- ...age-summary-current-model-provider.test.ts | 70 +++++++- ...ne-commands-strips-it-before-agent.test.ts | 127 +++++++++++++- ...inline-status-unauthorized-senders.test.ts | 159 ------------------ ...ve-auth-profile-key-snippet-status.test.ts | 87 ---------- 6 files changed, 246 insertions(+), 321 deletions(-) delete mode 100644 src/auto-reply/reply.triggers.trigger-handling.allows-elevated-off-groups-without-mention.test.ts delete mode 100644 src/auto-reply/reply.triggers.trigger-handling.keeps-inline-status-unauthorized-senders.test.ts delete mode 100644 src/auto-reply/reply.triggers.trigger-handling.reports-active-auth-profile-key-snippet-status.test.ts diff --git a/src/auto-reply/reply.triggers.trigger-handling.allows-approved-sender-toggle-elevated-mode.test.ts b/src/auto-reply/reply.triggers.trigger-handling.allows-approved-sender-toggle-elevated-mode.test.ts index daedca41038..10152a8bf5b 100644 --- a/src/auto-reply/reply.triggers.trigger-handling.allows-approved-sender-toggle-elevated-mode.test.ts +++ b/src/auto-reply/reply.triggers.trigger-handling.allows-approved-sender-toggle-elevated-mode.test.ts @@ -47,6 +47,59 @@ describe("trigger handling", () => { expect(store[MAIN_SESSION_KEY]?.elevatedLevel).toBeUndefined(); }); }); + + it("allows elevated off in groups without mention", async () => { + await withTempHome(async (home) => { + const cfg = makeWhatsAppElevatedCfg(home, { requireMentionInGroups: false }); + + const res = await getReplyFromConfig( + { + Body: "/elevated off", + From: "whatsapp:group:123@g.us", + To: "whatsapp:+2000", + Provider: "whatsapp", + SenderE164: "+1000", + CommandAuthorized: true, + ChatType: "group", + WasMentioned: false, + }, + {}, + cfg, + ); + const text = Array.isArray(res) ? res[0]?.text : res?.text; + expect(text).toContain("Elevated mode disabled."); + + const store = await readSessionStore(cfg); + expect(store["agent:main:whatsapp:group:123@g.us"]?.elevatedLevel).toBe("off"); + }); + }); + + it("allows elevated directive in groups when mentioned", async () => { + await withTempHome(async (home) => { + const cfg = makeWhatsAppElevatedCfg(home, { requireMentionInGroups: true }); + + const res = await getReplyFromConfig( + { + Body: "/elevated on", + From: "whatsapp:group:123@g.us", + To: "whatsapp:+2000", + Provider: "whatsapp", + SenderE164: "+1000", + CommandAuthorized: true, + ChatType: "group", + WasMentioned: true, + }, + {}, + cfg, + ); + const text = Array.isArray(res) ? res[0]?.text : res?.text; + expect(text).toContain("Elevated mode set to ask"); + + const store = await readSessionStore(cfg); + expect(store["agent:main:whatsapp:group:123@g.us"]?.elevatedLevel).toBe("on"); + }); + }); + it("ignores elevated directive in groups when not mentioned", async () => { await withTempHome(async (home) => { getRunEmbeddedPiAgentMock().mockResolvedValue({ diff --git a/src/auto-reply/reply.triggers.trigger-handling.allows-elevated-off-groups-without-mention.test.ts b/src/auto-reply/reply.triggers.trigger-handling.allows-elevated-off-groups-without-mention.test.ts deleted file mode 100644 index 9b9d097f572..00000000000 --- a/src/auto-reply/reply.triggers.trigger-handling.allows-elevated-off-groups-without-mention.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { beforeAll, describe, expect, it } from "vitest"; -import { loadSessionStore } from "../config/sessions.js"; -import { - installTriggerHandlingE2eTestHooks, - loadGetReplyFromConfig, - makeWhatsAppElevatedCfg, - readSessionStore, - requireSessionStorePath, - withTempHome, -} from "./reply.triggers.trigger-handling.test-harness.js"; - -let getReplyFromConfig: typeof import("./reply.js").getReplyFromConfig; -beforeAll(async () => { - getReplyFromConfig = await loadGetReplyFromConfig(); -}); - -installTriggerHandlingE2eTestHooks(); - -describe("trigger handling", () => { - it("allows elevated off in groups without mention", async () => { - await withTempHome(async (home) => { - const cfg = makeWhatsAppElevatedCfg(home, { requireMentionInGroups: false }); - - const res = await getReplyFromConfig( - { - Body: "/elevated off", - From: "whatsapp:group:123@g.us", - To: "whatsapp:+2000", - Provider: "whatsapp", - SenderE164: "+1000", - CommandAuthorized: true, - ChatType: "group", - WasMentioned: false, - }, - {}, - cfg, - ); - const text = Array.isArray(res) ? res[0]?.text : res?.text; - expect(text).toContain("Elevated mode disabled."); - - const store = loadSessionStore(requireSessionStorePath(cfg)); - expect(store["agent:main:whatsapp:group:123@g.us"]?.elevatedLevel).toBe("off"); - }); - }); - - it("allows elevated directive in groups when mentioned", async () => { - await withTempHome(async (home) => { - const cfg = makeWhatsAppElevatedCfg(home, { requireMentionInGroups: true }); - - const res = await getReplyFromConfig( - { - Body: "/elevated on", - From: "whatsapp:group:123@g.us", - To: "whatsapp:+2000", - Provider: "whatsapp", - SenderE164: "+1000", - CommandAuthorized: true, - ChatType: "group", - WasMentioned: true, - }, - {}, - cfg, - ); - const text = Array.isArray(res) ? res[0]?.text : res?.text; - expect(text).toContain("Elevated mode set to ask"); - - const store = await readSessionStore(cfg); - expect(store["agent:main:whatsapp:group:123@g.us"]?.elevatedLevel).toBe("on"); - }); - }); -}); diff --git a/src/auto-reply/reply.triggers.trigger-handling.filters-usage-summary-current-model-provider.test.ts b/src/auto-reply/reply.triggers.trigger-handling.filters-usage-summary-current-model-provider.test.ts index e6f179b5f28..584799e18db 100644 --- a/src/auto-reply/reply.triggers.trigger-handling.filters-usage-summary-current-model-provider.test.ts +++ b/src/auto-reply/reply.triggers.trigger-handling.filters-usage-summary-current-model-provider.test.ts @@ -1,14 +1,16 @@ -import { readFile } from "node:fs/promises"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { beforeAll, describe, expect, it } from "vitest"; import { normalizeTestText } from "../../test/helpers/normalize-text.js"; import type { OpenClawConfig } from "../config/config.js"; +import { resolveSessionKey } from "../config/sessions.js"; import { createBlockReplyCollector, getProviderUsageMocks, getRunEmbeddedPiAgentMock, installTriggerHandlingE2eTestHooks, makeCfg, + requireSessionStorePath, withTempHome, } from "./reply.triggers.trigger-handling.test-harness.js"; @@ -362,4 +364,70 @@ describe("trigger handling", () => { expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled(); }); }); + + it("reports active auth profile and key snippet in status", async () => { + await withTempHome(async (home) => { + const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); + const cfg = makeCfg(home); + const agentDir = join(home, ".openclaw", "agents", "main", "agent"); + await mkdir(agentDir, { recursive: true }); + await writeFile( + join(agentDir, "auth-profiles.json"), + JSON.stringify( + { + version: 1, + profiles: { + "anthropic:work": { + type: "api_key", + provider: "anthropic", + key: "sk-test-1234567890abcdef", + }, + }, + lastGood: { anthropic: "anthropic:work" }, + }, + null, + 2, + ), + ); + + const sessionKey = resolveSessionKey("per-sender", { + From: "+1002", + To: "+2000", + Provider: "whatsapp", + } as Parameters[1]); + await writeFile( + requireSessionStorePath(cfg), + JSON.stringify( + { + [sessionKey]: { + sessionId: "session-auth", + updatedAt: Date.now(), + authProfileOverride: "anthropic:work", + }, + }, + null, + 2, + ), + ); + + const res = await getReplyFromConfig( + { + Body: "/status", + From: "+1002", + To: "+2000", + Provider: "whatsapp", + SenderE164: "+1002", + CommandAuthorized: true, + }, + {}, + cfg, + ); + const text = Array.isArray(res) ? res[0]?.text : res?.text; + expect(text).toContain("api-key"); + expect(text).toMatch(/\u2026|\.{3}/); + expect(text).toContain("(anthropic:work)"); + expect(text).not.toContain("mixed"); + expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled(); + }); + }); }); diff --git a/src/auto-reply/reply.triggers.trigger-handling.handles-inline-commands-strips-it-before-agent.test.ts b/src/auto-reply/reply.triggers.trigger-handling.handles-inline-commands-strips-it-before-agent.test.ts index af10f07457b..08d80e03c58 100644 --- a/src/auto-reply/reply.triggers.trigger-handling.handles-inline-commands-strips-it-before-agent.test.ts +++ b/src/auto-reply/reply.triggers.trigger-handling.handles-inline-commands-strips-it-before-agent.test.ts @@ -1,9 +1,11 @@ +import fs from "node:fs/promises"; import { beforeAll, describe, expect, it } from "vitest"; import { expectInlineCommandHandledAndStripped, getRunEmbeddedPiAgentMock, installTriggerHandlingE2eTestHooks, loadGetReplyFromConfig, + MAIN_SESSION_KEY, makeCfg, withTempHome, } from "./reply.triggers.trigger-handling.test-harness.js"; @@ -15,10 +17,9 @@ beforeAll(async () => { installTriggerHandlingE2eTestHooks(); -async function expectUnauthorizedCommandDropped(home: string, body: "/status" | "/whoami") { - const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); +function makeUnauthorizedWhatsAppCfg(home: string) { const baseCfg = makeCfg(home); - const cfg = { + return { ...baseCfg, channels: { ...baseCfg.channels, @@ -27,6 +28,19 @@ async function expectUnauthorizedCommandDropped(home: string, body: "/status" | }, }, }; +} + +function requireSessionStorePath(cfg: { session?: { store?: string } }): string { + const storePath = cfg.session?.store; + if (!storePath) { + throw new Error("expected session store path"); + } + return storePath; +} + +async function expectUnauthorizedCommandDropped(home: string, body: "/status" | "/whoami") { + const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); + const cfg = makeUnauthorizedWhatsAppCfg(home); const res = await getReplyFromConfig( { @@ -44,6 +58,37 @@ async function expectUnauthorizedCommandDropped(home: string, body: "/status" | expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled(); } +function mockEmbeddedOk() { + const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); + runEmbeddedPiAgentMock.mockResolvedValue({ + payloads: [{ text: "ok" }], + meta: { + durationMs: 1, + agentMeta: { sessionId: "s", provider: "p", model: "m" }, + }, + }); + return runEmbeddedPiAgentMock; +} + +async function runInlineUnauthorizedCommand(params: { + home: string; + command: "/status" | "/help"; +}) { + const cfg = makeUnauthorizedWhatsAppCfg(params.home); + const res = await getReplyFromConfig( + { + Body: `please ${params.command} now`, + From: "+2001", + To: "+2000", + Provider: "whatsapp", + SenderE164: "+2001", + }, + {}, + cfg, + ); + return res; +} + describe("trigger handling", () => { it("handles inline /commands and strips it before the agent", async () => { await withTempHome(async (home) => { @@ -95,4 +140,80 @@ describe("trigger handling", () => { await expectUnauthorizedCommandDropped(home, "/whoami"); }); }); + + it("keeps inline /status for unauthorized senders", async () => { + await withTempHome(async (home) => { + const runEmbeddedPiAgentMock = mockEmbeddedOk(); + const res = await runInlineUnauthorizedCommand({ + home, + command: "/status", + }); + const text = Array.isArray(res) ? res[0]?.text : res?.text; + expect(text).toBe("ok"); + expect(runEmbeddedPiAgentMock).toHaveBeenCalled(); + const prompt = runEmbeddedPiAgentMock.mock.calls[0]?.[0]?.prompt ?? ""; + expect(prompt).toContain("/status"); + }); + }); + + it("keeps inline /help for unauthorized senders", async () => { + await withTempHome(async (home) => { + const runEmbeddedPiAgentMock = mockEmbeddedOk(); + const res = await runInlineUnauthorizedCommand({ + home, + command: "/help", + }); + const text = Array.isArray(res) ? res[0]?.text : res?.text; + expect(text).toBe("ok"); + expect(runEmbeddedPiAgentMock).toHaveBeenCalled(); + const prompt = runEmbeddedPiAgentMock.mock.calls[0]?.[0]?.prompt ?? ""; + expect(prompt).toContain("/help"); + }); + }); + + it("returns help without invoking the agent", async () => { + await withTempHome(async (home) => { + const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); + const res = await getReplyFromConfig( + { + Body: "/help", + From: "+1002", + To: "+2000", + CommandAuthorized: true, + }, + {}, + makeCfg(home), + ); + const text = Array.isArray(res) ? res[0]?.text : res?.text; + expect(text).toContain("Help"); + expect(text).toContain("Session"); + expect(text).toContain("More: /commands for full list"); + expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled(); + }); + }); + + it("allows owner to set send policy", async () => { + await withTempHome(async (home) => { + const cfg = makeUnauthorizedWhatsAppCfg(home); + + const res = await getReplyFromConfig( + { + Body: "/send off", + From: "+1000", + To: "+2000", + Provider: "whatsapp", + SenderE164: "+1000", + CommandAuthorized: true, + }, + {}, + cfg, + ); + const text = Array.isArray(res) ? res[0]?.text : res?.text; + expect(text).toContain("Send policy set to off"); + + const storeRaw = await fs.readFile(requireSessionStorePath(cfg), "utf-8"); + const store = JSON.parse(storeRaw) as Record; + expect(store[MAIN_SESSION_KEY]?.sendPolicy).toBe("deny"); + }); + }); }); diff --git a/src/auto-reply/reply.triggers.trigger-handling.keeps-inline-status-unauthorized-senders.test.ts b/src/auto-reply/reply.triggers.trigger-handling.keeps-inline-status-unauthorized-senders.test.ts deleted file mode 100644 index 99306b3f648..00000000000 --- a/src/auto-reply/reply.triggers.trigger-handling.keeps-inline-status-unauthorized-senders.test.ts +++ /dev/null @@ -1,159 +0,0 @@ -import fs from "node:fs/promises"; -import { beforeAll, describe, expect, it } from "vitest"; -import { - getRunEmbeddedPiAgentMock, - installTriggerHandlingE2eTestHooks, - MAIN_SESSION_KEY, - makeCfg, - withTempHome, -} from "./reply.triggers.trigger-handling.test-harness.js"; - -let getReplyFromConfig: typeof import("./reply.js").getReplyFromConfig; -beforeAll(async () => { - ({ getReplyFromConfig } = await import("./reply.js")); -}); - -installTriggerHandlingE2eTestHooks(); - -function mockEmbeddedOk() { - const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); - runEmbeddedPiAgentMock.mockResolvedValue({ - payloads: [{ text: "ok" }], - meta: { - durationMs: 1, - agentMeta: { sessionId: "s", provider: "p", model: "m" }, - }, - }); - return runEmbeddedPiAgentMock; -} - -function makeUnauthorizedWhatsAppCfg(home: string) { - const baseCfg = makeCfg(home); - return { - ...baseCfg, - channels: { - ...baseCfg.channels, - whatsapp: { - allowFrom: ["+1000"], - }, - }, - }; -} - -function requireSessionStorePath(cfg: { session?: { store?: string } }): string { - const storePath = cfg.session?.store; - if (!storePath) { - throw new Error("expected session store path"); - } - return storePath; -} - -async function runInlineUnauthorizedCommand(params: { - home: string; - command: "/status" | "/help"; - getReplyFromConfig: typeof import("./reply.js").getReplyFromConfig; -}) { - const cfg = makeUnauthorizedWhatsAppCfg(params.home); - const res = await params.getReplyFromConfig( - { - Body: `please ${params.command} now`, - From: "+2001", - To: "+2000", - Provider: "whatsapp", - SenderE164: "+2001", - }, - {}, - cfg, - ); - return { cfg, res }; -} - -describe("trigger handling", () => { - it("keeps inline /status for unauthorized senders", async () => { - await withTempHome(async (home) => { - const runEmbeddedPiAgentMock = mockEmbeddedOk(); - const { res } = await runInlineUnauthorizedCommand({ - home, - command: "/status", - getReplyFromConfig, - }); - const text = Array.isArray(res) ? res[0]?.text : res?.text; - expect(text).toBe("ok"); - expect(runEmbeddedPiAgentMock).toHaveBeenCalled(); - const prompt = runEmbeddedPiAgentMock.mock.calls[0]?.[0]?.prompt ?? ""; - // Not allowlisted: inline /status is treated as plain text and is not stripped. - expect(prompt).toContain("/status"); - }); - }); - - it("keeps inline /help for unauthorized senders", async () => { - await withTempHome(async (home) => { - const runEmbeddedPiAgentMock = mockEmbeddedOk(); - const { res } = await runInlineUnauthorizedCommand({ - home, - command: "/help", - getReplyFromConfig, - }); - const text = Array.isArray(res) ? res[0]?.text : res?.text; - expect(text).toBe("ok"); - expect(runEmbeddedPiAgentMock).toHaveBeenCalled(); - const prompt = runEmbeddedPiAgentMock.mock.calls[0]?.[0]?.prompt ?? ""; - expect(prompt).toContain("/help"); - }); - }); - - it("returns help without invoking the agent", async () => { - await withTempHome(async (home) => { - const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); - const res = await getReplyFromConfig( - { - Body: "/help", - From: "+1002", - To: "+2000", - CommandAuthorized: true, - }, - {}, - makeCfg(home), - ); - const text = Array.isArray(res) ? res[0]?.text : res?.text; - expect(text).toContain("Help"); - expect(text).toContain("Session"); - expect(text).toContain("More: /commands for full list"); - expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled(); - }); - }); - - it("allows owner to set send policy", async () => { - await withTempHome(async (home) => { - const baseCfg = makeCfg(home); - const cfg = { - ...baseCfg, - channels: { - ...baseCfg.channels, - whatsapp: { - allowFrom: ["+1000"], - }, - }, - }; - - const res = await getReplyFromConfig( - { - Body: "/send off", - From: "+1000", - To: "+2000", - Provider: "whatsapp", - SenderE164: "+1000", - CommandAuthorized: true, - }, - {}, - cfg, - ); - const text = Array.isArray(res) ? res[0]?.text : res?.text; - expect(text).toContain("Send policy set to off"); - - const storeRaw = await fs.readFile(requireSessionStorePath(cfg), "utf-8"); - const store = JSON.parse(storeRaw) as Record; - expect(store[MAIN_SESSION_KEY]?.sendPolicy).toBe("deny"); - }); - }); -}); diff --git a/src/auto-reply/reply.triggers.trigger-handling.reports-active-auth-profile-key-snippet-status.test.ts b/src/auto-reply/reply.triggers.trigger-handling.reports-active-auth-profile-key-snippet-status.test.ts deleted file mode 100644 index cc6c83cf956..00000000000 --- a/src/auto-reply/reply.triggers.trigger-handling.reports-active-auth-profile-key-snippet-status.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -import fs from "node:fs/promises"; -import { join } from "node:path"; -import { beforeAll, describe, expect, it } from "vitest"; -import { resolveSessionKey } from "../config/sessions.js"; -import { - getRunEmbeddedPiAgentMock, - installTriggerHandlingE2eTestHooks, - loadGetReplyFromConfig, - makeCfg, - requireSessionStorePath, - withTempHome, -} from "./reply.triggers.trigger-handling.test-harness.js"; - -let getReplyFromConfig: typeof import("./reply.js").getReplyFromConfig; -beforeAll(async () => { - getReplyFromConfig = await loadGetReplyFromConfig(); -}); - -installTriggerHandlingE2eTestHooks(); - -describe("trigger handling", () => { - it("reports active auth profile and key snippet in status", async () => { - await withTempHome(async (home) => { - const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); - const cfg = makeCfg(home); - const agentDir = join(home, ".openclaw", "agents", "main", "agent"); - await fs.mkdir(agentDir, { recursive: true }); - await fs.writeFile( - join(agentDir, "auth-profiles.json"), - JSON.stringify( - { - version: 1, - profiles: { - "anthropic:work": { - type: "api_key", - provider: "anthropic", - key: "sk-test-1234567890abcdef", - }, - }, - lastGood: { anthropic: "anthropic:work" }, - }, - null, - 2, - ), - ); - - const sessionKey = resolveSessionKey("per-sender", { - From: "+1002", - To: "+2000", - Provider: "whatsapp", - } as Parameters[1]); - await fs.writeFile( - requireSessionStorePath(cfg), - JSON.stringify( - { - [sessionKey]: { - sessionId: "session-auth", - updatedAt: Date.now(), - authProfileOverride: "anthropic:work", - }, - }, - null, - 2, - ), - ); - - const res = await getReplyFromConfig( - { - Body: "/status", - From: "+1002", - To: "+2000", - Provider: "whatsapp", - SenderE164: "+1002", - CommandAuthorized: true, - }, - {}, - cfg, - ); - const text = Array.isArray(res) ? res[0]?.text : res?.text; - expect(text).toContain("api-key"); - expect(text).toMatch(/\u2026|\.{3}/); - expect(text).toContain("(anthropic:work)"); - expect(text).not.toContain("mixed"); - expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled(); - }); - }); -});