refactor: localize cron channel test outbounds

This commit is contained in:
Shakker
2026-04-01 16:08:37 +01:00
committed by Shakker
parent e9a1f7818c
commit 7a9ad3820e
2 changed files with 171 additions and 114 deletions

View File

@@ -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",
},
]),

View File

@@ -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",
},
]),