From 74294a4653425097389f43f3b9a44d6436a478f0 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 15 Feb 2026 22:01:40 +0000 Subject: [PATCH] perf(test): consolidate web auto-reply suites --- .../auto-reply/monitor/message-line.test.ts | 82 -------------- src/web/auto-reply/session-snapshot.test.ts | 47 -------- src/web/auto-reply/util.test.ts | 60 ---------- ...test.ts => web-auto-reply-monitor.test.ts} | 91 ++++++++++++++- ...s.test.ts => web-auto-reply-utils.test.ts} | 104 +++++++++++++++++- 5 files changed, 189 insertions(+), 195 deletions(-) delete mode 100644 src/web/auto-reply/monitor/message-line.test.ts delete mode 100644 src/web/auto-reply/session-snapshot.test.ts delete mode 100644 src/web/auto-reply/util.test.ts rename src/web/auto-reply/{monitor/group-gating.test.ts => web-auto-reply-monitor.test.ts} (76%) rename src/web/auto-reply/{mentions.test.ts => web-auto-reply-utils.test.ts} (51%) diff --git a/src/web/auto-reply/monitor/message-line.test.ts b/src/web/auto-reply/monitor/message-line.test.ts deleted file mode 100644 index 4fad746d407..00000000000 --- a/src/web/auto-reply/monitor/message-line.test.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { buildInboundLine, formatReplyContext } from "./message-line.js"; - -describe("buildInboundLine", () => { - it("prefixes group messages with sender", () => { - const line = buildInboundLine({ - cfg: { - agents: { defaults: { workspace: "/tmp/openclaw" } }, - channels: { whatsapp: { messagePrefix: "" } }, - } as never, - agentId: "main", - msg: { - from: "123@g.us", - conversationId: "123@g.us", - to: "+15550009999", - accountId: "default", - body: "ping", - timestamp: 1700000000000, - chatType: "group", - chatId: "123@g.us", - senderJid: "111@s.whatsapp.net", - senderE164: "+15550001111", - senderName: "Bob", - sendComposing: async () => undefined, - reply: async () => undefined, - sendMedia: async () => undefined, - } as never, - }); - - expect(line).toContain("Bob (+15550001111):"); - expect(line).toContain("ping"); - }); - - it("includes reply-to context blocks when replyToBody is present", () => { - const line = buildInboundLine({ - cfg: { - agents: { defaults: { workspace: "/tmp/openclaw" } }, - channels: { whatsapp: { messagePrefix: "" } }, - } as never, - agentId: "main", - msg: { - from: "+1555", - to: "+1555", - body: "hello", - chatType: "direct", - replyToId: "q1", - replyToBody: "original", - replyToSender: "+1999", - } as never, - envelope: { includeTimestamp: false }, - }); - - expect(line).toContain("[Replying to +1999 id:q1]"); - expect(line).toContain("original"); - expect(line).toContain("[/Replying]"); - }); - - it("applies the WhatsApp messagePrefix when configured", () => { - const line = buildInboundLine({ - cfg: { - agents: { defaults: { workspace: "/tmp/openclaw" } }, - channels: { whatsapp: { messagePrefix: "[PFX]" } }, - } as never, - agentId: "main", - msg: { - from: "+1555", - to: "+2666", - body: "ping", - chatType: "direct", - } as never, - envelope: { includeTimestamp: false }, - }); - - expect(line).toContain("[PFX] ping"); - }); -}); - -describe("formatReplyContext", () => { - it("returns null when replyToBody is missing", () => { - expect(formatReplyContext({} as never)).toBeNull(); - }); -}); diff --git a/src/web/auto-reply/session-snapshot.test.ts b/src/web/auto-reply/session-snapshot.test.ts deleted file mode 100644 index 1f9d6dfc9f4..00000000000 --- a/src/web/auto-reply/session-snapshot.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import fs from "node:fs/promises"; -import os from "node:os"; -import path from "node:path"; -import { describe, expect, it, vi } from "vitest"; -import { saveSessionStore } from "../../config/sessions.js"; -import { getSessionSnapshot } from "./session-snapshot.js"; - -describe("getSessionSnapshot", () => { - it("uses channel reset overrides when configured", async () => { - vi.useFakeTimers(); - vi.setSystemTime(new Date(2026, 0, 18, 5, 0, 0)); - try { - const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-snapshot-")); - const storePath = path.join(root, "sessions.json"); - const sessionKey = "agent:main:whatsapp:dm:s1"; - - await saveSessionStore(storePath, { - [sessionKey]: { - sessionId: "snapshot-session", - updatedAt: new Date(2026, 0, 18, 3, 30, 0).getTime(), - lastChannel: "whatsapp", - }, - }); - - const cfg = { - session: { - store: storePath, - reset: { mode: "daily", atHour: 4, idleMinutes: 240 }, - resetByChannel: { - whatsapp: { mode: "idle", idleMinutes: 360 }, - }, - }, - } as Parameters[0]; - - const snapshot = getSessionSnapshot(cfg, "whatsapp:+15550001111", true, { - sessionKey, - }); - - expect(snapshot.resetPolicy.mode).toBe("idle"); - expect(snapshot.resetPolicy.idleMinutes).toBe(360); - expect(snapshot.fresh).toBe(true); - expect(snapshot.dailyResetAt).toBeUndefined(); - } finally { - vi.useRealTimers(); - } - }); -}); diff --git a/src/web/auto-reply/util.test.ts b/src/web/auto-reply/util.test.ts deleted file mode 100644 index a9327657d32..00000000000 --- a/src/web/auto-reply/util.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { elide, isLikelyWhatsAppCryptoError } from "./util.js"; - -describe("web auto-reply util", () => { - describe("elide", () => { - it("returns undefined for undefined input", () => { - expect(elide(undefined)).toBe(undefined); - }); - - it("returns input when under limit", () => { - expect(elide("hi", 10)).toBe("hi"); - }); - - it("returns input when exactly at limit", () => { - expect(elide("12345", 5)).toBe("12345"); - }); - - it("truncates and annotates when over limit", () => { - expect(elide("abcdef", 3)).toBe("abc… (truncated 3 chars)"); - }); - }); - - describe("isLikelyWhatsAppCryptoError", () => { - it("returns false for non-matching reasons", () => { - expect(isLikelyWhatsAppCryptoError(new Error("boom"))).toBe(false); - expect(isLikelyWhatsAppCryptoError("boom")).toBe(false); - expect(isLikelyWhatsAppCryptoError({ message: "bad mac" })).toBe(false); - }); - - it("matches known Baileys crypto auth errors (string)", () => { - expect( - isLikelyWhatsAppCryptoError( - "baileys: unsupported state or unable to authenticate data (noise-handler)", - ), - ).toBe(true); - expect(isLikelyWhatsAppCryptoError("bad mac in aesDecryptGCM (baileys)")).toBe(true); - }); - - it("matches known Baileys crypto auth errors (Error)", () => { - const err = new Error("bad mac"); - err.stack = "at something\nat @whiskeysockets/baileys/noise-handler\n"; - expect(isLikelyWhatsAppCryptoError(err)).toBe(true); - }); - - it("does not throw on circular objects", () => { - const circular: Record = {}; - circular.self = circular; - expect(isLikelyWhatsAppCryptoError(circular)).toBe(false); - }); - - it("handles non-string reasons without throwing", () => { - expect(isLikelyWhatsAppCryptoError(null)).toBe(false); - expect(isLikelyWhatsAppCryptoError(123)).toBe(false); - expect(isLikelyWhatsAppCryptoError(true)).toBe(false); - expect(isLikelyWhatsAppCryptoError(123n)).toBe(false); - expect(isLikelyWhatsAppCryptoError(Symbol("bad mac"))).toBe(false); - expect(isLikelyWhatsAppCryptoError(function namedFn() {})).toBe(false); - }); - }); -}); diff --git a/src/web/auto-reply/monitor/group-gating.test.ts b/src/web/auto-reply/web-auto-reply-monitor.test.ts similarity index 76% rename from src/web/auto-reply/monitor/group-gating.test.ts rename to src/web/auto-reply/web-auto-reply-monitor.test.ts index 74bce52197a..766bd0f932e 100644 --- a/src/web/auto-reply/monitor/group-gating.test.ts +++ b/src/web/auto-reply/web-auto-reply-monitor.test.ts @@ -2,9 +2,10 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; -import { resolveAgentRoute } from "../../../routing/resolve-route.js"; -import { buildMentionConfig } from "../mentions.js"; -import { applyGroupGating } from "./group-gating.js"; +import { resolveAgentRoute } from "../../routing/resolve-route.js"; +import { buildMentionConfig } from "./mentions.js"; +import { applyGroupGating } from "./monitor/group-gating.js"; +import { buildInboundLine, formatReplyContext } from "./monitor/message-line.js"; let sessionDir: string | undefined; let sessionStorePath: string; @@ -32,10 +33,10 @@ const makeConfig = (overrides: Record) => }, session: { store: sessionStorePath }, ...overrides, - }) as unknown as ReturnType; + }) as unknown as ReturnType; function runGroupGating(params: { - cfg: ReturnType; + cfg: ReturnType; msg: Record; conversationId?: string; agentId?: string; @@ -340,3 +341,83 @@ describe("applyGroupGating", () => { expect(result.shouldProcess).toBe(false); }); }); + +describe("buildInboundLine", () => { + it("prefixes group messages with sender", () => { + const line = buildInboundLine({ + cfg: { + agents: { defaults: { workspace: "/tmp/openclaw" } }, + channels: { whatsapp: { messagePrefix: "" } }, + } as never, + agentId: "main", + msg: { + from: "123@g.us", + conversationId: "123@g.us", + to: "+15550009999", + accountId: "default", + body: "ping", + timestamp: 1700000000000, + chatType: "group", + chatId: "123@g.us", + senderJid: "111@s.whatsapp.net", + senderE164: "+15550001111", + senderName: "Bob", + sendComposing: async () => undefined, + reply: async () => undefined, + sendMedia: async () => undefined, + } as never, + }); + + expect(line).toContain("Bob (+15550001111):"); + expect(line).toContain("ping"); + }); + + it("includes reply-to context blocks when replyToBody is present", () => { + const line = buildInboundLine({ + cfg: { + agents: { defaults: { workspace: "/tmp/openclaw" } }, + channels: { whatsapp: { messagePrefix: "" } }, + } as never, + agentId: "main", + msg: { + from: "+1555", + to: "+1555", + body: "hello", + chatType: "direct", + replyToId: "q1", + replyToBody: "original", + replyToSender: "+1999", + } as never, + envelope: { includeTimestamp: false }, + }); + + expect(line).toContain("[Replying to +1999 id:q1]"); + expect(line).toContain("original"); + expect(line).toContain("[/Replying]"); + }); + + it("applies the WhatsApp messagePrefix when configured", () => { + const line = buildInboundLine({ + cfg: { + agents: { defaults: { workspace: "/tmp/openclaw" } }, + channels: { whatsapp: { messagePrefix: "[PFX]" } }, + } as never, + agentId: "main", + msg: { + from: "+1555", + to: "+2666", + body: "ping", + chatType: "direct", + } as never, + envelope: { includeTimestamp: false }, + }); + + expect(line).toContain("[PFX] ping"); + }); +}); + +describe("formatReplyContext", () => { + it("returns null when replyToBody is missing", () => { + expect(formatReplyContext({} as never)).toBeNull(); + }); +}); diff --git a/src/web/auto-reply/mentions.test.ts b/src/web/auto-reply/web-auto-reply-utils.test.ts similarity index 51% rename from src/web/auto-reply/mentions.test.ts rename to src/web/auto-reply/web-auto-reply-utils.test.ts index 27e4f426bc3..cd35a9a2374 100644 --- a/src/web/auto-reply/mentions.test.ts +++ b/src/web/auto-reply/web-auto-reply-utils.test.ts @@ -1,9 +1,12 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import type { WebInboundMsg } from "./types.js"; +import { saveSessionStore } from "../../config/sessions.js"; import { isBotMentionedFromTargets, resolveMentionTargets } from "./mentions.js"; +import { getSessionSnapshot } from "./session-snapshot.js"; +import { elide, isLikelyWhatsAppCryptoError } from "./util.js"; const makeMsg = (overrides: Partial): WebInboundMsg => ({ @@ -116,3 +119,102 @@ describe("resolveMentionTargets with @lid mapping", () => { } }); }); + +describe("getSessionSnapshot", () => { + it("uses channel reset overrides when configured", async () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date(2026, 0, 18, 5, 0, 0)); + try { + const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-snapshot-")); + const storePath = path.join(root, "sessions.json"); + const sessionKey = "agent:main:whatsapp:dm:s1"; + + await saveSessionStore(storePath, { + [sessionKey]: { + sessionId: "snapshot-session", + updatedAt: new Date(2026, 0, 18, 3, 30, 0).getTime(), + lastChannel: "whatsapp", + }, + }); + + const cfg = { + session: { + store: storePath, + reset: { mode: "daily", atHour: 4, idleMinutes: 240 }, + resetByChannel: { + whatsapp: { mode: "idle", idleMinutes: 360 }, + }, + }, + } as Parameters[0]; + + const snapshot = getSessionSnapshot(cfg, "whatsapp:+15550001111", true, { + sessionKey, + }); + + expect(snapshot.resetPolicy.mode).toBe("idle"); + expect(snapshot.resetPolicy.idleMinutes).toBe(360); + expect(snapshot.fresh).toBe(true); + expect(snapshot.dailyResetAt).toBeUndefined(); + } finally { + vi.useRealTimers(); + } + }); +}); + +describe("web auto-reply util", () => { + describe("elide", () => { + it("returns undefined for undefined input", () => { + expect(elide(undefined)).toBe(undefined); + }); + + it("returns input when under limit", () => { + expect(elide("hi", 10)).toBe("hi"); + }); + + it("returns input when exactly at limit", () => { + expect(elide("12345", 5)).toBe("12345"); + }); + + it("truncates and annotates when over limit", () => { + expect(elide("abcdef", 3)).toBe("abc… (truncated 3 chars)"); + }); + }); + + describe("isLikelyWhatsAppCryptoError", () => { + it("returns false for non-matching reasons", () => { + expect(isLikelyWhatsAppCryptoError(new Error("boom"))).toBe(false); + expect(isLikelyWhatsAppCryptoError("boom")).toBe(false); + expect(isLikelyWhatsAppCryptoError({ message: "bad mac" })).toBe(false); + }); + + it("matches known Baileys crypto auth errors (string)", () => { + expect( + isLikelyWhatsAppCryptoError( + "baileys: unsupported state or unable to authenticate data (noise-handler)", + ), + ).toBe(true); + expect(isLikelyWhatsAppCryptoError("bad mac in aesDecryptGCM (baileys)")).toBe(true); + }); + + it("matches known Baileys crypto auth errors (Error)", () => { + const err = new Error("bad mac"); + err.stack = "at something\nat @whiskeysockets/baileys/noise-handler\n"; + expect(isLikelyWhatsAppCryptoError(err)).toBe(true); + }); + + it("does not throw on circular objects", () => { + const circular: Record = {}; + circular.self = circular; + expect(isLikelyWhatsAppCryptoError(circular)).toBe(false); + }); + + it("handles non-string reasons without throwing", () => { + expect(isLikelyWhatsAppCryptoError(null)).toBe(false); + expect(isLikelyWhatsAppCryptoError(123)).toBe(false); + expect(isLikelyWhatsAppCryptoError(true)).toBe(false); + expect(isLikelyWhatsAppCryptoError(123n)).toBe(false); + expect(isLikelyWhatsAppCryptoError(Symbol("bad mac"))).toBe(false); + expect(isLikelyWhatsAppCryptoError(function namedFn() {})).toBe(false); + }); + }); +});