mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 09:22:45 +00:00
refactor(channels): dedupe plugin routing and channel helpers
This commit is contained in:
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, expectTypeOf, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import type { DiscordProbe } from "../../discord/probe.js";
|
||||
import type { DiscordTokenResolution } from "../../discord/token.js";
|
||||
import type { IMessageProbe } from "../../imessage/probe.js";
|
||||
@@ -11,7 +12,11 @@ import type { SignalProbe } from "../../signal/probe.js";
|
||||
import type { SlackProbe } from "../../slack/probe.js";
|
||||
import type { TelegramProbe } from "../../telegram/probe.js";
|
||||
import type { TelegramTokenResolution } from "../../telegram/token.js";
|
||||
import { createTestRegistry } from "../../test-utils/channel-plugins.js";
|
||||
import {
|
||||
createChannelTestPluginBase,
|
||||
createOutboundTestPlugin,
|
||||
createTestRegistry,
|
||||
} from "../../test-utils/channel-plugins.js";
|
||||
import { getChannelPluginCatalogEntry, listChannelPluginCatalogEntries } from "./catalog.js";
|
||||
import { resolveChannelConfigWrites } from "./config-writes.js";
|
||||
import {
|
||||
@@ -27,7 +32,7 @@ import {
|
||||
import { listChannelPlugins } from "./index.js";
|
||||
import { loadChannelPlugin } from "./load.js";
|
||||
import { loadChannelOutboundAdapter } from "./outbound/load.js";
|
||||
import type { ChannelOutboundAdapter, ChannelPlugin } from "./types.js";
|
||||
import type { ChannelDirectoryEntry, ChannelOutboundAdapter, ChannelPlugin } from "./types.js";
|
||||
import type { BaseProbeResult, BaseTokenResolution } from "./types.js";
|
||||
|
||||
describe("channel plugin registry", () => {
|
||||
@@ -147,6 +152,71 @@ const registryWithMSTeams = createTestRegistry([
|
||||
{ pluginId: "msteams", plugin: msteamsPlugin, source: "test" },
|
||||
]);
|
||||
|
||||
const msteamsOutboundV2: ChannelOutboundAdapter = {
|
||||
deliveryMode: "direct",
|
||||
sendText: async () => ({ channel: "msteams", messageId: "m3" }),
|
||||
sendMedia: async () => ({ channel: "msteams", messageId: "m4" }),
|
||||
};
|
||||
|
||||
const msteamsPluginV2 = createOutboundTestPlugin({
|
||||
id: "msteams",
|
||||
label: "Microsoft Teams",
|
||||
outbound: msteamsOutboundV2,
|
||||
});
|
||||
|
||||
const registryWithMSTeamsV2 = createTestRegistry([
|
||||
{ pluginId: "msteams", plugin: msteamsPluginV2, source: "test-v2" },
|
||||
]);
|
||||
|
||||
const mstNoOutboundPlugin = createChannelTestPluginBase({
|
||||
id: "msteams",
|
||||
label: "Microsoft Teams",
|
||||
});
|
||||
|
||||
const registryWithMSTeamsNoOutbound = createTestRegistry([
|
||||
{ pluginId: "msteams", plugin: mstNoOutboundPlugin, source: "test-no-outbound" },
|
||||
]);
|
||||
|
||||
function makeSlackConfigWritesCfg(accountIdKey: string) {
|
||||
return {
|
||||
channels: {
|
||||
slack: {
|
||||
configWrites: true,
|
||||
accounts: {
|
||||
[accountIdKey]: { configWrites: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
type DirectoryListFn = (params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId?: string | null;
|
||||
query?: string | null;
|
||||
limit?: number | null;
|
||||
}) => Promise<ChannelDirectoryEntry[]>;
|
||||
|
||||
async function listDirectoryEntriesWithDefaults(listFn: DirectoryListFn, cfg: OpenClawConfig) {
|
||||
return await listFn({
|
||||
cfg,
|
||||
accountId: "default",
|
||||
query: null,
|
||||
limit: null,
|
||||
});
|
||||
}
|
||||
|
||||
async function expectDirectoryIds(
|
||||
listFn: DirectoryListFn,
|
||||
cfg: OpenClawConfig,
|
||||
expected: string[],
|
||||
options?: { sorted?: boolean },
|
||||
) {
|
||||
const entries = await listDirectoryEntriesWithDefaults(listFn, cfg);
|
||||
const ids = entries.map((entry) => entry.id);
|
||||
expect(options?.sorted ? ids.toSorted() : ids).toEqual(expected);
|
||||
}
|
||||
|
||||
describe("channel plugin loader", () => {
|
||||
beforeEach(() => {
|
||||
setActivePluginRegistry(emptyRegistry);
|
||||
@@ -167,6 +237,25 @@ describe("channel plugin loader", () => {
|
||||
const outbound = await loadChannelOutboundAdapter("msteams");
|
||||
expect(outbound).toBe(msteamsOutbound);
|
||||
});
|
||||
|
||||
it("refreshes cached plugin values when registry changes", async () => {
|
||||
setActivePluginRegistry(registryWithMSTeams);
|
||||
expect(await loadChannelPlugin("msteams")).toBe(msteamsPlugin);
|
||||
setActivePluginRegistry(registryWithMSTeamsV2);
|
||||
expect(await loadChannelPlugin("msteams")).toBe(msteamsPluginV2);
|
||||
});
|
||||
|
||||
it("refreshes cached outbound values when registry changes", async () => {
|
||||
setActivePluginRegistry(registryWithMSTeams);
|
||||
expect(await loadChannelOutboundAdapter("msteams")).toBe(msteamsOutbound);
|
||||
setActivePluginRegistry(registryWithMSTeamsV2);
|
||||
expect(await loadChannelOutboundAdapter("msteams")).toBe(msteamsOutboundV2);
|
||||
});
|
||||
|
||||
it("returns undefined when plugin has no outbound adapter", async () => {
|
||||
setActivePluginRegistry(registryWithMSTeamsNoOutbound);
|
||||
expect(await loadChannelOutboundAdapter("msteams")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("BaseProbeResult assignability", () => {
|
||||
@@ -196,11 +285,8 @@ describe("BaseProbeResult assignability", () => {
|
||||
});
|
||||
|
||||
describe("BaseTokenResolution assignability", () => {
|
||||
it("TelegramTokenResolution satisfies BaseTokenResolution", () => {
|
||||
it("Telegram and Discord token resolutions satisfy BaseTokenResolution", () => {
|
||||
expectTypeOf<TelegramTokenResolution>().toMatchTypeOf<BaseTokenResolution>();
|
||||
});
|
||||
|
||||
it("DiscordTokenResolution satisfies BaseTokenResolution", () => {
|
||||
expectTypeOf<DiscordTokenResolution>().toMatchTypeOf<BaseTokenResolution>();
|
||||
});
|
||||
});
|
||||
@@ -217,30 +303,12 @@ describe("resolveChannelConfigWrites", () => {
|
||||
});
|
||||
|
||||
it("account override wins over channel default", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
slack: {
|
||||
configWrites: true,
|
||||
accounts: {
|
||||
work: { configWrites: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const cfg = makeSlackConfigWritesCfg("work");
|
||||
expect(resolveChannelConfigWrites({ cfg, channelId: "slack", accountId: "work" })).toBe(false);
|
||||
});
|
||||
|
||||
it("matches account ids case-insensitively", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
slack: {
|
||||
configWrites: true,
|
||||
accounts: {
|
||||
Work: { configWrites: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const cfg = makeSlackConfigWritesCfg("Work");
|
||||
expect(resolveChannelConfigWrites({ cfg, channelId: "slack", accountId: "work" })).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -260,26 +328,13 @@ describe("directory (config-backed)", () => {
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
} as any;
|
||||
|
||||
const peers = await listSlackDirectoryPeersFromConfig({
|
||||
await expectDirectoryIds(
|
||||
listSlackDirectoryPeersFromConfig,
|
||||
cfg,
|
||||
accountId: "default",
|
||||
query: null,
|
||||
limit: null,
|
||||
});
|
||||
expect(peers?.map((e) => e.id).toSorted()).toEqual([
|
||||
"user:u123",
|
||||
"user:u234",
|
||||
"user:u777",
|
||||
"user:u999",
|
||||
]);
|
||||
|
||||
const groups = await listSlackDirectoryGroupsFromConfig({
|
||||
cfg,
|
||||
accountId: "default",
|
||||
query: null,
|
||||
limit: null,
|
||||
});
|
||||
expect(groups?.map((e) => e.id)).toEqual(["channel:c111"]);
|
||||
["user:u123", "user:u234", "user:u777", "user:u999"],
|
||||
{ sorted: true },
|
||||
);
|
||||
await expectDirectoryIds(listSlackDirectoryGroupsFromConfig, cfg, ["channel:c111"]);
|
||||
});
|
||||
|
||||
it("lists Discord peers/groups from config (numeric ids only)", async () => {
|
||||
@@ -287,13 +342,14 @@ describe("directory (config-backed)", () => {
|
||||
channels: {
|
||||
discord: {
|
||||
token: "discord-test",
|
||||
dm: { allowFrom: ["<@111>", "nope"] },
|
||||
dm: { allowFrom: ["<@111>", "<@!333>", "nope"] },
|
||||
dms: { "222": {} },
|
||||
guilds: {
|
||||
"123": {
|
||||
users: ["<@12345>", "not-an-id"],
|
||||
users: ["<@12345>", " discord:444 ", "not-an-id"],
|
||||
channels: {
|
||||
"555": {},
|
||||
"<#777>": {},
|
||||
"channel:666": {},
|
||||
general: {},
|
||||
},
|
||||
@@ -304,21 +360,18 @@ describe("directory (config-backed)", () => {
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
} as any;
|
||||
|
||||
const peers = await listDiscordDirectoryPeersFromConfig({
|
||||
await expectDirectoryIds(
|
||||
listDiscordDirectoryPeersFromConfig,
|
||||
cfg,
|
||||
accountId: "default",
|
||||
query: null,
|
||||
limit: null,
|
||||
});
|
||||
expect(peers?.map((e) => e.id).toSorted()).toEqual(["user:111", "user:12345", "user:222"]);
|
||||
|
||||
const groups = await listDiscordDirectoryGroupsFromConfig({
|
||||
["user:111", "user:12345", "user:222", "user:333", "user:444"],
|
||||
{ sorted: true },
|
||||
);
|
||||
await expectDirectoryIds(
|
||||
listDiscordDirectoryGroupsFromConfig,
|
||||
cfg,
|
||||
accountId: "default",
|
||||
query: null,
|
||||
limit: null,
|
||||
});
|
||||
expect(groups?.map((e) => e.id).toSorted()).toEqual(["channel:555", "channel:666"]);
|
||||
["channel:555", "channel:666", "channel:777"],
|
||||
{ sorted: true },
|
||||
);
|
||||
});
|
||||
|
||||
it("lists Telegram peers/groups from config", async () => {
|
||||
@@ -334,21 +387,15 @@ describe("directory (config-backed)", () => {
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
} as any;
|
||||
|
||||
const peers = await listTelegramDirectoryPeersFromConfig({
|
||||
await expectDirectoryIds(
|
||||
listTelegramDirectoryPeersFromConfig,
|
||||
cfg,
|
||||
accountId: "default",
|
||||
query: null,
|
||||
limit: null,
|
||||
});
|
||||
expect(peers?.map((e) => e.id).toSorted()).toEqual(["123", "456", "@alice", "@bob"]);
|
||||
|
||||
const groups = await listTelegramDirectoryGroupsFromConfig({
|
||||
cfg,
|
||||
accountId: "default",
|
||||
query: null,
|
||||
limit: null,
|
||||
});
|
||||
expect(groups?.map((e) => e.id)).toEqual(["-1001"]);
|
||||
["123", "456", "@alice", "@bob"],
|
||||
{
|
||||
sorted: true,
|
||||
},
|
||||
);
|
||||
await expectDirectoryIds(listTelegramDirectoryGroupsFromConfig, cfg, ["-1001"]);
|
||||
});
|
||||
|
||||
it("lists WhatsApp peers/groups from config", async () => {
|
||||
@@ -362,21 +409,8 @@ describe("directory (config-backed)", () => {
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
} as any;
|
||||
|
||||
const peers = await listWhatsAppDirectoryPeersFromConfig({
|
||||
cfg,
|
||||
accountId: "default",
|
||||
query: null,
|
||||
limit: null,
|
||||
});
|
||||
expect(peers?.map((e) => e.id)).toEqual(["+15550000000"]);
|
||||
|
||||
const groups = await listWhatsAppDirectoryGroupsFromConfig({
|
||||
cfg,
|
||||
accountId: "default",
|
||||
query: null,
|
||||
limit: null,
|
||||
});
|
||||
expect(groups?.map((e) => e.id)).toEqual(["999@g.us"]);
|
||||
await expectDirectoryIds(listWhatsAppDirectoryPeersFromConfig, cfg, ["+15550000000"]);
|
||||
await expectDirectoryIds(listWhatsAppDirectoryGroupsFromConfig, cfg, ["999@g.us"]);
|
||||
});
|
||||
|
||||
it("applies query and limit filtering for config-backed directories", async () => {
|
||||
|
||||
Reference in New Issue
Block a user