mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 21:54:31 +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 os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||||
import { resolveAgentRoute } from "../../../routing/resolve-route.js";
|
import { resolveAgentRoute } from "../../routing/resolve-route.js";
|
||||||
import { buildMentionConfig } from "../mentions.js";
|
import { buildMentionConfig } from "./mentions.js";
|
||||||
import { applyGroupGating } from "./group-gating.js";
|
import { applyGroupGating } from "./monitor/group-gating.js";
|
||||||
|
import { buildInboundLine, formatReplyContext } from "./monitor/message-line.js";
|
||||||
|
|
||||||
let sessionDir: string | undefined;
|
let sessionDir: string | undefined;
|
||||||
let sessionStorePath: string;
|
let sessionStorePath: string;
|
||||||
@@ -32,10 +33,10 @@ const makeConfig = (overrides: Record<string, unknown>) =>
|
|||||||
},
|
},
|
||||||
session: { store: sessionStorePath },
|
session: { store: sessionStorePath },
|
||||||
...overrides,
|
...overrides,
|
||||||
}) as unknown as ReturnType<typeof import("../../../config/config.js").loadConfig>;
|
}) as unknown as ReturnType<typeof import("../../config/config.js").loadConfig>;
|
||||||
|
|
||||||
function runGroupGating(params: {
|
function runGroupGating(params: {
|
||||||
cfg: ReturnType<typeof import("../../../config/config.js").loadConfig>;
|
cfg: ReturnType<typeof import("../../config/config.js").loadConfig>;
|
||||||
msg: Record<string, unknown>;
|
msg: Record<string, unknown>;
|
||||||
conversationId?: string;
|
conversationId?: string;
|
||||||
agentId?: string;
|
agentId?: string;
|
||||||
@@ -340,3 +341,83 @@ describe("applyGroupGating", () => {
|
|||||||
expect(result.shouldProcess).toBe(false);
|
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 fs from "node:fs/promises";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
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 type { WebInboundMsg } from "./types.js";
|
||||||
|
import { saveSessionStore } from "../../config/sessions.js";
|
||||||
import { isBotMentionedFromTargets, resolveMentionTargets } from "./mentions.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 =>
|
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