mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 09:21:26 +00:00
test: optimize gateway infra memory and security coverage
This commit is contained in:
@@ -161,17 +161,22 @@ describe("delivery-queue", () => {
|
||||
});
|
||||
|
||||
describe("computeBackoffMs", () => {
|
||||
it("returns 0 for retryCount 0", () => {
|
||||
expect(computeBackoffMs(0)).toBe(0);
|
||||
});
|
||||
it("returns scheduled backoff values and clamps at max retry", () => {
|
||||
const cases = [
|
||||
{ retryCount: 0, expected: 0 },
|
||||
{ retryCount: 1, expected: 5_000 },
|
||||
{ retryCount: 2, expected: 25_000 },
|
||||
{ retryCount: 3, expected: 120_000 },
|
||||
{ retryCount: 4, expected: 600_000 },
|
||||
// Beyond defined schedule -- clamps to last value.
|
||||
{ retryCount: 5, expected: 600_000 },
|
||||
] as const;
|
||||
|
||||
it("returns correct backoff for each retry", () => {
|
||||
expect(computeBackoffMs(1)).toBe(5_000);
|
||||
expect(computeBackoffMs(2)).toBe(25_000);
|
||||
expect(computeBackoffMs(3)).toBe(120_000);
|
||||
expect(computeBackoffMs(4)).toBe(600_000);
|
||||
// Beyond defined schedule -- clamps to last value.
|
||||
expect(computeBackoffMs(5)).toBe(600_000);
|
||||
for (const testCase of cases) {
|
||||
expect(computeBackoffMs(testCase.retryCount), String(testCase.retryCount)).toBe(
|
||||
testCase.expected,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -383,28 +388,36 @@ describe("DirectoryCache", () => {
|
||||
expect(cache.get("a", cfg)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("evicts oldest keys when max size is exceeded", () => {
|
||||
const cache = new DirectoryCache<string>(60_000, 2);
|
||||
cache.set("a", "value-a", cfg);
|
||||
cache.set("b", "value-b", cfg);
|
||||
cache.set("c", "value-c", cfg);
|
||||
it("evicts least-recent entries when capacity is exceeded", () => {
|
||||
const cases = [
|
||||
{
|
||||
actions: [
|
||||
["set", "a", "value-a"],
|
||||
["set", "b", "value-b"],
|
||||
["set", "c", "value-c"],
|
||||
] as const,
|
||||
expected: { a: undefined, b: "value-b", c: "value-c" },
|
||||
},
|
||||
{
|
||||
actions: [
|
||||
["set", "a", "value-a"],
|
||||
["set", "b", "value-b"],
|
||||
["set", "a", "value-a2"],
|
||||
["set", "c", "value-c"],
|
||||
] as const,
|
||||
expected: { a: "value-a2", b: undefined, c: "value-c" },
|
||||
},
|
||||
] as const;
|
||||
|
||||
expect(cache.get("a", cfg)).toBeUndefined();
|
||||
expect(cache.get("b", cfg)).toBe("value-b");
|
||||
expect(cache.get("c", cfg)).toBe("value-c");
|
||||
});
|
||||
|
||||
it("refreshes insertion order on key updates", () => {
|
||||
const cache = new DirectoryCache<string>(60_000, 2);
|
||||
cache.set("a", "value-a", cfg);
|
||||
cache.set("b", "value-b", cfg);
|
||||
cache.set("a", "value-a2", cfg);
|
||||
cache.set("c", "value-c", cfg);
|
||||
|
||||
// Updating "a" should keep it and evict older "b".
|
||||
expect(cache.get("a", cfg)).toBe("value-a2");
|
||||
expect(cache.get("b", cfg)).toBeUndefined();
|
||||
expect(cache.get("c", cfg)).toBe("value-c");
|
||||
for (const testCase of cases) {
|
||||
const cache = new DirectoryCache<string>(60_000, 2);
|
||||
for (const action of testCase.actions) {
|
||||
cache.set(action[1], action[2], cfg);
|
||||
}
|
||||
expect(cache.get("a", cfg)).toBe(testCase.expected.a);
|
||||
expect(cache.get("b", cfg)).toBe(testCase.expected.b);
|
||||
expect(cache.get("c", cfg)).toBe(testCase.expected.c);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -470,103 +483,128 @@ describe("buildOutboundResultEnvelope", () => {
|
||||
});
|
||||
|
||||
describe("formatOutboundDeliverySummary", () => {
|
||||
it("falls back when result is missing", () => {
|
||||
expect(formatOutboundDeliverySummary("telegram")).toBe(
|
||||
"✅ Sent via Telegram. Message ID: unknown",
|
||||
);
|
||||
expect(formatOutboundDeliverySummary("imessage")).toBe(
|
||||
"✅ Sent via iMessage. Message ID: unknown",
|
||||
);
|
||||
});
|
||||
it("formats fallback and channel-specific detail variants", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "fallback telegram",
|
||||
channel: "telegram" as const,
|
||||
result: undefined,
|
||||
expected: "✅ Sent via Telegram. Message ID: unknown",
|
||||
},
|
||||
{
|
||||
name: "fallback imessage",
|
||||
channel: "imessage" as const,
|
||||
result: undefined,
|
||||
expected: "✅ Sent via iMessage. Message ID: unknown",
|
||||
},
|
||||
{
|
||||
name: "telegram with chat detail",
|
||||
channel: "telegram" as const,
|
||||
result: {
|
||||
channel: "telegram" as const,
|
||||
messageId: "m1",
|
||||
chatId: "c1",
|
||||
},
|
||||
expected: "✅ Sent via Telegram. Message ID: m1 (chat c1)",
|
||||
},
|
||||
{
|
||||
name: "discord with channel detail",
|
||||
channel: "discord" as const,
|
||||
result: {
|
||||
channel: "discord" as const,
|
||||
messageId: "d1",
|
||||
channelId: "chan",
|
||||
},
|
||||
expected: "✅ Sent via Discord. Message ID: d1 (channel chan)",
|
||||
},
|
||||
] as const;
|
||||
|
||||
it("adds chat or channel details", () => {
|
||||
expect(
|
||||
formatOutboundDeliverySummary("telegram", {
|
||||
channel: "telegram",
|
||||
messageId: "m1",
|
||||
chatId: "c1",
|
||||
}),
|
||||
).toBe("✅ Sent via Telegram. Message ID: m1 (chat c1)");
|
||||
|
||||
expect(
|
||||
formatOutboundDeliverySummary("discord", {
|
||||
channel: "discord",
|
||||
messageId: "d1",
|
||||
channelId: "chan",
|
||||
}),
|
||||
).toBe("✅ Sent via Discord. Message ID: d1 (channel chan)");
|
||||
for (const testCase of cases) {
|
||||
expect(formatOutboundDeliverySummary(testCase.channel, testCase.result), testCase.name).toBe(
|
||||
testCase.expected,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildOutboundDeliveryJson", () => {
|
||||
it("builds direct delivery payloads", () => {
|
||||
expect(
|
||||
buildOutboundDeliveryJson({
|
||||
channel: "telegram",
|
||||
to: "123",
|
||||
result: { channel: "telegram", messageId: "m1", chatId: "c1" },
|
||||
mediaUrl: "https://example.com/a.png",
|
||||
}),
|
||||
).toEqual({
|
||||
channel: "telegram",
|
||||
via: "direct",
|
||||
to: "123",
|
||||
messageId: "m1",
|
||||
mediaUrl: "https://example.com/a.png",
|
||||
chatId: "c1",
|
||||
});
|
||||
});
|
||||
it("builds direct delivery payloads across provider-specific fields", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "telegram direct payload",
|
||||
input: {
|
||||
channel: "telegram" as const,
|
||||
to: "123",
|
||||
result: { channel: "telegram" as const, messageId: "m1", chatId: "c1" },
|
||||
mediaUrl: "https://example.com/a.png",
|
||||
},
|
||||
expected: {
|
||||
channel: "telegram",
|
||||
via: "direct",
|
||||
to: "123",
|
||||
messageId: "m1",
|
||||
mediaUrl: "https://example.com/a.png",
|
||||
chatId: "c1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "whatsapp metadata",
|
||||
input: {
|
||||
channel: "whatsapp" as const,
|
||||
to: "+1",
|
||||
result: { channel: "whatsapp" as const, messageId: "w1", toJid: "jid" },
|
||||
},
|
||||
expected: {
|
||||
channel: "whatsapp",
|
||||
via: "direct",
|
||||
to: "+1",
|
||||
messageId: "w1",
|
||||
mediaUrl: null,
|
||||
toJid: "jid",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "signal timestamp",
|
||||
input: {
|
||||
channel: "signal" as const,
|
||||
to: "+1",
|
||||
result: { channel: "signal" as const, messageId: "s1", timestamp: 123 },
|
||||
},
|
||||
expected: {
|
||||
channel: "signal",
|
||||
via: "direct",
|
||||
to: "+1",
|
||||
messageId: "s1",
|
||||
mediaUrl: null,
|
||||
timestamp: 123,
|
||||
},
|
||||
},
|
||||
] as const;
|
||||
|
||||
it("supports whatsapp metadata when present", () => {
|
||||
expect(
|
||||
buildOutboundDeliveryJson({
|
||||
channel: "whatsapp",
|
||||
to: "+1",
|
||||
result: { channel: "whatsapp", messageId: "w1", toJid: "jid" },
|
||||
}),
|
||||
).toEqual({
|
||||
channel: "whatsapp",
|
||||
via: "direct",
|
||||
to: "+1",
|
||||
messageId: "w1",
|
||||
mediaUrl: null,
|
||||
toJid: "jid",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps timestamp for signal", () => {
|
||||
expect(
|
||||
buildOutboundDeliveryJson({
|
||||
channel: "signal",
|
||||
to: "+1",
|
||||
result: { channel: "signal", messageId: "s1", timestamp: 123 },
|
||||
}),
|
||||
).toEqual({
|
||||
channel: "signal",
|
||||
via: "direct",
|
||||
to: "+1",
|
||||
messageId: "s1",
|
||||
mediaUrl: null,
|
||||
timestamp: 123,
|
||||
});
|
||||
for (const testCase of cases) {
|
||||
expect(buildOutboundDeliveryJson(testCase.input), testCase.name).toEqual(testCase.expected);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatGatewaySummary", () => {
|
||||
it("formats gateway summaries with channel", () => {
|
||||
expect(formatGatewaySummary({ channel: "whatsapp", messageId: "m1" })).toBe(
|
||||
"✅ Sent via gateway (whatsapp). Message ID: m1",
|
||||
);
|
||||
});
|
||||
it("formats default and custom gateway action summaries", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "default send action",
|
||||
input: { channel: "whatsapp", messageId: "m1" },
|
||||
expected: "✅ Sent via gateway (whatsapp). Message ID: m1",
|
||||
},
|
||||
{
|
||||
name: "custom action",
|
||||
input: { action: "Poll sent", channel: "discord", messageId: "p1" },
|
||||
expected: "✅ Poll sent via gateway (discord). Message ID: p1",
|
||||
},
|
||||
] as const;
|
||||
|
||||
it("supports custom actions", () => {
|
||||
expect(
|
||||
formatGatewaySummary({
|
||||
action: "Poll sent",
|
||||
channel: "discord",
|
||||
messageId: "p1",
|
||||
}),
|
||||
).toBe("✅ Poll sent via gateway (discord). Message ID: p1");
|
||||
for (const testCase of cases) {
|
||||
expect(formatGatewaySummary(testCase.input), testCase.name).toBe(testCase.expected);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -741,45 +779,50 @@ describe("resolveOutboundSessionRoute", () => {
|
||||
});
|
||||
|
||||
describe("normalizeOutboundPayloadsForJson", () => {
|
||||
it("normalizes payloads with mediaUrl and mediaUrls", () => {
|
||||
expect(
|
||||
normalizeOutboundPayloadsForJson([
|
||||
{ text: "hi" },
|
||||
{ text: "photo", mediaUrl: "https://x.test/a.jpg" },
|
||||
{ text: "multi", mediaUrls: ["https://x.test/1.png"] },
|
||||
]),
|
||||
).toEqual([
|
||||
{ text: "hi", mediaUrl: null, mediaUrls: undefined, channelData: undefined },
|
||||
it("normalizes payloads for JSON output", () => {
|
||||
const cases = [
|
||||
{
|
||||
text: "photo",
|
||||
mediaUrl: "https://x.test/a.jpg",
|
||||
mediaUrls: ["https://x.test/a.jpg"],
|
||||
channelData: undefined,
|
||||
input: [
|
||||
{ text: "hi" },
|
||||
{ text: "photo", mediaUrl: "https://x.test/a.jpg" },
|
||||
{ text: "multi", mediaUrls: ["https://x.test/1.png"] },
|
||||
],
|
||||
expected: [
|
||||
{ text: "hi", mediaUrl: null, mediaUrls: undefined, channelData: undefined },
|
||||
{
|
||||
text: "photo",
|
||||
mediaUrl: "https://x.test/a.jpg",
|
||||
mediaUrls: ["https://x.test/a.jpg"],
|
||||
channelData: undefined,
|
||||
},
|
||||
{
|
||||
text: "multi",
|
||||
mediaUrl: null,
|
||||
mediaUrls: ["https://x.test/1.png"],
|
||||
channelData: undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "multi",
|
||||
mediaUrl: null,
|
||||
mediaUrls: ["https://x.test/1.png"],
|
||||
channelData: undefined,
|
||||
input: [
|
||||
{
|
||||
text: "MEDIA:https://x.test/a.png\nMEDIA:https://x.test/b.png",
|
||||
},
|
||||
],
|
||||
expected: [
|
||||
{
|
||||
text: "",
|
||||
mediaUrl: null,
|
||||
mediaUrls: ["https://x.test/a.png", "https://x.test/b.png"],
|
||||
channelData: undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
] as const;
|
||||
|
||||
it("keeps mediaUrl null for multi MEDIA tags", () => {
|
||||
expect(
|
||||
normalizeOutboundPayloadsForJson([
|
||||
{
|
||||
text: "MEDIA:https://x.test/a.png\nMEDIA:https://x.test/b.png",
|
||||
},
|
||||
]),
|
||||
).toEqual([
|
||||
{
|
||||
text: "",
|
||||
mediaUrl: null,
|
||||
mediaUrls: ["https://x.test/a.png", "https://x.test/b.png"],
|
||||
channelData: undefined,
|
||||
},
|
||||
]);
|
||||
for (const testCase of cases) {
|
||||
expect(normalizeOutboundPayloadsForJson(testCase.input)).toEqual(testCase.expected);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -792,22 +835,29 @@ describe("normalizeOutboundPayloads", () => {
|
||||
});
|
||||
|
||||
describe("formatOutboundPayloadLog", () => {
|
||||
it("trims trailing text and appends media lines", () => {
|
||||
expect(
|
||||
formatOutboundPayloadLog({
|
||||
text: "hello ",
|
||||
mediaUrls: ["https://x.test/a.png", "https://x.test/b.png"],
|
||||
}),
|
||||
).toBe("hello\nMEDIA:https://x.test/a.png\nMEDIA:https://x.test/b.png");
|
||||
});
|
||||
it("formats text+media and media-only logs", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "text with media lines",
|
||||
input: {
|
||||
text: "hello ",
|
||||
mediaUrls: ["https://x.test/a.png", "https://x.test/b.png"],
|
||||
},
|
||||
expected: "hello\nMEDIA:https://x.test/a.png\nMEDIA:https://x.test/b.png",
|
||||
},
|
||||
{
|
||||
name: "media only",
|
||||
input: {
|
||||
text: "",
|
||||
mediaUrls: ["https://x.test/a.png"],
|
||||
},
|
||||
expected: "MEDIA:https://x.test/a.png",
|
||||
},
|
||||
] as const;
|
||||
|
||||
it("logs media-only payloads", () => {
|
||||
expect(
|
||||
formatOutboundPayloadLog({
|
||||
text: "",
|
||||
mediaUrls: ["https://x.test/a.png"],
|
||||
}),
|
||||
).toBe("MEDIA:https://x.test/a.png");
|
||||
for (const testCase of cases) {
|
||||
expect(formatOutboundPayloadLog(testCase.input), testCase.name).toBe(testCase.expected);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -825,22 +875,6 @@ describe("resolveOutboundTarget", () => {
|
||||
setActivePluginRegistry(createTestRegistry());
|
||||
});
|
||||
|
||||
it("rejects whatsapp with empty target even when allowFrom configured", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: { whatsapp: { allowFrom: ["+1555"] } },
|
||||
};
|
||||
const res = resolveOutboundTarget({
|
||||
channel: "whatsapp",
|
||||
to: "",
|
||||
cfg,
|
||||
mode: "explicit",
|
||||
});
|
||||
expect(res.ok).toBe(false);
|
||||
if (!res.ok) {
|
||||
expect(res.error.message).toContain("WhatsApp");
|
||||
}
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "normalizes whatsapp target when provided",
|
||||
@@ -860,6 +894,16 @@ describe("resolveOutboundTarget", () => {
|
||||
},
|
||||
expected: { ok: true as const, to: "120363401234567890@g.us" },
|
||||
},
|
||||
{
|
||||
name: "rejects whatsapp with empty target in explicit mode even with cfg allowFrom",
|
||||
input: {
|
||||
channel: "whatsapp" as const,
|
||||
to: "",
|
||||
cfg: { channels: { whatsapp: { allowFrom: ["+1555"] } } } as OpenClawConfig,
|
||||
mode: "explicit" as const,
|
||||
},
|
||||
expectedErrorIncludes: "WhatsApp",
|
||||
},
|
||||
{
|
||||
name: "rejects whatsapp with empty target and allowFrom (no silent fallback)",
|
||||
input: { channel: "whatsapp" as const, to: "", allowFrom: ["+1555"] },
|
||||
@@ -901,19 +945,18 @@ describe("resolveOutboundTarget", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects telegram with missing target", () => {
|
||||
const res = resolveOutboundTarget({ channel: "telegram", to: " " });
|
||||
expect(res.ok).toBe(false);
|
||||
if (!res.ok) {
|
||||
expect(res.error.message).toContain("Telegram");
|
||||
}
|
||||
});
|
||||
it("rejects invalid non-whatsapp targets", () => {
|
||||
const cases = [
|
||||
{ input: { channel: "telegram" as const, to: " " }, expectedErrorIncludes: "Telegram" },
|
||||
{ input: { channel: "webchat" as const, to: "x" }, expectedErrorIncludes: "WebChat" },
|
||||
] as const;
|
||||
|
||||
it("rejects webchat delivery", () => {
|
||||
const res = resolveOutboundTarget({ channel: "webchat", to: "x" });
|
||||
expect(res.ok).toBe(false);
|
||||
if (!res.ok) {
|
||||
expect(res.error.message).toContain("WebChat");
|
||||
for (const testCase of cases) {
|
||||
const res = resolveOutboundTarget(testCase.input);
|
||||
expect(res.ok).toBe(false);
|
||||
if (!res.ok) {
|
||||
expect(res.error.message).toContain(testCase.expectedErrorIncludes);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user