mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-25 23:47:20 +00:00
perf(test): consolidate web auto-reply suites
This commit is contained in:
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user