test: extract outbound format and cache coverage

This commit is contained in:
Peter Steinberger 2026-03-13 20:47:29 +00:00
parent bde038527c
commit 0643c0d15a
3 changed files with 245 additions and 185 deletions

View File

@ -0,0 +1,65 @@
import { describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../config/config.js";
import { DirectoryCache, buildDirectoryCacheKey } from "./directory-cache.js";
describe("buildDirectoryCacheKey", () => {
it("includes account and signature fallbacks", () => {
expect(
buildDirectoryCacheKey({
channel: "slack",
kind: "channel",
source: "cache",
}),
).toBe("slack:default:channel:cache:default");
expect(
buildDirectoryCacheKey({
channel: "discord",
accountId: "work",
kind: "user",
source: "live",
signature: "v2",
}),
).toBe("discord:work:user:live:v2");
});
});
describe("DirectoryCache", () => {
it("expires entries after ttl and resets when config ref changes", () => {
vi.useFakeTimers();
const cache = new DirectoryCache<string>(1_000);
const cfgA = {} as OpenClawConfig;
const cfgB = {} as OpenClawConfig;
cache.set("a", "first", cfgA);
expect(cache.get("a", cfgA)).toBe("first");
vi.advanceTimersByTime(1_001);
expect(cache.get("a", cfgA)).toBeUndefined();
cache.set("b", "second", cfgA);
expect(cache.get("b", cfgB)).toBeUndefined();
vi.useRealTimers();
});
it("evicts least-recent entries, refreshes insertion order, and clears matches", () => {
const cache = new DirectoryCache<string>(60_000, 2);
const cfg = {} as OpenClawConfig;
cache.set("a", "A", cfg);
cache.set("b", "B", cfg);
cache.set("a", "A2", cfg);
cache.set("c", "C", cfg);
expect(cache.get("a", cfg)).toBe("A2");
expect(cache.get("b", cfg)).toBeUndefined();
expect(cache.get("c", cfg)).toBe("C");
cache.clearMatching((key) => key.startsWith("c"));
expect(cache.get("c", cfg)).toBeUndefined();
cache.clear(cfg);
expect(cache.get("a", cfg)).toBeUndefined();
});
});

View File

@ -0,0 +1,179 @@
import { describe, expect, it } from "vitest";
import {
buildOutboundDeliveryJson,
formatGatewaySummary,
formatOutboundDeliverySummary,
} from "./format.js";
describe("formatOutboundDeliverySummary", () => {
it("formats fallback and provider-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)",
},
{
name: "slack with room detail",
channel: "slack" as const,
result: {
channel: "slack" as const,
messageId: "s1",
roomId: "room-1",
},
expected: "✅ Sent via Slack. Message ID: s1 (room room-1)",
},
{
name: "msteams with conversation detail",
channel: "msteams" as const,
result: {
channel: "msteams" as const,
messageId: "t1",
conversationId: "conv-1",
},
expected: "✅ Sent via msteams. Message ID: t1 (conversation conv-1)",
},
];
for (const testCase of cases) {
expect(formatOutboundDeliverySummary(testCase.channel, testCase.result), testCase.name).toBe(
testCase.expected,
);
}
});
});
describe("buildOutboundDeliveryJson", () => {
it("builds 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,
},
},
{
name: "gateway payload with meta and explicit via",
input: {
channel: "discord" as const,
to: "channel:1",
via: "gateway" as const,
result: {
messageId: "g1",
channelId: "1",
meta: { thread: "2" },
},
},
expected: {
channel: "discord",
via: "gateway",
to: "channel:1",
messageId: "g1",
mediaUrl: null,
channelId: "1",
meta: { thread: "2" },
},
},
];
for (const testCase of cases) {
expect(buildOutboundDeliveryJson(testCase.input), testCase.name).toEqual(testCase.expected);
}
});
});
describe("formatGatewaySummary", () => {
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",
},
{
name: "missing channel and message id",
input: {},
expected: "✅ Sent via gateway. Message ID: unknown",
},
];
for (const testCase of cases) {
expect(formatGatewaySummary(testCase.input), testCase.name).toBe(testCase.expected);
}
});
});

View File

@ -1,7 +1,7 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { ReplyPayload } from "../../auto-reply/types.js";
import type { OpenClawConfig } from "../../config/config.js";
import { typedCases } from "../../test-utils/typed-cases.js";
@ -18,12 +18,6 @@ import {
moveToFailed,
recoverPendingDeliveries,
} from "./delivery-queue.js";
import { DirectoryCache } from "./directory-cache.js";
import {
buildOutboundDeliveryJson,
formatGatewaySummary,
formatOutboundDeliverySummary,
} from "./format.js";
import {
applyCrossContextDecoration,
buildCrossContextDecoration,
@ -616,184 +610,6 @@ describe("delivery-queue", () => {
});
});
describe("DirectoryCache", () => {
const cfg = {} as OpenClawConfig;
afterEach(() => {
vi.useRealTimers();
});
it("expires entries after ttl", () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-01-01T00:00:00.000Z"));
const cache = new DirectoryCache<string>(1000, 10);
cache.set("a", "value-a", cfg);
expect(cache.get("a", cfg)).toBe("value-a");
vi.setSystemTime(new Date("2026-01-01T00:00:02.000Z"));
expect(cache.get("a", cfg)).toBeUndefined();
});
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" },
},
];
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);
}
});
});
describe("formatOutboundDeliverySummary", () => {
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)",
},
];
for (const testCase of cases) {
expect(formatOutboundDeliverySummary(testCase.channel, testCase.result), testCase.name).toBe(
testCase.expected,
);
}
});
});
describe("buildOutboundDeliveryJson", () => {
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,
},
},
];
for (const testCase of cases) {
expect(buildOutboundDeliveryJson(testCase.input), testCase.name).toEqual(testCase.expected);
}
});
});
describe("formatGatewaySummary", () => {
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",
},
];
for (const testCase of cases) {
expect(formatGatewaySummary(testCase.input), testCase.name).toBe(testCase.expected);
}
});
});
const slackConfig = {
channels: {
slack: {