test: optimize gateway infra memory and security coverage

This commit is contained in:
Peter Steinberger
2026-02-21 21:43:20 +00:00
parent 58254b3b57
commit cc2ff68947
24 changed files with 1163 additions and 1284 deletions

View File

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