mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:10:43 +00:00
Tests: avoid bundled Discord runtime lookup
This commit is contained in:
@@ -1,17 +1,25 @@
|
||||
import { ChannelType } from "discord-api-types/v10";
|
||||
import type { NativeCommandSpec } from "openclaw/plugin-sdk/command-auth";
|
||||
import { resolveDirectStatusReplyForSession } from "openclaw/plugin-sdk/command-status-runtime";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { clearPluginCommands, registerPluginCommand } from "openclaw/plugin-sdk/plugin-runtime";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
clearPluginCommands,
|
||||
executePluginCommand,
|
||||
matchPluginCommand,
|
||||
registerPluginCommand,
|
||||
} from "openclaw/plugin-sdk/plugin-runtime";
|
||||
import { dispatchReplyWithDispatcher } from "openclaw/plugin-sdk/reply-dispatch-runtime";
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
createTestRegistry,
|
||||
setActivePluginRegistry,
|
||||
} from "../../../../test/helpers/plugins/plugin-registry.js";
|
||||
import { resolveDiscordNativeInteractionRouteState } from "./native-command-route.js";
|
||||
import {
|
||||
createMockCommandInteraction,
|
||||
type MockCommandInteraction,
|
||||
} from "./native-command.test-helpers.js";
|
||||
import { createNoopThreadBindingManager } from "./thread-bindings.js";
|
||||
import { createNoopThreadBindingManager } from "./thread-bindings.manager.js";
|
||||
|
||||
let createDiscordNativeCommand: typeof import("./native-command.js").createDiscordNativeCommand;
|
||||
let discordNativeCommandTesting: typeof import("./native-command.js").__testing;
|
||||
@@ -22,33 +30,6 @@ const runtimeModuleMocks = vi.hoisted(() => ({
|
||||
resolveDirectStatusReplyForSession: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/plugin-runtime", async () => {
|
||||
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/plugin-runtime")>(
|
||||
"openclaw/plugin-sdk/plugin-runtime",
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
matchPluginCommand: (...args: unknown[]) => runtimeModuleMocks.matchPluginCommand(...args),
|
||||
executePluginCommand: (...args: unknown[]) => runtimeModuleMocks.executePluginCommand(...args),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/reply-runtime", async () => {
|
||||
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/reply-runtime")>(
|
||||
"openclaw/plugin-sdk/reply-runtime",
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
dispatchReplyWithDispatcher: (...args: unknown[]) =>
|
||||
runtimeModuleMocks.dispatchReplyWithDispatcher(...args),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/command-status-runtime", () => ({
|
||||
resolveDirectStatusReplyForSession: (...args: unknown[]) =>
|
||||
runtimeModuleMocks.resolveDirectStatusReplyForSession(...args),
|
||||
}));
|
||||
|
||||
function createInteraction(params?: {
|
||||
channelType?: ChannelType;
|
||||
channelId?: string;
|
||||
@@ -270,13 +251,16 @@ async function expectPairCommandReply(params: {
|
||||
cfg: OpenClawConfig;
|
||||
commandName: string;
|
||||
interaction: MockCommandInteraction;
|
||||
expectedRegisteredName?: string;
|
||||
}) {
|
||||
const command = await createPluginCommand({
|
||||
cfg: params.cfg,
|
||||
name: params.commandName,
|
||||
});
|
||||
const dispatchSpy = runtimeModuleMocks.dispatchReplyWithDispatcher;
|
||||
|
||||
const executeSpy = runtimeModuleMocks.executePluginCommand.mockResolvedValue({
|
||||
text: "paired:now",
|
||||
});
|
||||
await (command as { run: (interaction: unknown) => Promise<void> }).run(
|
||||
Object.assign(params.interaction, {
|
||||
options: {
|
||||
@@ -288,6 +272,12 @@ async function expectPairCommandReply(params: {
|
||||
);
|
||||
|
||||
expect(dispatchSpy).not.toHaveBeenCalled();
|
||||
expect(executeSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
command: expect.objectContaining({ name: params.expectedRegisteredName ?? "pair" }),
|
||||
args: "now",
|
||||
}),
|
||||
);
|
||||
expect(params.interaction.followUp).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ content: "paired:now" }),
|
||||
);
|
||||
@@ -338,21 +328,28 @@ describe("Discord native plugin command dispatch", () => {
|
||||
await import("./native-command.js"));
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
afterAll(() => {
|
||||
clearPluginCommands();
|
||||
setActivePluginRegistry(createTestRegistry());
|
||||
discordNativeCommandTesting.setMatchPluginCommand(matchPluginCommand);
|
||||
discordNativeCommandTesting.setExecutePluginCommand(executePluginCommand);
|
||||
discordNativeCommandTesting.setDispatchReplyWithDispatcher(dispatchReplyWithDispatcher);
|
||||
discordNativeCommandTesting.setResolveDirectStatusReplyForSession(
|
||||
resolveDirectStatusReplyForSession,
|
||||
);
|
||||
discordNativeCommandTesting.setResolveDiscordNativeInteractionRouteState(
|
||||
resolveDiscordNativeInteractionRouteState,
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
clearPluginCommands();
|
||||
setActivePluginRegistry(createTestRegistry());
|
||||
const actualPluginRuntime = await vi.importActual<
|
||||
typeof import("openclaw/plugin-sdk/plugin-runtime")
|
||||
>("openclaw/plugin-sdk/plugin-runtime");
|
||||
runtimeModuleMocks.matchPluginCommand.mockReset();
|
||||
runtimeModuleMocks.matchPluginCommand.mockImplementation(
|
||||
actualPluginRuntime.matchPluginCommand,
|
||||
);
|
||||
runtimeModuleMocks.matchPluginCommand.mockImplementation(matchPluginCommand);
|
||||
runtimeModuleMocks.executePluginCommand.mockReset();
|
||||
runtimeModuleMocks.executePluginCommand.mockImplementation(
|
||||
actualPluginRuntime.executePluginCommand,
|
||||
);
|
||||
runtimeModuleMocks.executePluginCommand.mockImplementation(executePluginCommand);
|
||||
runtimeModuleMocks.dispatchReplyWithDispatcher.mockReset();
|
||||
runtimeModuleMocks.dispatchReplyWithDispatcher.mockResolvedValue({
|
||||
counts: {
|
||||
@@ -372,7 +369,10 @@ describe("Discord native plugin command dispatch", () => {
|
||||
runtimeModuleMocks.executePluginCommand as typeof import("openclaw/plugin-sdk/plugin-runtime").executePluginCommand,
|
||||
);
|
||||
discordNativeCommandTesting.setDispatchReplyWithDispatcher(
|
||||
runtimeModuleMocks.dispatchReplyWithDispatcher as typeof import("openclaw/plugin-sdk/reply-runtime").dispatchReplyWithDispatcher,
|
||||
runtimeModuleMocks.dispatchReplyWithDispatcher as typeof dispatchReplyWithDispatcher,
|
||||
);
|
||||
discordNativeCommandTesting.setResolveDirectStatusReplyForSession(
|
||||
runtimeModuleMocks.resolveDirectStatusReplyForSession as typeof resolveDirectStatusReplyForSession,
|
||||
);
|
||||
discordNativeCommandTesting.setResolveDiscordNativeInteractionRouteState(async (params) =>
|
||||
createUnboundRouteState({
|
||||
@@ -436,7 +436,6 @@ describe("Discord native plugin command dispatch", () => {
|
||||
description: "Pair",
|
||||
acceptsArgs: true,
|
||||
};
|
||||
const command = await createNativeCommand(cfg, commandSpec);
|
||||
const interaction = createInteraction({
|
||||
channelType: ChannelType.GuildText,
|
||||
channelId: "234567890123456789",
|
||||
@@ -455,6 +454,7 @@ describe("Discord native plugin command dispatch", () => {
|
||||
handler: async ({ args }) => ({ text: `open:${args ?? ""}` }),
|
||||
}),
|
||||
).toEqual({ ok: true });
|
||||
const command = await createNativeCommand(cfg, commandSpec);
|
||||
|
||||
const executeSpy = runtimeModuleMocks.executePluginCommand;
|
||||
const dispatchSpy = runtimeModuleMocks.dispatchReplyWithDispatcher.mockResolvedValue(
|
||||
|
||||
@@ -94,6 +94,7 @@ const DISCORD_COMMAND_DESCRIPTION_MAX = 100;
|
||||
let matchPluginCommandImpl = pluginRuntime.matchPluginCommand;
|
||||
let executePluginCommandImpl = pluginRuntime.executePluginCommand;
|
||||
let dispatchReplyWithDispatcherImpl = dispatchReplyWithDispatcher;
|
||||
let resolveDirectStatusReplyForSessionImpl = resolveDirectStatusReplyForSession;
|
||||
let resolveDiscordNativeInteractionRouteStateImpl = resolveDiscordNativeInteractionRouteState;
|
||||
|
||||
export const __testing = {
|
||||
@@ -118,6 +119,13 @@ export const __testing = {
|
||||
dispatchReplyWithDispatcherImpl = next;
|
||||
return previous;
|
||||
},
|
||||
setResolveDirectStatusReplyForSession(
|
||||
next: typeof resolveDirectStatusReplyForSession,
|
||||
): typeof resolveDirectStatusReplyForSession {
|
||||
const previous = resolveDirectStatusReplyForSessionImpl;
|
||||
resolveDirectStatusReplyForSessionImpl = next;
|
||||
return previous;
|
||||
},
|
||||
setResolveDiscordNativeInteractionRouteState(
|
||||
next: typeof resolveDiscordNativeInteractionRouteState,
|
||||
): typeof resolveDiscordNativeInteractionRouteState {
|
||||
@@ -621,6 +629,19 @@ async function safeDiscordInteractionCall<T>(
|
||||
}
|
||||
}
|
||||
|
||||
function createNativeCommandDefinition(command: NativeCommandSpec): ChatCommandDefinition {
|
||||
return {
|
||||
key: command.name,
|
||||
nativeName: command.name,
|
||||
description: command.description,
|
||||
textAliases: [],
|
||||
acceptsArgs: command.acceptsArgs,
|
||||
args: command.args,
|
||||
argsParsing: "none",
|
||||
scope: "native",
|
||||
};
|
||||
}
|
||||
|
||||
export function createDiscordNativeCommand(params: {
|
||||
command: NativeCommandSpec;
|
||||
cfg: ReturnType<typeof loadConfig>;
|
||||
@@ -639,18 +660,13 @@ export function createDiscordNativeCommand(params: {
|
||||
ephemeralDefault,
|
||||
threadBindings,
|
||||
} = params;
|
||||
const fallbackCommandDefinition = createNativeCommandDefinition(command);
|
||||
const commandDefinition =
|
||||
findCommandByNativeName(command.name, "discord") ??
|
||||
({
|
||||
key: command.name,
|
||||
nativeName: command.name,
|
||||
description: command.description,
|
||||
textAliases: [],
|
||||
acceptsArgs: command.acceptsArgs,
|
||||
args: command.args,
|
||||
argsParsing: "none",
|
||||
scope: "native",
|
||||
} satisfies ChatCommandDefinition);
|
||||
matchPluginCommandImpl(`/${command.name}`) !== null
|
||||
? fallbackCommandDefinition
|
||||
: (findCommandByNativeName(command.name, "discord", {
|
||||
includeBundledChannelFallback: false,
|
||||
}) ?? fallbackCommandDefinition);
|
||||
const argDefinitions = commandDefinition.args ?? command.args;
|
||||
const commandOptions = buildDiscordCommandOptions({
|
||||
command: commandDefinition,
|
||||
@@ -1130,7 +1146,7 @@ async function dispatchDiscordCommandInteraction(params: {
|
||||
});
|
||||
const mediaLocalRoots = getAgentScopedMediaLocalRoots(cfg, effectiveRoute.agentId);
|
||||
if (!suppressReplies && commandName === "status") {
|
||||
const statusReply = await resolveDirectStatusReplyForSession({
|
||||
const statusReply = await resolveDirectStatusReplyForSessionImpl({
|
||||
cfg,
|
||||
sessionKey: commandTargetSessionKey?.trim() || sessionKey,
|
||||
channel: "discord",
|
||||
|
||||
@@ -172,6 +172,24 @@ describe("commands registry", () => {
|
||||
expect(native.find((spec) => spec.name === "agentstatus")).toBeTruthy();
|
||||
expect(findCommandByNativeName("agentstatus", "slack")?.key).toBe("status");
|
||||
expect(findCommandByNativeName("status", "slack")).toBeUndefined();
|
||||
expect(
|
||||
findCommandByNativeName("agentstatus", "slack", {
|
||||
includeBundledChannelFallback: false,
|
||||
})?.key,
|
||||
).toBe("status");
|
||||
expect(
|
||||
findCommandByNativeName("status", "slack", {
|
||||
includeBundledChannelFallback: false,
|
||||
}),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("can resolve default native command names without loading bundled channel fallbacks", () => {
|
||||
expect(
|
||||
findCommandByNativeName("status", "discord", {
|
||||
includeBundledChannelFallback: false,
|
||||
})?.key,
|
||||
).toBe("status");
|
||||
});
|
||||
|
||||
it("keeps discord native command specs within slash-command limits", () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js";
|
||||
import { resolveConfiguredModelRef } from "../agents/model-selection.js";
|
||||
import type { SkillCommandSpec } from "../agents/skills.js";
|
||||
import { getChannelPlugin } from "../channels/plugins/index.js";
|
||||
import { getChannelPlugin, getLoadedChannelPlugin } from "../channels/plugins/index.js";
|
||||
import type { OpenClawConfig } from "../config/types.js";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
@@ -55,15 +55,27 @@ export type {
|
||||
ShouldHandleTextCommandsParams,
|
||||
} from "./commands-registry.types.js";
|
||||
|
||||
function resolveNativeName(command: ChatCommandDefinition, provider?: string): string | undefined {
|
||||
type NativeCommandProviderLookupOptions = {
|
||||
includeBundledChannelFallback?: boolean;
|
||||
};
|
||||
|
||||
function resolveNativeName(
|
||||
command: ChatCommandDefinition,
|
||||
provider?: string,
|
||||
options?: NativeCommandProviderLookupOptions,
|
||||
): string | undefined {
|
||||
if (!command.nativeName) {
|
||||
return undefined;
|
||||
}
|
||||
if (!provider) {
|
||||
return command.nativeName;
|
||||
}
|
||||
const channelPlugin =
|
||||
options?.includeBundledChannelFallback === false
|
||||
? getLoadedChannelPlugin(provider)
|
||||
: getChannelPlugin(provider);
|
||||
return (
|
||||
getChannelPlugin(provider)?.commands?.resolveNativeCommandName?.({
|
||||
channelPlugin?.commands?.resolveNativeCommandName?.({
|
||||
commandKey: command.key,
|
||||
defaultName: command.nativeName,
|
||||
}) ?? command.nativeName
|
||||
@@ -108,6 +120,7 @@ export function listNativeCommandSpecsForConfig(
|
||||
export function findCommandByNativeName(
|
||||
name: string,
|
||||
provider?: string,
|
||||
options?: NativeCommandProviderLookupOptions,
|
||||
): ChatCommandDefinition | undefined {
|
||||
const normalized = normalizeOptionalLowercaseString(name);
|
||||
if (!normalized) {
|
||||
@@ -116,7 +129,8 @@ export function findCommandByNativeName(
|
||||
return getChatCommands().find(
|
||||
(command) =>
|
||||
command.scope !== "text" &&
|
||||
normalizeOptionalLowercaseString(resolveNativeName(command, provider)) === normalized,
|
||||
normalizeOptionalLowercaseString(resolveNativeName(command, provider, options)) ===
|
||||
normalized,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user