mirror of https://github.com/openclaw/openclaw.git
test: add channel resolution helper coverage
This commit is contained in:
parent
c3fadff0ce
commit
fdbfdec341
|
|
@ -0,0 +1,196 @@
|
|||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const loadConfigMock = vi.hoisted(() => vi.fn());
|
||||
const listEnabledDiscordAccountsMock = vi.hoisted(() => vi.fn());
|
||||
const isDiscordExecApprovalClientEnabledMock = vi.hoisted(() => vi.fn());
|
||||
const listEnabledTelegramAccountsMock = vi.hoisted(() => vi.fn());
|
||||
const isTelegramExecApprovalClientEnabledMock = vi.hoisted(() => vi.fn());
|
||||
const normalizeMessageChannelMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("../config/config.js", () => ({
|
||||
loadConfig: (...args: unknown[]) => loadConfigMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../discord/accounts.js", () => ({
|
||||
listEnabledDiscordAccounts: (...args: unknown[]) => listEnabledDiscordAccountsMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../discord/exec-approvals.js", () => ({
|
||||
isDiscordExecApprovalClientEnabled: (...args: unknown[]) =>
|
||||
isDiscordExecApprovalClientEnabledMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../telegram/accounts.js", () => ({
|
||||
listEnabledTelegramAccounts: (...args: unknown[]) => listEnabledTelegramAccountsMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../telegram/exec-approvals.js", () => ({
|
||||
isTelegramExecApprovalClientEnabled: (...args: unknown[]) =>
|
||||
isTelegramExecApprovalClientEnabledMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../utils/message-channel.js", () => ({
|
||||
INTERNAL_MESSAGE_CHANNEL: "web",
|
||||
normalizeMessageChannel: (...args: unknown[]) => normalizeMessageChannelMock(...args),
|
||||
}));
|
||||
|
||||
import {
|
||||
hasConfiguredExecApprovalDmRoute,
|
||||
resolveExecApprovalInitiatingSurfaceState,
|
||||
} from "./exec-approval-surface.js";
|
||||
|
||||
describe("resolveExecApprovalInitiatingSurfaceState", () => {
|
||||
beforeEach(() => {
|
||||
loadConfigMock.mockReset();
|
||||
listEnabledDiscordAccountsMock.mockReset();
|
||||
isDiscordExecApprovalClientEnabledMock.mockReset();
|
||||
listEnabledTelegramAccountsMock.mockReset();
|
||||
isTelegramExecApprovalClientEnabledMock.mockReset();
|
||||
normalizeMessageChannelMock.mockReset();
|
||||
normalizeMessageChannelMock.mockImplementation((value?: string | null) =>
|
||||
typeof value === "string" ? value.trim().toLowerCase() : undefined,
|
||||
);
|
||||
});
|
||||
|
||||
it("treats web UI, terminal UI, and missing channels as enabled", () => {
|
||||
expect(resolveExecApprovalInitiatingSurfaceState({ channel: null })).toEqual({
|
||||
kind: "enabled",
|
||||
channel: undefined,
|
||||
channelLabel: "this platform",
|
||||
});
|
||||
expect(resolveExecApprovalInitiatingSurfaceState({ channel: "tui" })).toEqual({
|
||||
kind: "enabled",
|
||||
channel: "tui",
|
||||
channelLabel: "terminal UI",
|
||||
});
|
||||
expect(resolveExecApprovalInitiatingSurfaceState({ channel: "web" })).toEqual({
|
||||
kind: "enabled",
|
||||
channel: "web",
|
||||
channelLabel: "Web UI",
|
||||
});
|
||||
});
|
||||
|
||||
it("uses the provided cfg for telegram and discord client enablement", () => {
|
||||
isTelegramExecApprovalClientEnabledMock.mockReturnValueOnce(true);
|
||||
isDiscordExecApprovalClientEnabledMock.mockReturnValueOnce(false);
|
||||
const cfg = { channels: {} };
|
||||
|
||||
expect(
|
||||
resolveExecApprovalInitiatingSurfaceState({
|
||||
channel: "telegram",
|
||||
accountId: "main",
|
||||
cfg: cfg as never,
|
||||
}),
|
||||
).toEqual({
|
||||
kind: "enabled",
|
||||
channel: "telegram",
|
||||
channelLabel: "Telegram",
|
||||
});
|
||||
expect(
|
||||
resolveExecApprovalInitiatingSurfaceState({
|
||||
channel: "discord",
|
||||
accountId: "main",
|
||||
cfg: cfg as never,
|
||||
}),
|
||||
).toEqual({
|
||||
kind: "disabled",
|
||||
channel: "discord",
|
||||
channelLabel: "Discord",
|
||||
});
|
||||
|
||||
expect(loadConfigMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("loads config lazily when cfg is omitted and marks unsupported channels", () => {
|
||||
loadConfigMock.mockReturnValueOnce({ loaded: true });
|
||||
isTelegramExecApprovalClientEnabledMock.mockReturnValueOnce(false);
|
||||
|
||||
expect(
|
||||
resolveExecApprovalInitiatingSurfaceState({
|
||||
channel: "telegram",
|
||||
accountId: "main",
|
||||
}),
|
||||
).toEqual({
|
||||
kind: "disabled",
|
||||
channel: "telegram",
|
||||
channelLabel: "Telegram",
|
||||
});
|
||||
expect(loadConfigMock).toHaveBeenCalledOnce();
|
||||
|
||||
expect(resolveExecApprovalInitiatingSurfaceState({ channel: "signal" })).toEqual({
|
||||
kind: "unsupported",
|
||||
channel: "signal",
|
||||
channelLabel: "Signal",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("hasConfiguredExecApprovalDmRoute", () => {
|
||||
beforeEach(() => {
|
||||
listEnabledDiscordAccountsMock.mockReset();
|
||||
listEnabledTelegramAccountsMock.mockReset();
|
||||
});
|
||||
|
||||
it("returns true when any enabled account routes approvals to DM or both", () => {
|
||||
listEnabledDiscordAccountsMock.mockReturnValueOnce([
|
||||
{
|
||||
config: {
|
||||
execApprovals: {
|
||||
enabled: true,
|
||||
approvers: ["a"],
|
||||
target: "channel",
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
listEnabledTelegramAccountsMock.mockReturnValueOnce([
|
||||
{
|
||||
config: {
|
||||
execApprovals: {
|
||||
enabled: true,
|
||||
approvers: ["a"],
|
||||
target: "both",
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
expect(hasConfiguredExecApprovalDmRoute({} as never)).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false when exec approvals are disabled or have no DM route", () => {
|
||||
listEnabledDiscordAccountsMock.mockReturnValueOnce([
|
||||
{
|
||||
config: {
|
||||
execApprovals: {
|
||||
enabled: false,
|
||||
approvers: ["a"],
|
||||
target: "dm",
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
listEnabledTelegramAccountsMock.mockReturnValueOnce([
|
||||
{
|
||||
config: {
|
||||
execApprovals: {
|
||||
enabled: true,
|
||||
approvers: [],
|
||||
target: "dm",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
config: {
|
||||
execApprovals: {
|
||||
enabled: true,
|
||||
approvers: ["a"],
|
||||
target: "channel",
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
expect(hasConfiguredExecApprovalDmRoute({} as never)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const resolveDefaultAgentIdMock = vi.hoisted(() => vi.fn());
|
||||
const resolveAgentWorkspaceDirMock = vi.hoisted(() => vi.fn());
|
||||
const getChannelPluginMock = vi.hoisted(() => vi.fn());
|
||||
const applyPluginAutoEnableMock = vi.hoisted(() => vi.fn());
|
||||
const loadOpenClawPluginsMock = vi.hoisted(() => vi.fn());
|
||||
const getActivePluginRegistryMock = vi.hoisted(() => vi.fn());
|
||||
const getActivePluginRegistryKeyMock = vi.hoisted(() => vi.fn());
|
||||
const normalizeMessageChannelMock = vi.hoisted(() => vi.fn());
|
||||
const isDeliverableMessageChannelMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("../../agents/agent-scope.js", () => ({
|
||||
resolveDefaultAgentId: (...args: unknown[]) => resolveDefaultAgentIdMock(...args),
|
||||
resolveAgentWorkspaceDir: (...args: unknown[]) => resolveAgentWorkspaceDirMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../../channels/plugins/index.js", () => ({
|
||||
getChannelPlugin: (...args: unknown[]) => getChannelPluginMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../../config/plugin-auto-enable.js", () => ({
|
||||
applyPluginAutoEnable: (...args: unknown[]) => applyPluginAutoEnableMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../../plugins/loader.js", () => ({
|
||||
loadOpenClawPlugins: (...args: unknown[]) => loadOpenClawPluginsMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../../plugins/runtime.js", () => ({
|
||||
getActivePluginRegistry: (...args: unknown[]) => getActivePluginRegistryMock(...args),
|
||||
getActivePluginRegistryKey: (...args: unknown[]) => getActivePluginRegistryKeyMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../../utils/message-channel.js", () => ({
|
||||
normalizeMessageChannel: (...args: unknown[]) => normalizeMessageChannelMock(...args),
|
||||
isDeliverableMessageChannel: (...args: unknown[]) => isDeliverableMessageChannelMock(...args),
|
||||
}));
|
||||
|
||||
import { importFreshModule } from "../../../test/helpers/import-fresh.js";
|
||||
|
||||
async function importChannelResolution(scope: string) {
|
||||
return await importFreshModule<typeof import("./channel-resolution.js")>(
|
||||
import.meta.url,
|
||||
`./channel-resolution.js?scope=${scope}`,
|
||||
);
|
||||
}
|
||||
|
||||
describe("outbound channel resolution", () => {
|
||||
beforeEach(() => {
|
||||
resolveDefaultAgentIdMock.mockReset();
|
||||
resolveAgentWorkspaceDirMock.mockReset();
|
||||
getChannelPluginMock.mockReset();
|
||||
applyPluginAutoEnableMock.mockReset();
|
||||
loadOpenClawPluginsMock.mockReset();
|
||||
getActivePluginRegistryMock.mockReset();
|
||||
getActivePluginRegistryKeyMock.mockReset();
|
||||
normalizeMessageChannelMock.mockReset();
|
||||
isDeliverableMessageChannelMock.mockReset();
|
||||
|
||||
normalizeMessageChannelMock.mockImplementation((value?: string | null) =>
|
||||
typeof value === "string" ? value.trim().toLowerCase() : undefined,
|
||||
);
|
||||
isDeliverableMessageChannelMock.mockImplementation((value?: string) =>
|
||||
["telegram", "discord", "slack"].includes(String(value)),
|
||||
);
|
||||
getActivePluginRegistryMock.mockReturnValue({ channels: [] });
|
||||
getActivePluginRegistryKeyMock.mockReturnValue("registry-key");
|
||||
applyPluginAutoEnableMock.mockReturnValue({ config: { autoEnabled: true } });
|
||||
resolveDefaultAgentIdMock.mockReturnValue("main");
|
||||
resolveAgentWorkspaceDirMock.mockReturnValue("/tmp/workspace");
|
||||
});
|
||||
|
||||
it("normalizes deliverable channels and rejects unknown ones", async () => {
|
||||
const channelResolution = await importChannelResolution("normalize");
|
||||
|
||||
expect(channelResolution.normalizeDeliverableOutboundChannel(" Telegram ")).toBe("telegram");
|
||||
expect(channelResolution.normalizeDeliverableOutboundChannel("unknown")).toBeUndefined();
|
||||
expect(channelResolution.normalizeDeliverableOutboundChannel(null)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns the already-registered plugin without bootstrapping", async () => {
|
||||
const plugin = { id: "telegram" };
|
||||
getChannelPluginMock.mockReturnValueOnce(plugin);
|
||||
const channelResolution = await importChannelResolution("existing-plugin");
|
||||
|
||||
expect(
|
||||
channelResolution.resolveOutboundChannelPlugin({
|
||||
channel: "telegram",
|
||||
cfg: {} as never,
|
||||
}),
|
||||
).toBe(plugin);
|
||||
expect(loadOpenClawPluginsMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("falls back to the active registry when getChannelPlugin misses", async () => {
|
||||
const plugin = { id: "telegram" };
|
||||
getChannelPluginMock.mockReturnValue(undefined);
|
||||
getActivePluginRegistryMock.mockReturnValue({
|
||||
channels: [{ plugin }],
|
||||
});
|
||||
const channelResolution = await importChannelResolution("direct-registry");
|
||||
|
||||
expect(
|
||||
channelResolution.resolveOutboundChannelPlugin({
|
||||
channel: "telegram",
|
||||
cfg: {} as never,
|
||||
}),
|
||||
).toBe(plugin);
|
||||
});
|
||||
|
||||
it("bootstraps plugins once per registry key and returns the newly loaded plugin", async () => {
|
||||
const plugin = { id: "telegram" };
|
||||
getChannelPluginMock.mockReturnValueOnce(undefined).mockReturnValueOnce(plugin);
|
||||
const channelResolution = await importChannelResolution("bootstrap-success");
|
||||
|
||||
expect(
|
||||
channelResolution.resolveOutboundChannelPlugin({
|
||||
channel: "telegram",
|
||||
cfg: { channels: {} } as never,
|
||||
}),
|
||||
).toBe(plugin);
|
||||
expect(loadOpenClawPluginsMock).toHaveBeenCalledWith({
|
||||
config: { autoEnabled: true },
|
||||
workspaceDir: "/tmp/workspace",
|
||||
});
|
||||
|
||||
getChannelPluginMock.mockReturnValue(undefined);
|
||||
channelResolution.resolveOutboundChannelPlugin({
|
||||
channel: "telegram",
|
||||
cfg: { channels: {} } as never,
|
||||
});
|
||||
expect(loadOpenClawPluginsMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("retries bootstrap after a transient load failure", async () => {
|
||||
getChannelPluginMock.mockReturnValue(undefined);
|
||||
loadOpenClawPluginsMock.mockImplementationOnce(() => {
|
||||
throw new Error("transient");
|
||||
});
|
||||
const channelResolution = await importChannelResolution("bootstrap-retry");
|
||||
|
||||
expect(
|
||||
channelResolution.resolveOutboundChannelPlugin({
|
||||
channel: "telegram",
|
||||
cfg: { channels: {} } as never,
|
||||
}),
|
||||
).toBeUndefined();
|
||||
|
||||
channelResolution.resolveOutboundChannelPlugin({
|
||||
channel: "telegram",
|
||||
cfg: { channels: {} } as never,
|
||||
});
|
||||
expect(loadOpenClawPluginsMock).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue