mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-05 17:10:20 +00:00
refactor: localize cron channel test outbounds
This commit is contained in:
@@ -1,13 +1,11 @@
|
||||
import "./isolated-agent.mocks.js";
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { runSubagentAnnounceFlow } from "../agents/subagent-announce.js";
|
||||
import type { ChannelOutboundAdapter } from "../channels/plugins/types.js";
|
||||
import type { ChannelOutboundAdapter, ChannelOutboundContext } from "../channels/plugins/types.js";
|
||||
import type { CliDeps } from "../cli/deps.js";
|
||||
import { resolveOutboundSendDep } from "../infra/outbound/send-deps.js";
|
||||
import { createWhatsAppTestPlugin } from "../infra/outbound/targets.test-helpers.js";
|
||||
import { setActivePluginRegistry } from "../plugins/runtime.js";
|
||||
import {
|
||||
loadBundledPluginPublicSurfaceSync,
|
||||
loadBundledPluginTestApiSync,
|
||||
} from "../test-utils/bundled-plugin-public-surface.js";
|
||||
import { createOutboundTestPlugin, createTestRegistry } from "../test-utils/channel-plugins.js";
|
||||
import { createCliDeps, mockAgentPayloads } from "./isolated-agent.delivery.test-helpers.js";
|
||||
import { runCronIsolatedAgentTurn } from "./isolated-agent.js";
|
||||
@@ -30,73 +28,6 @@ type ChannelCase = {
|
||||
expectedTo: string;
|
||||
};
|
||||
|
||||
let discordOutboundCache: ChannelOutboundAdapter | undefined;
|
||||
let imessageOutboundCache: ChannelOutboundAdapter | undefined;
|
||||
let signalOutboundCache: ChannelOutboundAdapter | undefined;
|
||||
let slackOutboundCache: ChannelOutboundAdapter | undefined;
|
||||
let telegramOutboundCache: ChannelOutboundAdapter | undefined;
|
||||
let whatsappOutboundCache: ChannelOutboundAdapter | undefined;
|
||||
|
||||
function getDiscordOutbound(): ChannelOutboundAdapter {
|
||||
if (!discordOutboundCache) {
|
||||
({ discordOutbound: discordOutboundCache } = loadBundledPluginTestApiSync<{
|
||||
discordOutbound: ChannelOutboundAdapter;
|
||||
}>("discord"));
|
||||
}
|
||||
return discordOutboundCache;
|
||||
}
|
||||
|
||||
function getIMessageOutbound(): ChannelOutboundAdapter {
|
||||
if (!imessageOutboundCache) {
|
||||
({ imessageOutbound: imessageOutboundCache } = loadBundledPluginPublicSurfaceSync<{
|
||||
imessageOutbound: ChannelOutboundAdapter;
|
||||
}>({
|
||||
pluginId: "imessage",
|
||||
artifactBasename: "src/outbound-adapter.js",
|
||||
}));
|
||||
}
|
||||
return imessageOutboundCache;
|
||||
}
|
||||
|
||||
function getSignalOutbound(): ChannelOutboundAdapter {
|
||||
if (!signalOutboundCache) {
|
||||
({ signalOutbound: signalOutboundCache } = loadBundledPluginTestApiSync<{
|
||||
signalOutbound: ChannelOutboundAdapter;
|
||||
}>("signal"));
|
||||
}
|
||||
return signalOutboundCache;
|
||||
}
|
||||
|
||||
function getSlackOutbound(): ChannelOutboundAdapter {
|
||||
if (!slackOutboundCache) {
|
||||
({ slackOutbound: slackOutboundCache } = loadBundledPluginTestApiSync<{
|
||||
slackOutbound: ChannelOutboundAdapter;
|
||||
}>("slack"));
|
||||
}
|
||||
return slackOutboundCache;
|
||||
}
|
||||
|
||||
function getTelegramOutbound(): ChannelOutboundAdapter {
|
||||
if (!telegramOutboundCache) {
|
||||
({ telegramOutbound: telegramOutboundCache } = loadBundledPluginPublicSurfaceSync<{
|
||||
telegramOutbound: ChannelOutboundAdapter;
|
||||
}>({
|
||||
pluginId: "telegram",
|
||||
artifactBasename: "src/outbound-adapter.js",
|
||||
}));
|
||||
}
|
||||
return telegramOutboundCache;
|
||||
}
|
||||
|
||||
function getWhatsAppOutbound(): ChannelOutboundAdapter {
|
||||
if (!whatsappOutboundCache) {
|
||||
({ whatsappOutbound: whatsappOutboundCache } = loadBundledPluginTestApiSync<{
|
||||
whatsappOutbound: ChannelOutboundAdapter;
|
||||
}>("whatsapp"));
|
||||
}
|
||||
return whatsappOutboundCache;
|
||||
}
|
||||
|
||||
const CASES: ChannelCase[] = [
|
||||
{
|
||||
name: "Slack",
|
||||
@@ -152,39 +83,95 @@ async function runExplicitAnnounceTurn(params: {
|
||||
});
|
||||
}
|
||||
|
||||
type CoreChannel = ChannelCase["channel"];
|
||||
type TestSendFn = (
|
||||
to: string,
|
||||
text: string,
|
||||
options?: Record<string, unknown>,
|
||||
) => Promise<{ messageId?: string } & Record<string, unknown>>;
|
||||
|
||||
function withRequiredMessageId(channel: CoreChannel, result: Awaited<ReturnType<TestSendFn>>) {
|
||||
return {
|
||||
channel,
|
||||
...result,
|
||||
messageId:
|
||||
typeof result.messageId === "string" && result.messageId.trim()
|
||||
? result.messageId
|
||||
: `${channel}-test-message`,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveCoreChannelSender(
|
||||
channel: CoreChannel,
|
||||
deps: ChannelOutboundContext["deps"],
|
||||
): TestSendFn {
|
||||
const sender = resolveOutboundSendDep<TestSendFn>(deps, channel);
|
||||
if (!sender) {
|
||||
throw new Error(`missing ${channel} sender`);
|
||||
}
|
||||
return sender;
|
||||
}
|
||||
|
||||
function createCliDelegatingOutbound(params: {
|
||||
channel: CoreChannel;
|
||||
deliveryMode?: ChannelOutboundAdapter["deliveryMode"];
|
||||
resolveTarget?: ChannelOutboundAdapter["resolveTarget"];
|
||||
}): ChannelOutboundAdapter {
|
||||
return {
|
||||
deliveryMode: params.deliveryMode ?? "direct",
|
||||
...(params.resolveTarget ? { resolveTarget: params.resolveTarget } : {}),
|
||||
sendText: async ({ cfg, to, text, accountId, deps }) =>
|
||||
withRequiredMessageId(
|
||||
params.channel,
|
||||
await resolveCoreChannelSender(params.channel, deps)(to, text, {
|
||||
cfg,
|
||||
accountId: accountId ?? undefined,
|
||||
}),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
const whatsappResolveTarget = createWhatsAppTestPlugin().outbound?.resolveTarget;
|
||||
|
||||
describe("runCronIsolatedAgentTurn core-channel direct delivery", () => {
|
||||
beforeEach(() => {
|
||||
setupIsolatedAgentTurnMocks();
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([
|
||||
{
|
||||
pluginId: "telegram",
|
||||
plugin: createOutboundTestPlugin({ id: "telegram", outbound: getTelegramOutbound() }),
|
||||
source: "test",
|
||||
},
|
||||
{
|
||||
pluginId: "signal",
|
||||
plugin: createOutboundTestPlugin({ id: "signal", outbound: getSignalOutbound() }),
|
||||
source: "test",
|
||||
},
|
||||
{
|
||||
pluginId: "slack",
|
||||
plugin: createOutboundTestPlugin({ id: "slack", outbound: getSlackOutbound() }),
|
||||
plugin: createOutboundTestPlugin({
|
||||
id: "slack",
|
||||
outbound: createCliDelegatingOutbound({ channel: "slack" }),
|
||||
}),
|
||||
source: "test",
|
||||
},
|
||||
{
|
||||
pluginId: "discord",
|
||||
plugin: createOutboundTestPlugin({ id: "discord", outbound: getDiscordOutbound() }),
|
||||
plugin: createOutboundTestPlugin({
|
||||
id: "discord",
|
||||
outbound: createCliDelegatingOutbound({ channel: "discord" }),
|
||||
}),
|
||||
source: "test",
|
||||
},
|
||||
{
|
||||
pluginId: "whatsapp",
|
||||
plugin: createOutboundTestPlugin({ id: "whatsapp", outbound: getWhatsAppOutbound() }),
|
||||
plugin: createOutboundTestPlugin({
|
||||
id: "whatsapp",
|
||||
outbound: createCliDelegatingOutbound({
|
||||
channel: "whatsapp",
|
||||
deliveryMode: "gateway",
|
||||
resolveTarget: whatsappResolveTarget,
|
||||
}),
|
||||
}),
|
||||
source: "test",
|
||||
},
|
||||
{
|
||||
pluginId: "imessage",
|
||||
plugin: createOutboundTestPlugin({ id: "imessage", outbound: getIMessageOutbound() }),
|
||||
plugin: createOutboundTestPlugin({
|
||||
id: "imessage",
|
||||
outbound: createCliDelegatingOutbound({ channel: "imessage" }),
|
||||
}),
|
||||
source: "test",
|
||||
},
|
||||
]),
|
||||
|
||||
@@ -2,37 +2,30 @@ import { vi } from "vitest";
|
||||
import { loadModelCatalog } from "../agents/model-catalog.js";
|
||||
import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
||||
import { runSubagentAnnounceFlow } from "../agents/subagent-announce.js";
|
||||
import type { ChannelOutboundAdapter } from "../channels/plugins/types.js";
|
||||
import type { ChannelOutboundAdapter, ChannelOutboundContext } from "../channels/plugins/types.js";
|
||||
import { callGateway } from "../gateway/call.js";
|
||||
import { resolveOutboundSendDep } from "../infra/outbound/send-deps.js";
|
||||
import { setActivePluginRegistry } from "../plugins/runtime.js";
|
||||
import {
|
||||
loadBundledPluginPublicSurfaceSync,
|
||||
loadBundledPluginTestApiSync,
|
||||
} from "../test-utils/bundled-plugin-public-surface.js";
|
||||
import { createOutboundTestPlugin, createTestRegistry } from "../test-utils/channel-plugins.js";
|
||||
|
||||
let signalOutboundCache: ChannelOutboundAdapter | undefined;
|
||||
let telegramOutboundCache: ChannelOutboundAdapter | undefined;
|
||||
type TestSendFn = (
|
||||
to: string,
|
||||
text: string,
|
||||
options?: Record<string, unknown>,
|
||||
) => Promise<{ messageId?: string } & Record<string, unknown>>;
|
||||
|
||||
function getSignalOutbound(): ChannelOutboundAdapter {
|
||||
if (!signalOutboundCache) {
|
||||
({ signalOutbound: signalOutboundCache } = loadBundledPluginTestApiSync<{
|
||||
signalOutbound: ChannelOutboundAdapter;
|
||||
}>("signal"));
|
||||
}
|
||||
return signalOutboundCache;
|
||||
}
|
||||
|
||||
function getTelegramOutbound(): ChannelOutboundAdapter {
|
||||
if (!telegramOutboundCache) {
|
||||
({ telegramOutbound: telegramOutboundCache } = loadBundledPluginPublicSurfaceSync<{
|
||||
telegramOutbound: ChannelOutboundAdapter;
|
||||
}>({
|
||||
pluginId: "telegram",
|
||||
artifactBasename: "src/outbound-adapter.js",
|
||||
}));
|
||||
}
|
||||
return telegramOutboundCache;
|
||||
function withRequiredMessageId(
|
||||
channel: "signal" | "telegram",
|
||||
result: Awaited<ReturnType<TestSendFn>>,
|
||||
) {
|
||||
return {
|
||||
channel,
|
||||
...result,
|
||||
messageId:
|
||||
typeof result.messageId === "string" && result.messageId.trim()
|
||||
? result.messageId
|
||||
: `${channel}-test-message`,
|
||||
};
|
||||
}
|
||||
|
||||
function parseTelegramTargetForTest(raw: string): {
|
||||
@@ -74,6 +67,83 @@ function parseTelegramTargetForTest(raw: string): {
|
||||
};
|
||||
}
|
||||
|
||||
function resolveRequiredTarget(label: string, raw: string | undefined) {
|
||||
const trimmed = raw?.trim();
|
||||
if (!trimmed) {
|
||||
return { ok: false as const, error: new Error(`${label} target is required`) };
|
||||
}
|
||||
return { ok: true as const, to: trimmed };
|
||||
}
|
||||
|
||||
function resolveTestSender(
|
||||
channel: "signal" | "telegram",
|
||||
deps: ChannelOutboundContext["deps"],
|
||||
): TestSendFn {
|
||||
const sender = resolveOutboundSendDep<TestSendFn>(deps, channel);
|
||||
if (!sender) {
|
||||
throw new Error(`missing ${channel} sender`);
|
||||
}
|
||||
return sender;
|
||||
}
|
||||
|
||||
const telegramOutboundForTest: ChannelOutboundAdapter = {
|
||||
deliveryMode: "direct",
|
||||
sendText: async () => ({ channel: "telegram", messageId: "telegram-msg" }),
|
||||
resolveTarget: ({ to }) => {
|
||||
const resolved = resolveRequiredTarget("Telegram", to);
|
||||
if (!resolved.ok) {
|
||||
return resolved;
|
||||
}
|
||||
return { ok: true, to: parseTelegramTargetForTest(resolved.to).chatId };
|
||||
},
|
||||
};
|
||||
|
||||
const signalOutboundForTest: ChannelOutboundAdapter = {
|
||||
deliveryMode: "direct",
|
||||
sendText: async ({ cfg, to, text, accountId, deps }) =>
|
||||
withRequiredMessageId(
|
||||
"signal",
|
||||
await resolveTestSender("signal", deps)(to, text, {
|
||||
cfg,
|
||||
accountId: accountId ?? undefined,
|
||||
}),
|
||||
),
|
||||
resolveTarget: ({ to }) => resolveRequiredTarget("Signal", to),
|
||||
};
|
||||
|
||||
telegramOutboundForTest.sendText = async ({ cfg, to, text, accountId, deps, threadId }) =>
|
||||
withRequiredMessageId(
|
||||
"telegram",
|
||||
await resolveTestSender("telegram", deps)(to, text, {
|
||||
cfg,
|
||||
accountId: accountId ?? undefined,
|
||||
messageThreadId: threadId ?? undefined,
|
||||
}),
|
||||
);
|
||||
|
||||
telegramOutboundForTest.sendMedia = async ({
|
||||
cfg,
|
||||
to,
|
||||
text,
|
||||
mediaUrl,
|
||||
mediaLocalRoots,
|
||||
mediaReadFile,
|
||||
accountId,
|
||||
deps,
|
||||
threadId,
|
||||
}) =>
|
||||
withRequiredMessageId(
|
||||
"telegram",
|
||||
await resolveTestSender("telegram", deps)(to, text, {
|
||||
cfg,
|
||||
mediaUrl,
|
||||
mediaLocalRoots,
|
||||
mediaReadFile,
|
||||
accountId: accountId ?? undefined,
|
||||
messageThreadId: threadId ?? undefined,
|
||||
}),
|
||||
);
|
||||
|
||||
export function setupIsolatedAgentTurnMocks(params?: { fast?: boolean }): void {
|
||||
if (params?.fast) {
|
||||
vi.stubEnv("OPENCLAW_TEST_FAST", "1");
|
||||
@@ -88,7 +158,7 @@ export function setupIsolatedAgentTurnMocks(params?: { fast?: boolean }): void {
|
||||
pluginId: "telegram",
|
||||
plugin: createOutboundTestPlugin({
|
||||
id: "telegram",
|
||||
outbound: getTelegramOutbound(),
|
||||
outbound: telegramOutboundForTest,
|
||||
messaging: {
|
||||
parseExplicitTarget: ({ raw }) => {
|
||||
const target = parseTelegramTargetForTest(raw);
|
||||
@@ -104,7 +174,7 @@ export function setupIsolatedAgentTurnMocks(params?: { fast?: boolean }): void {
|
||||
},
|
||||
{
|
||||
pluginId: "signal",
|
||||
plugin: createOutboundTestPlugin({ id: "signal", outbound: getSignalOutbound() }),
|
||||
plugin: createOutboundTestPlugin({ id: "signal", outbound: signalOutboundForTest }),
|
||||
source: "test",
|
||||
},
|
||||
]),
|
||||
|
||||
Reference in New Issue
Block a user