test: stabilize discord monitor ci isolation

This commit is contained in:
Peter Steinberger
2026-03-27 00:37:57 +00:00
parent 89e6b91b89
commit 7de494fcec
7 changed files with 66 additions and 39 deletions

View File

@@ -19,19 +19,7 @@ import {
resolvePluginConversationBindingApprovalMock,
upsertPairingRequestMock,
} from "../../../../test/helpers/extensions/discord-component-runtime.js";
import {
clearDiscordComponentEntries,
registerDiscordComponentEntries,
resolveDiscordComponentEntry,
resolveDiscordModalEntry,
} from "../components-registry.js";
import type { DiscordComponentEntry, DiscordModalEntry } from "../components.js";
import * as sendComponents from "../send.components.js";
import {
createDiscordComponentButton,
createDiscordComponentStringSelect,
createDiscordComponentModal,
} from "./agent-components.js";
import { type DiscordComponentEntry, type DiscordModalEntry } from "../components.js";
import type { DiscordChannelConfigResolved } from "./allow-list.js";
import {
resolveDiscordMemberAllowed,
@@ -53,6 +41,22 @@ import {
resolveDiscordReplyDeliveryPlan,
} from "./threading.js";
type CreateDiscordComponentButton =
typeof import("./agent-components.js").createDiscordComponentButton;
type CreateDiscordComponentModal =
typeof import("./agent-components.js").createDiscordComponentModal;
type CreateDiscordComponentStringSelect =
typeof import("./agent-components.js").createDiscordComponentStringSelect;
let createDiscordComponentButton: CreateDiscordComponentButton;
let createDiscordComponentStringSelect: CreateDiscordComponentStringSelect;
let createDiscordComponentModal: CreateDiscordComponentModal;
let clearDiscordComponentEntries: typeof import("../components-registry.js").clearDiscordComponentEntries;
let registerDiscordComponentEntries: typeof import("../components-registry.js").registerDiscordComponentEntries;
let resolveDiscordComponentEntry: typeof import("../components-registry.js").resolveDiscordComponentEntry;
let resolveDiscordModalEntry: typeof import("../components-registry.js").resolveDiscordModalEntry;
let sendComponents: typeof import("../send.components.js");
const enqueueSystemEventMock = vi.hoisted(() => vi.fn());
const dispatchReplyMock = vi.hoisted(() => vi.fn());
const readSessionUpdatedAtMock = vi.hoisted(() => vi.fn());
@@ -136,9 +140,9 @@ describe("discord component interactions", () => {
};
};
const createComponentContext = (
overrides?: Partial<Parameters<typeof createDiscordComponentButton>[0]>,
) =>
type ComponentContext = Parameters<CreateDiscordComponentButton>[0];
const createComponentContext = (overrides?: Partial<ComponentContext>) =>
({
cfg: createCfg(),
accountId: "default",
@@ -147,7 +151,7 @@ describe("discord component interactions", () => {
discordConfig: createDiscordConfig(),
token: "token",
...overrides,
}) as Parameters<typeof createDiscordComponentButton>[0];
}) as ComponentContext;
const createComponentButtonInteraction = (overrides: Partial<ButtonInteraction> = {}) => {
const reply = vi.fn().mockResolvedValue(undefined);
@@ -307,7 +311,20 @@ describe("discord component interactions", () => {
expect(dispatchReplyMock).not.toHaveBeenCalled();
}
beforeEach(() => {
beforeEach(async () => {
vi.resetModules();
({
createDiscordComponentButton,
createDiscordComponentStringSelect,
createDiscordComponentModal,
} = await import("./agent-components.js"));
({
clearDiscordComponentEntries,
registerDiscordComponentEntries,
resolveDiscordComponentEntry,
resolveDiscordModalEntry,
} = await import("../components-registry.js"));
sendComponents = await import("../send.components.js");
editDiscordComponentMessageMock = vi
.spyOn(sendComponents, "editDiscordComponentMessage")
.mockResolvedValue({

View File

@@ -5,7 +5,7 @@ import * as dispatcherModule from "../../../../src/auto-reply/reply/provider-dis
import type { OpenClawConfig } from "../../../../src/config/config.js";
import type { DiscordAccountConfig } from "../../../../src/config/types.discord.js";
import * as pluginCommandsModule from "../../../../src/plugins/commands.js";
import { createDiscordNativeCommand } from "./native-command.js";
import { __testing as nativeCommandTesting, createDiscordNativeCommand } from "./native-command.js";
import {
createMockCommandInteraction,
type MockCommandInteraction,
@@ -68,13 +68,17 @@ function createCommand(cfg: OpenClawConfig, discordConfig?: DiscordAccountConfig
}
function createDispatchSpy() {
return vi.spyOn(dispatcherModule, "dispatchReplyWithDispatcher").mockResolvedValue({
const dispatchSpy = vi.spyOn(dispatcherModule, "dispatchReplyWithDispatcher").mockResolvedValue({
counts: {
final: 1,
block: 0,
tool: 0,
},
} as never);
nativeCommandTesting.setDispatchReplyWithDispatcher(
dispatcherModule.dispatchReplyWithDispatcher as typeof import("openclaw/plugin-sdk/reply-runtime").dispatchReplyWithDispatcher,
);
return dispatchSpy;
}
async function runGuildSlashCommand(params?: {
@@ -110,6 +114,9 @@ function expectUnauthorizedReply(interaction: MockCommandInteraction) {
describe("Discord native slash commands with commands.allowFrom", () => {
beforeEach(() => {
vi.restoreAllMocks();
nativeCommandTesting.setDispatchReplyWithDispatcher(
dispatcherModule.dispatchReplyWithDispatcher as typeof import("openclaw/plugin-sdk/reply-runtime").dispatchReplyWithDispatcher,
);
});
it("authorizes guild slash commands when commands.allowFrom.discord matches the sender", async () => {

View File

@@ -10,7 +10,6 @@ import {
import type { OpenClawConfig, loadConfig } from "../../../../src/config/config.js";
import { clearSessionStoreCacheForTest } from "../../../../src/config/sessions/store.js";
import { createConfiguredBindingConversationRuntimeModuleMock } from "../../../../test/helpers/extensions/configured-binding-runtime.js";
import { resolveDiscordNativeChoiceContext } from "./native-command-ui.js";
import { createNoopThreadBindingManager } from "./thread-bindings.js";
const ensureConfiguredBindingRouteReadyMock = vi.hoisted(() =>
@@ -52,9 +51,12 @@ const STORE_PATH = path.join(
`openclaw-discord-think-autocomplete-${process.pid}.json`,
);
const SESSION_KEY = "agent:main:main";
let resolveDiscordNativeChoiceContext: typeof import("./native-command-ui.js").resolveDiscordNativeChoiceContext;
describe("discord native /think autocomplete", () => {
beforeEach(() => {
beforeEach(async () => {
vi.resetModules();
({ resolveDiscordNativeChoiceContext } = await import("./native-command-ui.js"));
clearSessionStoreCacheForTest();
ensureConfiguredBindingRouteReadyMock.mockReset();
ensureConfiguredBindingRouteReadyMock.mockResolvedValue({ ok: true });

View File

@@ -1,5 +1,4 @@
import { describe, expect, it, vi } from "vitest";
import { resolveDiscordRestFetch } from "./rest-fetch.js";
import { beforeEach, describe, expect, it, vi } from "vitest";
const { undiciFetchMock, proxyAgentSpy } = vi.hoisted(() => ({
undiciFetchMock: vi.fn(),
@@ -23,7 +22,14 @@ vi.mock("undici", () => {
};
});
let resolveDiscordRestFetch: typeof import("./rest-fetch.js").resolveDiscordRestFetch;
describe("resolveDiscordRestFetch", () => {
beforeEach(async () => {
vi.resetModules();
({ resolveDiscordRestFetch } = await import("./rest-fetch.js"));
});
it("uses undici proxy fetch when a proxy URL is configured", async () => {
const runtime = {
log: vi.fn(),

View File

@@ -1,7 +1,6 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../../../src/config/config.js";
import type { RuntimeEnv } from "../../../../src/runtime.js";
import { deliverDiscordReply } from "./reply-delivery.js";
import {
__testing as threadBindingTesting,
createThreadBindingManager,
@@ -59,6 +58,8 @@ vi.mock("openclaw/plugin-sdk/infra-runtime", async (importOriginal) => {
};
});
let deliverDiscordReply: typeof import("./reply-delivery.js").deliverDiscordReply;
describe("deliverDiscordReply", () => {
const runtime = {} as RuntimeEnv;
const cfg = {
@@ -111,7 +112,9 @@ describe("deliverDiscordReply", () => {
return threadBindings;
};
beforeEach(() => {
beforeEach(async () => {
vi.resetModules();
({ deliverDiscordReply } = await import("./reply-delivery.js"));
sendMessageDiscordMock.mockClear().mockResolvedValue({
messageId: "msg-1",
channelId: "channel-1",

View File

@@ -30,11 +30,9 @@ const MATCHED_KEY = `agent:main:discord:channel:${THREAD_ID}`;
const UNMATCHED_KEY = `agent:main:discord:channel:${OTHER_ID}`;
describe("closeDiscordThreadSessions", () => {
beforeAll(async () => {
beforeEach(async () => {
vi.resetModules();
({ closeDiscordThreadSessions } = await import("./thread-session-close.js"));
});
beforeEach(() => {
hoisted.updateSessionStore.mockClear();
hoisted.resolveStorePath.mockClear();
hoisted.resolveStorePath.mockReturnValue("/tmp/openclaw-sessions.json");

View File

@@ -1,5 +1,5 @@
import { ChannelType } from "@buape/carbon";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../../../src/config/config.js";
type MaybeCreateDiscordAutoThreadFn = typeof import("./threading.js").maybeCreateDiscordAutoThread;
@@ -46,7 +46,8 @@ async function flushAsyncWork() {
await Promise.resolve();
}
beforeAll(async () => {
beforeEach(async () => {
vi.resetModules();
postMock.mockReset();
getMock.mockReset();
patchMock.mockReset();
@@ -54,13 +55,6 @@ beforeAll(async () => {
({ maybeCreateDiscordAutoThread } = await import("./threading.js"));
});
beforeEach(() => {
postMock.mockReset();
getMock.mockReset();
patchMock.mockReset();
generateThreadTitleMock.mockReset();
});
describe("maybeCreateDiscordAutoThread", () => {
it("skips auto-thread if channelType is GuildForum", async () => {
const result = await maybeCreateDiscordAutoThread(