Tests: stabilize bundled native command regressions

This commit is contained in:
Vincent Koc 2026-03-16 22:52:34 -07:00
parent be2e6ca0f6
commit efaa4dc5b3
19 changed files with 144 additions and 106 deletions

View File

@ -5,7 +5,6 @@ import * as dispatcherModule from "../../../../src/auto-reply/reply/provider-dis
import type { OpenClawConfig } from "../../../../src/config/config.js";
import * as pluginCommandsModule from "../../../../src/plugins/commands.js";
import { clearPluginCommands, registerPluginCommand } from "../../../../src/plugins/commands.js";
import { createDiscordNativeCommand } from "./native-command.js";
import {
createMockCommandInteraction,
type MockCommandInteraction,
@ -13,28 +12,31 @@ import {
import { createNoopThreadBindingManager } from "./thread-bindings.js";
type ResolveConfiguredAcpBindingRecordFn =
typeof import("../../../../src/acp/persistent-bindings.js").resolveConfiguredAcpBindingRecord;
typeof import("openclaw/plugin-sdk/conversation-runtime").resolveConfiguredAcpRoute;
type EnsureConfiguredAcpBindingSessionFn =
typeof import("../../../../src/acp/persistent-bindings.js").ensureConfiguredAcpBindingSession;
typeof import("openclaw/plugin-sdk/conversation-runtime").ensureConfiguredAcpRouteReady;
const persistentBindingMocks = vi.hoisted(() => ({
resolveConfiguredAcpBindingRecord: vi.fn<ResolveConfiguredAcpBindingRecordFn>(() => null),
resolveConfiguredAcpBindingRecord: vi.fn<ResolveConfiguredAcpBindingRecordFn>((params) => ({
configuredBinding: null,
route: params.route,
})),
ensureConfiguredAcpBindingSession: vi.fn<EnsureConfiguredAcpBindingSessionFn>(async () => ({
ok: true,
sessionKey: "agent:codex:acp:binding:discord:default:seed",
})),
}));
vi.mock("../../../../src/acp/persistent-bindings.js", async (importOriginal) => {
const actual =
await importOriginal<typeof import("../../../../src/acp/persistent-bindings.js")>();
vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => {
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/conversation-runtime")>();
return {
...actual,
resolveConfiguredAcpBindingRecord: persistentBindingMocks.resolveConfiguredAcpBindingRecord,
ensureConfiguredAcpBindingSession: persistentBindingMocks.ensureConfiguredAcpBindingSession,
resolveConfiguredAcpRoute: persistentBindingMocks.resolveConfiguredAcpBindingRecord,
ensureConfiguredAcpRouteReady: persistentBindingMocks.ensureConfiguredAcpBindingSession,
};
});
import { createDiscordNativeCommand } from "./native-command.js";
function createInteraction(params?: {
channelType?: ChannelType;
channelId?: string;
@ -146,30 +148,40 @@ async function expectPairCommandReply(params: {
}
function setConfiguredBinding(channelId: string, boundSessionKey: string) {
persistentBindingMocks.resolveConfiguredAcpBindingRecord.mockReturnValue({
spec: {
channel: "discord",
accountId: "default",
conversationId: channelId,
agentId: "codex",
mode: "persistent",
},
record: {
bindingId: `config:acp:discord:default:${channelId}`,
targetSessionKey: boundSessionKey,
targetKind: "session",
conversation: {
persistentBindingMocks.resolveConfiguredAcpBindingRecord.mockImplementation((params) => ({
configuredBinding: {
spec: {
channel: "discord",
accountId: "default",
accountId: params.accountId,
conversationId: channelId,
parentConversationId: params.parentConversationId,
agentId: "codex",
mode: "persistent",
},
record: {
bindingId: `config:acp:discord:${params.accountId}:${channelId}`,
targetSessionKey: boundSessionKey,
targetKind: "session",
conversation: {
channel: "discord",
accountId: params.accountId,
conversationId: channelId,
},
status: "active",
boundAt: 0,
},
status: "active",
boundAt: 0,
},
});
boundSessionKey,
boundAgentId: "codex",
route: {
...params.route,
agentId: "codex",
sessionKey: boundSessionKey,
matchedBy: "binding.channel",
},
}));
persistentBindingMocks.ensureConfiguredAcpBindingSession.mockResolvedValue({
ok: true,
sessionKey: boundSessionKey,
});
}
@ -221,11 +233,13 @@ describe("Discord native plugin command dispatch", () => {
vi.restoreAllMocks();
clearPluginCommands();
persistentBindingMocks.resolveConfiguredAcpBindingRecord.mockReset();
persistentBindingMocks.resolveConfiguredAcpBindingRecord.mockReturnValue(null);
persistentBindingMocks.resolveConfiguredAcpBindingRecord.mockImplementation((params) => ({
configuredBinding: null,
route: params.route,
}));
persistentBindingMocks.ensureConfiguredAcpBindingSession.mockReset();
persistentBindingMocks.ensureConfiguredAcpBindingSession.mockResolvedValue({
ok: true,
sessionKey: "agent:codex:acp:binding:discord:default:seed",
});
});

View File

@ -3,7 +3,6 @@ import {
applyAccountNameToChannelSection,
createPatchedAccountSetupAdapter,
DEFAULT_ACCOUNT_ID,
formatDocsLink,
migrateBaseNameToDefaultAccount,
normalizeAccountId,
noteChannelLookupFailure,
@ -14,6 +13,7 @@ import {
setSetupChannelEnabled,
type OpenClawConfig,
} from "openclaw/plugin-sdk/setup";
import { formatDocsLink } from "../../../src/terminal/links.js";
import {
createAllowlistSetupWizardProxy,
type ChannelSetupAdapter,

View File

@ -1,6 +1,5 @@
import {
DEFAULT_ACCOUNT_ID,
formatDocsLink,
noteChannelLookupFailure,
noteChannelLookupSummary,
type OpenClawConfig,
@ -12,6 +11,7 @@ import {
setSetupChannelEnabled,
type WizardPrompter,
} from "openclaw/plugin-sdk/setup";
import { formatDocsLink } from "../../../src/terminal/links.js";
import { type ChannelSetupDmPolicy, type ChannelSetupWizard } from "openclaw/plugin-sdk/setup";
import { inspectDiscordAccount } from "./account-inspect.js";
import {

View File

@ -3,12 +3,10 @@ import {
createScopedAccountConfigAccessors,
createScopedChannelConfigBase,
} from "openclaw/plugin-sdk/channel-config-helpers";
import {
buildChannelConfigSchema,
DiscordConfigSchema,
getChatChannelMeta,
type ChannelPlugin,
} from "openclaw/plugin-sdk/discord";
import { buildChannelConfigSchema } from "../../../src/channels/plugins/config-schema.js";
import type { ChannelPlugin } from "../../../src/channels/plugins/types.plugin.js";
import { getChatChannelMeta } from "../../../src/channels/registry.js";
import { DiscordConfigSchema } from "../../../src/config/zod-schema.providers-core.js";
import { inspectDiscordAccount } from "./account-inspect.js";
import {
listDiscordAccountIds,

View File

@ -1,7 +1,6 @@
import {
applyAccountNameToChannelSection,
DEFAULT_ACCOUNT_ID,
formatDocsLink,
migrateBaseNameToDefaultAccount,
normalizeAccountId,
parseSetupEntriesAllowingWildcard,
@ -11,6 +10,7 @@ import {
type OpenClawConfig,
type WizardPrompter,
} from "openclaw/plugin-sdk/setup";
import { formatDocsLink } from "../../../src/terminal/links.js";
import type {
ChannelSetupAdapter,
ChannelSetupDmPolicy,

View File

@ -1,8 +1,8 @@
import {
detectBinary,
setSetupChannelEnabled,
type ChannelSetupWizard,
} from "openclaw/plugin-sdk/setup";
import { detectBinary } from "../../../src/plugins/setup-binary.js";
import { listIMessageAccountIds, resolveIMessageAccount } from "./accounts.js";
import {
createIMessageCliPathTextInput,

View File

@ -3,17 +3,19 @@ import {
collectAllowlistProviderRestrictSendersWarnings,
} from "openclaw/plugin-sdk/channel-policy";
import {
buildChannelConfigSchema,
DEFAULT_ACCOUNT_ID,
deleteAccountFromConfigSection,
formatTrimmedAllowFromEntries,
getChatChannelMeta,
IMessageConfigSchema,
resolveIMessageConfigAllowFrom,
resolveIMessageConfigDefaultTo,
} from "../../../src/plugin-sdk/channel-config-helpers.js";
import { buildChannelConfigSchema } from "../../../src/channels/plugins/config-schema.js";
import {
deleteAccountFromConfigSection,
setAccountEnabledInConfigSection,
type ChannelPlugin,
} from "openclaw/plugin-sdk/imessage";
} from "../../../src/channels/plugins/config-helpers.js";
import type { ChannelPlugin } from "../../../src/channels/plugins/types.plugin.js";
import { getChatChannelMeta } from "../../../src/channels/registry.js";
import { IMessageConfigSchema } from "../../../src/config/zod-schema.providers-core.js";
import { DEFAULT_ACCOUNT_ID } from "../../../src/routing/session-key.js";
import {
listIMessageAccountIds,
resolveDefaultIMessageAccountId,

View File

@ -1,8 +1,6 @@
import {
applyAccountNameToChannelSection,
DEFAULT_ACCOUNT_ID,
formatCliCommand,
formatDocsLink,
migrateBaseNameToDefaultAccount,
normalizeAccountId,
normalizeE164,
@ -19,6 +17,8 @@ import type {
ChannelSetupWizard,
ChannelSetupWizardTextInput,
} from "openclaw/plugin-sdk/setup";
import { formatCliCommand } from "../../../src/cli/command-format.js";
import { formatDocsLink } from "../../../src/terminal/links.js";
import {
listSignalAccountIds,
resolveDefaultSignalAccountId,

View File

@ -1,9 +1,9 @@
import {
detectBinary,
installSignalCli,
setSetupChannelEnabled,
type ChannelSetupWizard,
} from "openclaw/plugin-sdk/setup";
import { detectBinary } from "../../../src/plugins/setup-binary.js";
import { installSignalCli } from "../../../src/plugins/signal-cli-install.js";
import { listSignalAccountIds, resolveSignalAccount } from "./accounts.js";
import {
createSignalCliPathTextInput,

View File

@ -4,15 +4,15 @@ import {
collectAllowlistProviderRestrictSendersWarnings,
} from "openclaw/plugin-sdk/channel-policy";
import {
buildChannelConfigSchema,
DEFAULT_ACCOUNT_ID,
deleteAccountFromConfigSection,
getChatChannelMeta,
normalizeE164,
setAccountEnabledInConfigSection,
SignalConfigSchema,
type ChannelPlugin,
} from "openclaw/plugin-sdk/signal";
} from "../../../src/channels/plugins/config-helpers.js";
import { buildChannelConfigSchema } from "../../../src/channels/plugins/config-schema.js";
import type { ChannelPlugin } from "../../../src/channels/plugins/types.plugin.js";
import { getChatChannelMeta } from "../../../src/channels/registry.js";
import { SignalConfigSchema } from "../../../src/config/zod-schema.providers-core.js";
import { DEFAULT_ACCOUNT_ID } from "../../../src/routing/session-key.js";
import { normalizeE164 } from "../../../src/utils.js";
import {
listSignalAccountIds,
resolveDefaultSignalAccountId,

View File

@ -3,7 +3,6 @@ import {
createAllowlistSetupWizardProxy,
createPatchedAccountSetupAdapter,
DEFAULT_ACCOUNT_ID,
formatDocsLink,
hasConfiguredSecretInput,
migrateBaseNameToDefaultAccount,
normalizeAccountId,
@ -16,6 +15,7 @@ import {
setLegacyChannelDmPolicyWithAllowFrom,
setSetupChannelEnabled,
} from "openclaw/plugin-sdk/setup";
import { formatDocsLink } from "../../../src/terminal/links.js";
import {
type ChannelSetupAdapter,
type ChannelSetupDmPolicy,

View File

@ -1,6 +1,5 @@
import {
DEFAULT_ACCOUNT_ID,
formatDocsLink,
hasConfiguredSecretInput,
noteChannelLookupFailure,
noteChannelLookupSummary,
@ -15,6 +14,7 @@ import {
setSetupChannelEnabled,
type WizardPrompter,
} from "openclaw/plugin-sdk/setup";
import { formatDocsLink } from "../../../src/terminal/links.js";
import type {
ChannelSetupDmPolicy,
ChannelSetupWizard,

View File

@ -3,18 +3,14 @@ import {
createScopedAccountConfigAccessors,
createScopedChannelConfigBase,
} from "openclaw/plugin-sdk/channel-config-helpers";
import {
formatDocsLink,
hasConfiguredSecretInput,
patchChannelConfigForAccount,
} from "openclaw/plugin-sdk/setup";
import {
buildChannelConfigSchema,
getChatChannelMeta,
SlackConfigSchema,
type ChannelPlugin,
type OpenClawConfig,
} from "openclaw/plugin-sdk/slack";
import { buildChannelConfigSchema } from "../../../src/channels/plugins/config-schema.js";
import { patchChannelConfigForAccount } from "../../../src/channels/plugins/setup-wizard-helpers.js";
import type { ChannelPlugin } from "../../../src/channels/plugins/types.plugin.js";
import { getChatChannelMeta } from "../../../src/channels/registry.js";
import type { OpenClawConfig } from "../../../src/config/config.js";
import { hasConfiguredSecretInput } from "../../../src/config/types.secrets.js";
import { SlackConfigSchema } from "../../../src/config/zod-schema.providers-core.js";
import { formatDocsLink } from "../../../src/terminal/links.js";
import { inspectSlackAccount } from "./account-inspect.js";
import {
listSlackAccountIds,

View File

@ -1,20 +1,27 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../../src/config/config.js";
import { clearPluginCommands, registerPluginCommand } from "../../../src/plugins/commands.js";
const deliveryMocks = vi.hoisted(() => ({
deliverReplies: vi.fn(async () => ({ delivered: true })),
}));
vi.mock("./bot/delivery.js", () => ({
deliverReplies: deliveryMocks.deliverReplies,
}));
import { registerTelegramNativeCommands } from "./bot-native-commands.js";
import {
createCommandBot,
createNativeCommandTestParams,
createPrivateCommandContext,
deliverReplies,
resetNativeCommandMenuMocks,
waitForRegisteredCommands,
} from "./bot-native-commands.menu-test-support.js";
describe("registerTelegramNativeCommands real plugin registry", () => {
beforeEach(() => {
clearPluginCommands();
resetNativeCommandMenuMocks();
deliveryMocks.deliverReplies.mockClear();
deliveryMocks.deliverReplies.mockResolvedValue({ delivered: true });
});
afterEach(() => {
@ -49,7 +56,7 @@ describe("registerTelegramNativeCommands real plugin registry", () => {
await handler?.(createPrivateCommandContext({ match: "now" }));
expect(deliverReplies).toHaveBeenCalledWith(
expect(deliveryMocks.deliverReplies).toHaveBeenCalledWith(
expect.objectContaining({
replies: [expect.objectContaining({ text: "paired:now" })],
}),
@ -89,7 +96,7 @@ describe("registerTelegramNativeCommands real plugin registry", () => {
await handler?.(createPrivateCommandContext({ match: "now", messageId: 2 }));
expect(deliverReplies).toHaveBeenCalledWith(
expect(deliveryMocks.deliverReplies).toHaveBeenCalledWith(
expect.objectContaining({
replies: [expect.objectContaining({ text: "paired:now" })],
}),
@ -157,7 +164,7 @@ describe("registerTelegramNativeCommands real plugin registry", () => {
}),
);
expect(deliverReplies).toHaveBeenCalledWith(
expect(deliveryMocks.deliverReplies).toHaveBeenCalledWith(
expect.objectContaining({
replies: [expect.objectContaining({ text: "paired:now" })],
}),

View File

@ -5,14 +5,30 @@ import { STATE_DIR } from "../../../src/config/paths.js";
import { TELEGRAM_COMMAND_NAME_PATTERN } from "../../../src/config/telegram-custom-commands.js";
import type { TelegramAccountConfig } from "../../../src/config/types.js";
import type { RuntimeEnv } from "../../../src/runtime.js";
const skillCommandMocks = vi.hoisted(() => ({
listSkillCommandsForAgents: vi.fn(() => []),
}));
const deliveryMocks = vi.hoisted(() => ({
deliverReplies: vi.fn(async () => ({ delivered: true })),
}));
vi.mock("openclaw/plugin-sdk/reply-runtime", async (importOriginal) => {
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/reply-runtime")>();
return {
...actual,
listSkillCommandsForAgents: skillCommandMocks.listSkillCommandsForAgents,
};
});
vi.mock("./bot/delivery.js", () => ({
deliverReplies: deliveryMocks.deliverReplies,
}));
import { registerTelegramNativeCommands } from "./bot-native-commands.js";
import {
createCommandBot,
createNativeCommandTestParams,
createPrivateCommandContext,
deliverReplies,
listSkillCommandsForAgents,
resetNativeCommandMenuMocks,
waitForRegisteredCommands,
} from "./bot-native-commands.menu-test-support.js";
@ -29,7 +45,10 @@ vi.mock("../../../src/plugins/commands.js", () => ({
describe("registerTelegramNativeCommands", () => {
beforeEach(() => {
resetNativeCommandMenuMocks();
skillCommandMocks.listSkillCommandsForAgents.mockClear();
skillCommandMocks.listSkillCommandsForAgents.mockReturnValue([]);
deliveryMocks.deliverReplies.mockClear();
deliveryMocks.deliverReplies.mockResolvedValue({ delivered: true });
pluginCommandMocks.getPluginCommandSpecs.mockClear();
pluginCommandMocks.getPluginCommandSpecs.mockReturnValue([]);
pluginCommandMocks.matchPluginCommand.mockClear();
@ -53,7 +72,7 @@ describe("registerTelegramNativeCommands", () => {
registerTelegramNativeCommands(createNativeCommandTestParams(cfg, { accountId: "bot-a" }));
expect(listSkillCommandsForAgents).toHaveBeenCalledWith({
expect(skillCommandMocks.listSkillCommandsForAgents).toHaveBeenCalledWith({
cfg,
agentIds: ["butler"],
});
@ -68,7 +87,7 @@ describe("registerTelegramNativeCommands", () => {
registerTelegramNativeCommands(createNativeCommandTestParams(cfg, { accountId: "bot-a" }));
expect(listSkillCommandsForAgents).toHaveBeenCalledWith({
expect(skillCommandMocks.listSkillCommandsForAgents).toHaveBeenCalledWith({
cfg,
agentIds: ["main"],
});
@ -215,7 +234,7 @@ describe("registerTelegramNativeCommands", () => {
expect(handler).toBeTruthy();
await handler?.(createPrivateCommandContext());
expect(deliverReplies).toHaveBeenCalledWith(
expect(deliveryMocks.deliverReplies).toHaveBeenCalledWith(
expect.objectContaining({
mediaLocalRoots: expect.arrayContaining([path.join(STATE_DIR, "workspace-work")]),
}),
@ -263,7 +282,7 @@ describe("registerTelegramNativeCommands", () => {
expect(handler).toBeTruthy();
await handler?.(createPrivateCommandContext());
expect(deliverReplies).toHaveBeenCalledWith(
expect(deliveryMocks.deliverReplies).toHaveBeenCalledWith(
expect.objectContaining({
silent: true,
replies: [expect.objectContaining({ isError: true })],

View File

@ -1,8 +1,6 @@
import {
applyAccountNameToChannelSection,
DEFAULT_ACCOUNT_ID,
formatCliCommand,
formatDocsLink,
migrateBaseNameToDefaultAccount,
normalizeAccountId,
patchChannelConfigForAccount,
@ -11,6 +9,8 @@ import {
type OpenClawConfig,
type WizardPrompter,
} from "openclaw/plugin-sdk/setup";
import { formatCliCommand } from "../../../src/cli/command-format.js";
import { formatDocsLink } from "../../../src/terminal/links.js";
import type { ChannelSetupAdapter, ChannelSetupDmPolicy } from "openclaw/plugin-sdk/setup";
import { resolveDefaultTelegramAccountId, resolveTelegramAccount } from "./accounts.js";
import { fetchTelegramChatId } from "./api-fetch.js";

View File

@ -3,14 +3,12 @@ import {
createScopedAccountConfigAccessors,
createScopedChannelConfigBase,
} from "openclaw/plugin-sdk/channel-config-helpers";
import {
buildChannelConfigSchema,
getChatChannelMeta,
normalizeAccountId,
TelegramConfigSchema,
type ChannelPlugin,
type OpenClawConfig,
} from "openclaw/plugin-sdk/telegram";
import { buildChannelConfigSchema } from "../../../src/channels/plugins/config-schema.js";
import type { ChannelPlugin } from "../../../src/channels/plugins/types.plugin.js";
import { getChatChannelMeta } from "../../../src/channels/registry.js";
import type { OpenClawConfig } from "../../../src/config/config.js";
import { TelegramConfigSchema } from "../../../src/config/zod-schema.providers-core.js";
import { normalizeAccountId } from "../../../src/routing/session-key.js";
import { inspectTelegramAccount } from "./account-inspect.js";
import {
listTelegramAccountIds,

View File

@ -1,8 +1,6 @@
import path from "node:path";
import {
DEFAULT_ACCOUNT_ID,
formatCliCommand,
formatDocsLink,
normalizeAccountId,
normalizeAllowFromEntries,
normalizeE164,
@ -12,6 +10,8 @@ import {
type DmPolicy,
type OpenClawConfig,
} from "openclaw/plugin-sdk/setup";
import { formatCliCommand } from "../../../src/cli/command-format.js";
import { formatDocsLink } from "../../../src/terminal/links.js";
import type { ChannelSetupWizard } from "openclaw/plugin-sdk/setup";
import { listWhatsAppAccountIds, resolveWhatsAppAuthDir } from "./accounts.js";
import { loginWeb } from "./login.js";

View File

@ -1,20 +1,24 @@
import {
buildAccountScopedDmSecurityPolicy,
buildChannelConfigSchema,
collectAllowlistProviderGroupPolicyWarnings,
collectOpenGroupPolicyRouteAllowlistWarnings,
DEFAULT_ACCOUNT_ID,
} from "openclaw/plugin-sdk/channel-policy";
import {
formatWhatsAppConfigAllowFromEntries,
getChatChannelMeta,
normalizeE164,
resolveWhatsAppConfigAllowFrom,
resolveWhatsAppConfigDefaultTo,
resolveWhatsAppGroupIntroHint,
} from "../../../src/plugin-sdk/channel-config-helpers.js";
import { buildChannelConfigSchema } from "../../../src/channels/plugins/config-schema.js";
import type { ChannelPlugin } from "../../../src/channels/plugins/types.plugin.js";
import {
resolveWhatsAppGroupRequireMention,
resolveWhatsAppGroupToolPolicy,
WhatsAppConfigSchema,
type ChannelPlugin,
} from "openclaw/plugin-sdk/whatsapp";
} from "../../../src/channels/plugins/group-mentions.js";
import { resolveWhatsAppGroupIntroHint } from "../../../src/channels/plugins/whatsapp-shared.js";
import { getChatChannelMeta } from "../../../src/channels/registry.js";
import { WhatsAppConfigSchema } from "../../../src/config/zod-schema.providers-whatsapp.js";
import { DEFAULT_ACCOUNT_ID } from "../../../src/routing/session-key.js";
import { normalizeE164 } from "../../../src/utils.js";
import {
listWhatsAppAccountIds,
resolveDefaultWhatsAppAccountId,