From af547ec52c3a94c532ffe86ae392617a96ec66c7 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 23 Feb 2026 05:15:27 +0000 Subject: [PATCH] test: consolidate trigger-handling suites --- ...s-activation-from-allowfrom-groups.test.ts | 6 - ...proved-sender-toggle-elevated-mode.test.ts | 105 +++++++++++++++ ...levated-off-groups-without-mention.test.ts | 5 - ...age-summary-current-model-provider.test.ts | 102 +++++++++++++++ ...ne-commands-strips-it-before-agent.test.ts | 12 ++ ...evated-directive-unapproved-sender.test.ts | 120 ------------------ ...ve-auth-profile-key-snippet-status.test.ts | 42 ------ ...ng.runs-greeting-prompt-bare-reset.test.ts | 5 + ...efault-model-status-not-configured.test.ts | 116 ----------------- 9 files changed, 224 insertions(+), 289 deletions(-) delete mode 100644 src/auto-reply/reply.triggers.trigger-handling.ignores-inline-elevated-directive-unapproved-sender.test.ts delete mode 100644 src/auto-reply/reply.triggers.trigger-handling.shows-endpoint-default-model-status-not-configured.test.ts diff --git a/src/auto-reply/reply.triggers.trigger-handling.allows-activation-from-allowfrom-groups.test.ts b/src/auto-reply/reply.triggers.trigger-handling.allows-activation-from-allowfrom-groups.test.ts index ab83272e17a..3d97464c7d2 100644 --- a/src/auto-reply/reply.triggers.trigger-handling.allows-activation-from-allowfrom-groups.test.ts +++ b/src/auto-reply/reply.triggers.trigger-handling.allows-activation-from-allowfrom-groups.test.ts @@ -3,7 +3,6 @@ import { getRunEmbeddedPiAgentMock, installTriggerHandlingE2eTestHooks, makeCfg, - runGreetingPromptForBareNewOrReset, withTempHome, } from "./reply.triggers.trigger-handling.test-harness.js"; @@ -80,9 +79,4 @@ describe("trigger handling", () => { expect(extra).toContain("Activation: always-on"); }); }); - it("runs a greeting prompt for a bare /new", async () => { - await withTempHome(async (home) => { - await runGreetingPromptForBareNewOrReset({ home, body: "/new", getReplyFromConfig }); - }); - }); }); 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 c44d57ec104..daedca41038 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 @@ -6,7 +6,9 @@ import { installTriggerHandlingE2eTestHooks, loadGetReplyFromConfig, MAIN_SESSION_KEY, + makeCfg, makeWhatsAppElevatedCfg, + readSessionStore, requireSessionStorePath, withTempHome, } from "./reply.triggers.trigger-handling.test-harness.js"; @@ -74,4 +76,107 @@ describe("trigger handling", () => { expect(getRunEmbeddedPiAgentMock()).not.toHaveBeenCalled(); }); }); + + it("ignores inline elevated directive for unapproved sender", async () => { + await withTempHome(async (home) => { + getRunEmbeddedPiAgentMock().mockResolvedValue({ + payloads: [{ text: "ok" }], + meta: { + durationMs: 1, + agentMeta: { sessionId: "s", provider: "p", model: "m" }, + }, + }); + const cfg = makeWhatsAppElevatedCfg(home); + + const res = await getReplyFromConfig( + { + Body: "please /elevated on now", + From: "+2000", + To: "+2000", + Provider: "whatsapp", + SenderE164: "+2000", + }, + {}, + cfg, + ); + const text = Array.isArray(res) ? res[0]?.text : res?.text; + expect(text).not.toContain("elevated is not available right now"); + expect(getRunEmbeddedPiAgentMock()).toHaveBeenCalled(); + }); + }); + + it("uses tools.elevated.allowFrom.discord for elevated approval", async () => { + await withTempHome(async (home) => { + const cfg = makeCfg(home); + cfg.tools = { elevated: { allowFrom: { discord: ["123"] } } }; + + const res = await getReplyFromConfig( + { + Body: "/elevated on", + From: "discord:123", + To: "user:123", + Provider: "discord", + SenderName: "Peter Steinberger", + SenderUsername: "steipete", + SenderTag: "steipete", + CommandAuthorized: 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[MAIN_SESSION_KEY]?.elevatedLevel).toBe("on"); + }); + }); + + it("treats explicit discord elevated allowlist as override", async () => { + await withTempHome(async (home) => { + const cfg = makeCfg(home); + cfg.tools = { + elevated: { + allowFrom: { discord: [] }, + }, + }; + + const res = await getReplyFromConfig( + { + Body: "/elevated on", + From: "discord:123", + To: "user:123", + Provider: "discord", + SenderName: "steipete", + }, + {}, + cfg, + ); + const text = Array.isArray(res) ? res[0]?.text : res?.text; + expect(text).toContain("tools.elevated.allowFrom.discord"); + expect(getRunEmbeddedPiAgentMock()).not.toHaveBeenCalled(); + }); + }); + + it("returns a context overflow fallback when the embedded agent throws", async () => { + await withTempHome(async (home) => { + getRunEmbeddedPiAgentMock().mockRejectedValue(new Error("Context window exceeded")); + + const res = await getReplyFromConfig( + { + Body: "hello", + From: "+1002", + To: "+2000", + }, + {}, + makeCfg(home), + ); + + const text = Array.isArray(res) ? res[0]?.text : res?.text; + expect(text).toBe( + "⚠️ Context overflow — prompt too large for this model. Try a shorter message or a larger-context model.", + ); + expect(getRunEmbeddedPiAgentMock()).toHaveBeenCalledOnce(); + }); + }); }); 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 index 731c496be96..9b9d097f572 100644 --- 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 @@ -1,7 +1,6 @@ import { beforeAll, describe, expect, it } from "vitest"; import { loadSessionStore } from "../config/sessions.js"; import { - expectDirectElevatedToggleOn, installTriggerHandlingE2eTestHooks, loadGetReplyFromConfig, makeWhatsAppElevatedCfg, @@ -69,8 +68,4 @@ describe("trigger handling", () => { expect(store["agent:main:whatsapp:group:123@g.us"]?.elevatedLevel).toBe("on"); }); }); - - it("allows elevated directive in direct chats without mentions", async () => { - await expectDirectElevatedToggleOn({ getReplyFromConfig }); - }); }); 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 96fe1538cff..e6f179b5f28 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 @@ -2,6 +2,7 @@ import { readFile } 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 { createBlockReplyCollector, getProviderUsageMocks, @@ -19,6 +20,16 @@ beforeAll(async () => { installTriggerHandlingE2eTestHooks(); const usageMocks = getProviderUsageMocks(); +const modelStatusCtx = { + Body: "/model status", + From: "telegram:111", + To: "telegram:111", + ChatType: "direct", + Provider: "telegram", + Surface: "telegram", + SessionKey: "telegram:slash:111", + CommandAuthorized: true, +} as const; async function readSessionStore(home: string): Promise> { const raw = await readFile(join(home, "sessions.json"), "utf-8"); @@ -260,4 +271,95 @@ describe("trigger handling", () => { }); }); }); + + it("shows endpoint default in /model status when not configured", async () => { + await withTempHome(async (home) => { + const cfg = makeCfg(home); + const res = await getReplyFromConfig(modelStatusCtx, {}, cfg); + + const text = Array.isArray(res) ? res[0]?.text : res?.text; + expect(normalizeTestText(text ?? "")).toContain("endpoint: default"); + }); + }); + + it("includes endpoint details in /model status when configured", async () => { + await withTempHome(async (home) => { + const cfg = { + ...makeCfg(home), + models: { + providers: { + minimax: { + baseUrl: "https://api.minimax.io/anthropic", + api: "anthropic-messages", + }, + }, + }, + } as unknown as OpenClawConfig; + const res = await getReplyFromConfig(modelStatusCtx, {}, cfg); + + const text = Array.isArray(res) ? res[0]?.text : res?.text; + const normalized = normalizeTestText(text ?? ""); + expect(normalized).toContain( + "[minimax] endpoint: https://api.minimax.io/anthropic api: anthropic-messages auth:", + ); + }); + }); + + it("restarts by default", async () => { + await withTempHome(async (home) => { + const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); + const res = await getReplyFromConfig( + { + Body: " [Dec 5] /restart", + From: "+1001", + To: "+2000", + CommandAuthorized: true, + }, + {}, + makeCfg(home), + ); + const text = Array.isArray(res) ? res[0]?.text : res?.text; + expect(text?.startsWith("⚙️ Restarting") || text?.startsWith("⚠️ Restart failed")).toBe(true); + expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled(); + }); + }); + + it("rejects /restart when explicitly disabled", async () => { + await withTempHome(async (home) => { + const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); + const cfg = { ...makeCfg(home), commands: { restart: false } } as OpenClawConfig; + const res = await getReplyFromConfig( + { + Body: "/restart", + From: "+1001", + To: "+2000", + CommandAuthorized: true, + }, + {}, + cfg, + ); + const text = Array.isArray(res) ? res[0]?.text : res?.text; + expect(text).toContain("/restart is disabled"); + expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled(); + }); + }); + + it("reports status without invoking the agent", async () => { + await withTempHome(async (home) => { + const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); + const res = await getReplyFromConfig( + { + Body: "/status", + From: "+1002", + To: "+2000", + CommandAuthorized: true, + }, + {}, + makeCfg(home), + ); + const text = Array.isArray(res) ? res[0]?.text : res?.text; + expect(text).toContain("OpenClaw"); + 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 b3d1762d5a7..af10f07457b 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 @@ -72,6 +72,18 @@ describe("trigger handling", () => { }); }); + it("handles inline /help and strips it before the agent", async () => { + await withTempHome(async (home) => { + await expectInlineCommandHandledAndStripped({ + home, + getReplyFromConfig, + body: "please /help now", + stripToken: "/help", + blockReplyContains: "Help", + }); + }); + }); + it("drops /status for unauthorized senders", async () => { await withTempHome(async (home) => { await expectUnauthorizedCommandDropped(home, "/status"); diff --git a/src/auto-reply/reply.triggers.trigger-handling.ignores-inline-elevated-directive-unapproved-sender.test.ts b/src/auto-reply/reply.triggers.trigger-handling.ignores-inline-elevated-directive-unapproved-sender.test.ts deleted file mode 100644 index c8532b38bad..00000000000 --- a/src/auto-reply/reply.triggers.trigger-handling.ignores-inline-elevated-directive-unapproved-sender.test.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { beforeAll, describe, expect, it } from "vitest"; -import { - getRunEmbeddedPiAgentMock, - installTriggerHandlingE2eTestHooks, - loadGetReplyFromConfig, - MAIN_SESSION_KEY, - makeCfg, - makeWhatsAppElevatedCfg, - readSessionStore, - 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("ignores inline elevated directive for unapproved sender", async () => { - await withTempHome(async (home) => { - getRunEmbeddedPiAgentMock().mockResolvedValue({ - payloads: [{ text: "ok" }], - meta: { - durationMs: 1, - agentMeta: { sessionId: "s", provider: "p", model: "m" }, - }, - }); - const cfg = makeWhatsAppElevatedCfg(home); - - const res = await getReplyFromConfig( - { - Body: "please /elevated on now", - From: "+2000", - To: "+2000", - Provider: "whatsapp", - SenderE164: "+2000", - }, - {}, - cfg, - ); - const text = Array.isArray(res) ? res[0]?.text : res?.text; - expect(text).not.toContain("elevated is not available right now"); - expect(getRunEmbeddedPiAgentMock()).toHaveBeenCalled(); - }); - }); - it("uses tools.elevated.allowFrom.discord for elevated approval", async () => { - await withTempHome(async (home) => { - const cfg = makeCfg(home); - cfg.tools = { elevated: { allowFrom: { discord: ["123"] } } }; - - const res = await getReplyFromConfig( - { - Body: "/elevated on", - From: "discord:123", - To: "user:123", - Provider: "discord", - SenderName: "Peter Steinberger", - SenderUsername: "steipete", - SenderTag: "steipete", - CommandAuthorized: 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[MAIN_SESSION_KEY]?.elevatedLevel).toBe("on"); - }); - }); - it("treats explicit discord elevated allowlist as override", async () => { - await withTempHome(async (home) => { - const cfg = makeCfg(home); - cfg.tools = { - elevated: { - allowFrom: { discord: [] }, - }, - }; - - const res = await getReplyFromConfig( - { - Body: "/elevated on", - From: "discord:123", - To: "user:123", - Provider: "discord", - SenderName: "steipete", - }, - {}, - cfg, - ); - const text = Array.isArray(res) ? res[0]?.text : res?.text; - expect(text).toContain("tools.elevated.allowFrom.discord"); - expect(getRunEmbeddedPiAgentMock()).not.toHaveBeenCalled(); - }); - }); - it("returns a context overflow fallback when the embedded agent throws", async () => { - await withTempHome(async (home) => { - getRunEmbeddedPiAgentMock().mockRejectedValue(new Error("Context window exceeded")); - - const res = await getReplyFromConfig( - { - Body: "hello", - From: "+1002", - To: "+2000", - }, - {}, - makeCfg(home), - ); - - const text = Array.isArray(res) ? res[0]?.text : res?.text; - expect(text).toBe( - "⚠️ Context overflow — prompt too large for this model. Try a shorter message or a larger-context model.", - ); - expect(getRunEmbeddedPiAgentMock()).toHaveBeenCalledOnce(); - }); - }); -}); 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 index 52172b3ea98..cc6c83cf956 100644 --- 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 @@ -3,13 +3,10 @@ import { join } from "node:path"; import { beforeAll, describe, expect, it } from "vitest"; import { resolveSessionKey } from "../config/sessions.js"; import { - createBlockReplyCollector, - expectInlineCommandHandledAndStripped, getRunEmbeddedPiAgentMock, installTriggerHandlingE2eTestHooks, loadGetReplyFromConfig, makeCfg, - mockRunEmbeddedPiAgentOk, requireSessionStorePath, withTempHome, } from "./reply.triggers.trigger-handling.test-harness.js"; @@ -87,43 +84,4 @@ describe("trigger handling", () => { expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled(); }); }); - - it("strips inline /status and still runs the agent", async () => { - await withTempHome(async (home) => { - const runEmbeddedPiAgentMock = mockRunEmbeddedPiAgentOk(); - const { blockReplies, handlers } = createBlockReplyCollector(); - await getReplyFromConfig( - { - Body: "please /status now", - From: "+1002", - To: "+2000", - Provider: "whatsapp", - Surface: "whatsapp", - SenderE164: "+1002", - CommandAuthorized: true, - }, - handlers, - makeCfg(home), - ); - expect(runEmbeddedPiAgentMock).toHaveBeenCalled(); - // Allowlisted senders: inline /status runs immediately (like /help) and is - // stripped from the prompt; the remaining text continues through the agent. - expect(blockReplies.length).toBe(1); - expect(String(blockReplies[0]?.text ?? "").length).toBeGreaterThan(0); - const prompt = runEmbeddedPiAgentMock.mock.calls[0]?.[0]?.prompt ?? ""; - expect(prompt).not.toContain("/status"); - }); - }); - - it("handles inline /help and strips it before the agent", async () => { - await withTempHome(async (home) => { - await expectInlineCommandHandledAndStripped({ - home, - getReplyFromConfig, - body: "please /help now", - stripToken: "/help", - blockReplyContains: "Help", - }); - }); - }); }); diff --git a/src/auto-reply/reply.triggers.trigger-handling.runs-greeting-prompt-bare-reset.test.ts b/src/auto-reply/reply.triggers.trigger-handling.runs-greeting-prompt-bare-reset.test.ts index c9ec9d02975..916308069d4 100644 --- a/src/auto-reply/reply.triggers.trigger-handling.runs-greeting-prompt-bare-reset.test.ts +++ b/src/auto-reply/reply.triggers.trigger-handling.runs-greeting-prompt-bare-reset.test.ts @@ -52,6 +52,11 @@ describe("trigger handling", () => { await runGreetingPromptForBareNewOrReset({ home, body: "/reset", getReplyFromConfig }); }); }); + it("runs a greeting prompt for a bare /new", async () => { + await withTempHome(async (home) => { + await runGreetingPromptForBareNewOrReset({ home, body: "/new", getReplyFromConfig }); + }); + }); it("does not reset for unauthorized /reset", async () => { await withTempHome(async (home) => { await expectResetBlockedForNonOwner({ diff --git a/src/auto-reply/reply.triggers.trigger-handling.shows-endpoint-default-model-status-not-configured.test.ts b/src/auto-reply/reply.triggers.trigger-handling.shows-endpoint-default-model-status-not-configured.test.ts deleted file mode 100644 index d68b414d0f3..00000000000 --- a/src/auto-reply/reply.triggers.trigger-handling.shows-endpoint-default-model-status-not-configured.test.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { beforeAll, describe, expect, it } from "vitest"; -import { normalizeTestText } from "../../test/helpers/normalize-text.js"; -import type { OpenClawConfig } from "../config/config.js"; -import { - getRunEmbeddedPiAgentMock, - installTriggerHandlingE2eTestHooks, - 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(); - -const modelStatusCtx = { - Body: "/model status", - From: "telegram:111", - To: "telegram:111", - ChatType: "direct", - Provider: "telegram", - Surface: "telegram", - SessionKey: "telegram:slash:111", - CommandAuthorized: true, -} as const; - -describe("trigger handling", () => { - it("shows endpoint default in /model status when not configured", async () => { - await withTempHome(async (home) => { - const cfg = makeCfg(home); - const res = await getReplyFromConfig(modelStatusCtx, {}, cfg); - - const text = Array.isArray(res) ? res[0]?.text : res?.text; - expect(normalizeTestText(text ?? "")).toContain("endpoint: default"); - }); - }); - it("includes endpoint details in /model status when configured", async () => { - await withTempHome(async (home) => { - const cfg = { - ...makeCfg(home), - models: { - providers: { - minimax: { - baseUrl: "https://api.minimax.io/anthropic", - api: "anthropic-messages", - }, - }, - }, - } as unknown as OpenClawConfig; - const res = await getReplyFromConfig(modelStatusCtx, {}, cfg); - - const text = Array.isArray(res) ? res[0]?.text : res?.text; - const normalized = normalizeTestText(text ?? ""); - expect(normalized).toContain( - "[minimax] endpoint: https://api.minimax.io/anthropic api: anthropic-messages auth:", - ); - }); - }); - it("restarts by default", async () => { - await withTempHome(async (home) => { - const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); - const res = await getReplyFromConfig( - { - Body: " [Dec 5] /restart", - From: "+1001", - To: "+2000", - CommandAuthorized: true, - }, - {}, - makeCfg(home), - ); - const text = Array.isArray(res) ? res[0]?.text : res?.text; - expect(text?.startsWith("⚙️ Restarting") || text?.startsWith("⚠️ Restart failed")).toBe(true); - expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled(); - }); - }); - it("rejects /restart when explicitly disabled", async () => { - await withTempHome(async (home) => { - const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); - const cfg = { ...makeCfg(home), commands: { restart: false } } as OpenClawConfig; - const res = await getReplyFromConfig( - { - Body: "/restart", - From: "+1001", - To: "+2000", - CommandAuthorized: true, - }, - {}, - cfg, - ); - const text = Array.isArray(res) ? res[0]?.text : res?.text; - expect(text).toContain("/restart is disabled"); - expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled(); - }); - }); - it("reports status without invoking the agent", async () => { - await withTempHome(async (home) => { - const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); - const res = await getReplyFromConfig( - { - Body: "/status", - From: "+1002", - To: "+2000", - CommandAuthorized: true, - }, - {}, - makeCfg(home), - ); - const text = Array.isArray(res) ? res[0]?.text : res?.text; - expect(text).toContain("OpenClaw"); - expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled(); - }); - }); -});