test: extract outbound session route coverage

This commit is contained in:
Peter Steinberger
2026-03-13 20:54:31 +00:00
parent 6b49a604b4
commit d537904abb
2 changed files with 259 additions and 258 deletions

View File

@@ -0,0 +1,259 @@
import { describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../../config/config.js";
import { resolveOutboundSessionRoute } from "./outbound-session.js";
describe("resolveOutboundSessionRoute", () => {
const baseConfig = {} as OpenClawConfig;
it("resolves provider-specific session routes", async () => {
const perChannelPeerCfg = { session: { dmScope: "per-channel-peer" } } as OpenClawConfig;
const identityLinksCfg = {
session: {
dmScope: "per-peer",
identityLinks: {
alice: ["discord:123"],
},
},
} as OpenClawConfig;
const slackMpimCfg = {
channels: {
slack: {
dm: {
groupChannels: ["G123"],
},
},
},
} as OpenClawConfig;
const cases: Array<{
name: string;
cfg: OpenClawConfig;
channel: string;
target: string;
replyToId?: string;
threadId?: string;
expected: {
sessionKey: string;
from?: string;
to?: string;
threadId?: string | number;
chatType?: "direct" | "group";
};
}> = [
{
name: "Slack thread",
cfg: baseConfig,
channel: "slack",
target: "channel:C123",
replyToId: "456",
expected: {
sessionKey: "agent:main:slack:channel:c123:thread:456",
from: "slack:channel:C123",
to: "channel:C123",
threadId: "456",
},
},
{
name: "Telegram topic group",
cfg: baseConfig,
channel: "telegram",
target: "-100123456:topic:42",
expected: {
sessionKey: "agent:main:telegram:group:-100123456:topic:42",
from: "telegram:group:-100123456:topic:42",
to: "telegram:-100123456",
threadId: 42,
},
},
{
name: "Telegram DM with topic",
cfg: perChannelPeerCfg,
channel: "telegram",
target: "123456789:topic:99",
expected: {
sessionKey: "agent:main:telegram:direct:123456789:thread:99",
from: "telegram:123456789:topic:99",
to: "telegram:123456789",
threadId: 99,
chatType: "direct",
},
},
{
name: "Telegram unresolved username DM",
cfg: perChannelPeerCfg,
channel: "telegram",
target: "@alice",
expected: {
sessionKey: "agent:main:telegram:direct:@alice",
chatType: "direct",
},
},
{
name: "Telegram DM scoped threadId fallback",
cfg: perChannelPeerCfg,
channel: "telegram",
target: "12345",
threadId: "12345:99",
expected: {
sessionKey: "agent:main:telegram:direct:12345:thread:99",
from: "telegram:12345:topic:99",
to: "telegram:12345",
threadId: 99,
chatType: "direct",
},
},
{
name: "identity-links per-peer",
cfg: identityLinksCfg,
channel: "discord",
target: "user:123",
expected: {
sessionKey: "agent:main:direct:alice",
},
},
{
name: "BlueBubbles chat_* prefix stripping",
cfg: baseConfig,
channel: "bluebubbles",
target: "chat_guid:ABC123",
expected: {
sessionKey: "agent:main:bluebubbles:group:abc123",
from: "group:ABC123",
},
},
{
name: "Zalo Personal DM target",
cfg: perChannelPeerCfg,
channel: "zalouser",
target: "123456",
expected: {
sessionKey: "agent:main:zalouser:direct:123456",
chatType: "direct",
},
},
{
name: "Slack mpim allowlist -> group key",
cfg: slackMpimCfg,
channel: "slack",
target: "channel:G123",
expected: {
sessionKey: "agent:main:slack:group:g123",
from: "slack:group:G123",
},
},
{
name: "Feishu explicit group prefix keeps group routing",
cfg: baseConfig,
channel: "feishu",
target: "group:oc_group_chat",
expected: {
sessionKey: "agent:main:feishu:group:oc_group_chat",
from: "feishu:group:oc_group_chat",
to: "oc_group_chat",
chatType: "group",
},
},
{
name: "Feishu explicit dm prefix keeps direct routing",
cfg: perChannelPeerCfg,
channel: "feishu",
target: "dm:oc_dm_chat",
expected: {
sessionKey: "agent:main:feishu:direct:oc_dm_chat",
from: "feishu:oc_dm_chat",
to: "oc_dm_chat",
chatType: "direct",
},
},
{
name: "Feishu bare oc_ target defaults to direct routing",
cfg: perChannelPeerCfg,
channel: "feishu",
target: "oc_ambiguous_chat",
expected: {
sessionKey: "agent:main:feishu:direct:oc_ambiguous_chat",
from: "feishu:oc_ambiguous_chat",
to: "oc_ambiguous_chat",
chatType: "direct",
},
},
];
for (const testCase of cases) {
const route = await resolveOutboundSessionRoute({
cfg: testCase.cfg,
channel: testCase.channel,
agentId: "main",
target: testCase.target,
replyToId: testCase.replyToId,
threadId: testCase.threadId,
});
expect(route?.sessionKey, testCase.name).toBe(testCase.expected.sessionKey);
if (testCase.expected.from !== undefined) {
expect(route?.from, testCase.name).toBe(testCase.expected.from);
}
if (testCase.expected.to !== undefined) {
expect(route?.to, testCase.name).toBe(testCase.expected.to);
}
if (testCase.expected.threadId !== undefined) {
expect(route?.threadId, testCase.name).toBe(testCase.expected.threadId);
}
if (testCase.expected.chatType !== undefined) {
expect(route?.chatType, testCase.name).toBe(testCase.expected.chatType);
}
}
});
it("uses resolved Discord user targets to route bare numeric ids as DMs", async () => {
const route = await resolveOutboundSessionRoute({
cfg: { session: { dmScope: "per-channel-peer" } } as OpenClawConfig,
channel: "discord",
agentId: "main",
target: "123",
resolvedTarget: {
to: "user:123",
kind: "user",
source: "directory",
},
});
expect(route).toMatchObject({
sessionKey: "agent:main:discord:direct:123",
from: "discord:123",
to: "user:123",
chatType: "direct",
});
});
it("uses resolved Mattermost user targets to route bare ids as DMs", async () => {
const userId = "dthcxgoxhifn3pwh65cut3ud3w";
const route = await resolveOutboundSessionRoute({
cfg: { session: { dmScope: "per-channel-peer" } } as OpenClawConfig,
channel: "mattermost",
agentId: "main",
target: userId,
resolvedTarget: {
to: `user:${userId}`,
kind: "user",
source: "directory",
},
});
expect(route).toMatchObject({
sessionKey: `agent:main:mattermost:direct:${userId}`,
from: `mattermost:${userId}`,
to: `user:${userId}`,
chatType: "direct",
});
});
it("rejects bare numeric Discord targets when the caller has no kind hint", async () => {
await expect(
resolveOutboundSessionRoute({
cfg: { session: { dmScope: "per-channel-peer" } } as OpenClawConfig,
channel: "discord",
agentId: "main",
target: "123",
}),
).rejects.toThrow(/Ambiguous Discord recipient/);
});
});

View File

@@ -2,7 +2,6 @@ import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../config/config.js";
import {
ackDelivery,
computeBackoffMs,
@@ -16,7 +15,6 @@ import {
moveToFailed,
recoverPendingDeliveries,
} from "./delivery-queue.js";
import { resolveOutboundSessionRoute } from "./outbound-session.js";
import { runResolveOutboundTargetCoreTests } from "./targets.shared-test.js";
describe("delivery-queue", () => {
@@ -598,260 +596,4 @@ describe("delivery-queue", () => {
});
});
describe("resolveOutboundSessionRoute", () => {
const baseConfig = {} as OpenClawConfig;
it("resolves provider-specific session routes", async () => {
const perChannelPeerCfg = { session: { dmScope: "per-channel-peer" } } as OpenClawConfig;
const identityLinksCfg = {
session: {
dmScope: "per-peer",
identityLinks: {
alice: ["discord:123"],
},
},
} as OpenClawConfig;
const slackMpimCfg = {
channels: {
slack: {
dm: {
groupChannels: ["G123"],
},
},
},
} as OpenClawConfig;
const cases: Array<{
name: string;
cfg: OpenClawConfig;
channel: string;
target: string;
replyToId?: string;
threadId?: string;
expected: {
sessionKey: string;
from?: string;
to?: string;
threadId?: string | number;
chatType?: "direct" | "group";
};
}> = [
{
name: "Slack thread",
cfg: baseConfig,
channel: "slack",
target: "channel:C123",
replyToId: "456",
expected: {
sessionKey: "agent:main:slack:channel:c123:thread:456",
from: "slack:channel:C123",
to: "channel:C123",
threadId: "456",
},
},
{
name: "Telegram topic group",
cfg: baseConfig,
channel: "telegram",
target: "-100123456:topic:42",
expected: {
sessionKey: "agent:main:telegram:group:-100123456:topic:42",
from: "telegram:group:-100123456:topic:42",
to: "telegram:-100123456",
threadId: 42,
},
},
{
name: "Telegram DM with topic",
cfg: perChannelPeerCfg,
channel: "telegram",
target: "123456789:topic:99",
expected: {
sessionKey: "agent:main:telegram:direct:123456789:thread:99",
from: "telegram:123456789:topic:99",
to: "telegram:123456789",
threadId: 99,
chatType: "direct",
},
},
{
name: "Telegram unresolved username DM",
cfg: perChannelPeerCfg,
channel: "telegram",
target: "@alice",
expected: {
sessionKey: "agent:main:telegram:direct:@alice",
chatType: "direct",
},
},
{
name: "Telegram DM scoped threadId fallback",
cfg: perChannelPeerCfg,
channel: "telegram",
target: "12345",
threadId: "12345:99",
expected: {
sessionKey: "agent:main:telegram:direct:12345:thread:99",
from: "telegram:12345:topic:99",
to: "telegram:12345",
threadId: 99,
chatType: "direct",
},
},
{
name: "identity-links per-peer",
cfg: identityLinksCfg,
channel: "discord",
target: "user:123",
expected: {
sessionKey: "agent:main:direct:alice",
},
},
{
name: "BlueBubbles chat_* prefix stripping",
cfg: baseConfig,
channel: "bluebubbles",
target: "chat_guid:ABC123",
expected: {
sessionKey: "agent:main:bluebubbles:group:abc123",
from: "group:ABC123",
},
},
{
name: "Zalo Personal DM target",
cfg: perChannelPeerCfg,
channel: "zalouser",
target: "123456",
expected: {
sessionKey: "agent:main:zalouser:direct:123456",
chatType: "direct",
},
},
{
name: "Slack mpim allowlist -> group key",
cfg: slackMpimCfg,
channel: "slack",
target: "channel:G123",
expected: {
sessionKey: "agent:main:slack:group:g123",
from: "slack:group:G123",
},
},
{
name: "Feishu explicit group prefix keeps group routing",
cfg: baseConfig,
channel: "feishu",
target: "group:oc_group_chat",
expected: {
sessionKey: "agent:main:feishu:group:oc_group_chat",
from: "feishu:group:oc_group_chat",
to: "oc_group_chat",
chatType: "group",
},
},
{
name: "Feishu explicit dm prefix keeps direct routing",
cfg: perChannelPeerCfg,
channel: "feishu",
target: "dm:oc_dm_chat",
expected: {
sessionKey: "agent:main:feishu:direct:oc_dm_chat",
from: "feishu:oc_dm_chat",
to: "oc_dm_chat",
chatType: "direct",
},
},
{
name: "Feishu bare oc_ target defaults to direct routing",
cfg: perChannelPeerCfg,
channel: "feishu",
target: "oc_ambiguous_chat",
expected: {
sessionKey: "agent:main:feishu:direct:oc_ambiguous_chat",
from: "feishu:oc_ambiguous_chat",
to: "oc_ambiguous_chat",
chatType: "direct",
},
},
];
for (const testCase of cases) {
const route = await resolveOutboundSessionRoute({
cfg: testCase.cfg,
channel: testCase.channel,
agentId: "main",
target: testCase.target,
replyToId: testCase.replyToId,
threadId: testCase.threadId,
});
expect(route?.sessionKey, testCase.name).toBe(testCase.expected.sessionKey);
if (testCase.expected.from !== undefined) {
expect(route?.from, testCase.name).toBe(testCase.expected.from);
}
if (testCase.expected.to !== undefined) {
expect(route?.to, testCase.name).toBe(testCase.expected.to);
}
if (testCase.expected.threadId !== undefined) {
expect(route?.threadId, testCase.name).toBe(testCase.expected.threadId);
}
if (testCase.expected.chatType !== undefined) {
expect(route?.chatType, testCase.name).toBe(testCase.expected.chatType);
}
}
});
it("uses resolved Discord user targets to route bare numeric ids as DMs", async () => {
const route = await resolveOutboundSessionRoute({
cfg: { session: { dmScope: "per-channel-peer" } } as OpenClawConfig,
channel: "discord",
agentId: "main",
target: "123",
resolvedTarget: {
to: "user:123",
kind: "user",
source: "directory",
},
});
expect(route).toMatchObject({
sessionKey: "agent:main:discord:direct:123",
from: "discord:123",
to: "user:123",
chatType: "direct",
});
});
it("uses resolved Mattermost user targets to route bare ids as DMs", async () => {
const userId = "dthcxgoxhifn3pwh65cut3ud3w";
const route = await resolveOutboundSessionRoute({
cfg: { session: { dmScope: "per-channel-peer" } } as OpenClawConfig,
channel: "mattermost",
agentId: "main",
target: userId,
resolvedTarget: {
to: `user:${userId}`,
kind: "user",
source: "directory",
},
});
expect(route).toMatchObject({
sessionKey: `agent:main:mattermost:direct:${userId}`,
from: `mattermost:${userId}`,
to: `user:${userId}`,
chatType: "direct",
});
});
it("rejects bare numeric Discord targets when the caller has no kind hint", async () => {
await expect(
resolveOutboundSessionRoute({
cfg: { session: { dmScope: "per-channel-peer" } } as OpenClawConfig,
channel: "discord",
agentId: "main",
target: "123",
}),
).rejects.toThrow(/Ambiguous Discord recipient/);
});
});
runResolveOutboundTargetCoreTests();