perf(test): consolidate web auto-reply suites

This commit is contained in:
Peter Steinberger
2026-02-15 22:01:40 +00:00
parent c59a472ca2
commit 74294a4653
5 changed files with 189 additions and 195 deletions

View File

@@ -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();
});
});

View File

@@ -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<typeof getSessionSnapshot>[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();
}
});
});

View File

@@ -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<string, unknown> = {};
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);
});
});
});

View File

@@ -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<string, unknown>) =>
},
session: { store: sessionStorePath },
...overrides,
}) as unknown as ReturnType<typeof import("../../../config/config.js").loadConfig>;
}) as unknown as ReturnType<typeof import("../../config/config.js").loadConfig>;
function runGroupGating(params: {
cfg: ReturnType<typeof import("../../../config/config.js").loadConfig>;
cfg: ReturnType<typeof import("../../config/config.js").loadConfig>;
msg: Record<string, unknown>;
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();
});
});

View File

@@ -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>): 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<typeof getSessionSnapshot>[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<string, unknown> = {};
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);
});
});
});