test(telegram): rewire bot harnesses to runtime seams
This commit is contained in:
parent
edcf3e9d32
commit
23f618d62d
@ -7,6 +7,21 @@ import { beforeEach, vi } from "vitest";
|
||||
|
||||
type AnyMock = MockFn<(...args: unknown[]) => unknown>;
|
||||
type AnyAsyncMock = MockFn<(...args: unknown[]) => Promise<unknown>>;
|
||||
type DispatchReplyWithBufferedBlockDispatcherFn =
|
||||
typeof import("openclaw/plugin-sdk/reply-runtime").dispatchReplyWithBufferedBlockDispatcher;
|
||||
type DispatchReplyWithBufferedBlockDispatcherResult = Awaited<
|
||||
ReturnType<DispatchReplyWithBufferedBlockDispatcherFn>
|
||||
>;
|
||||
type DispatchReplyHarnessParams = {
|
||||
ctx: MsgContext;
|
||||
replyOptions?: GetReplyOptions;
|
||||
dispatcherOptions?: {
|
||||
typingCallbacks?: {
|
||||
start?: () => void | Promise<void>;
|
||||
};
|
||||
deliver?: (payload: ReplyPayload, info: { kind: "final" }) => void | Promise<void>;
|
||||
};
|
||||
};
|
||||
|
||||
const { sessionStorePath } = vi.hoisted(() => ({
|
||||
sessionStorePath: `/tmp/openclaw-telegram-${process.pid}-${process.env.VITEST_POOL_ID ?? "0"}.json`,
|
||||
@ -20,18 +35,21 @@ export function getLoadWebMediaMock(): AnyMock {
|
||||
return loadWebMedia;
|
||||
}
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/web-media", () => ({
|
||||
vi.doMock("openclaw/plugin-sdk/web-media", () => ({
|
||||
loadWebMedia,
|
||||
}));
|
||||
|
||||
const { loadConfig } = vi.hoisted((): { loadConfig: AnyMock } => ({
|
||||
loadConfig: vi.fn(() => ({})),
|
||||
}));
|
||||
const { resolveStorePathMock } = vi.hoisted((): { resolveStorePathMock: AnyMock } => ({
|
||||
resolveStorePathMock: vi.fn((storePath?: string) => storePath ?? sessionStorePath),
|
||||
}));
|
||||
|
||||
export function getLoadConfigMock(): AnyMock {
|
||||
return loadConfig;
|
||||
}
|
||||
vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => {
|
||||
vi.doMock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/config-runtime")>();
|
||||
return {
|
||||
...actual,
|
||||
@ -39,11 +57,11 @@ vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => {
|
||||
vi.doMock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/config-runtime")>();
|
||||
return {
|
||||
...actual,
|
||||
resolveStorePath: vi.fn((storePath) => storePath ?? sessionStorePath),
|
||||
resolveStorePath: resolveStorePathMock,
|
||||
};
|
||||
});
|
||||
|
||||
@ -68,7 +86,7 @@ export function getUpsertChannelPairingRequestMock(): AnyAsyncMock {
|
||||
return upsertChannelPairingRequest;
|
||||
}
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => {
|
||||
vi.doMock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/conversation-runtime")>();
|
||||
return {
|
||||
...actual,
|
||||
@ -89,23 +107,36 @@ const skillCommandsHoisted = vi.hoisted(() => ({
|
||||
configOverride?: OpenClawConfig,
|
||||
) => Promise<ReplyPayload | ReplyPayload[] | undefined>
|
||||
>,
|
||||
dispatchReplyWithBufferedBlockDispatcher: vi.fn<DispatchReplyWithBufferedBlockDispatcherFn>(
|
||||
async (params: DispatchReplyHarnessParams) => {
|
||||
const result: DispatchReplyWithBufferedBlockDispatcherResult = {
|
||||
queuedFinal: false,
|
||||
counts: {} as DispatchReplyWithBufferedBlockDispatcherResult["counts"],
|
||||
};
|
||||
await params.dispatcherOptions?.typingCallbacks?.start?.();
|
||||
const reply = await skillCommandsHoisted.replySpy(params.ctx, params.replyOptions);
|
||||
const payloads = reply === undefined ? [] : Array.isArray(reply) ? reply : [reply];
|
||||
for (const payload of payloads) {
|
||||
await params.dispatcherOptions?.deliver?.(payload, { kind: "final" });
|
||||
}
|
||||
return result;
|
||||
},
|
||||
),
|
||||
}));
|
||||
export const listSkillCommandsForAgents = skillCommandsHoisted.listSkillCommandsForAgents;
|
||||
export const replySpy = skillCommandsHoisted.replySpy;
|
||||
export const dispatchReplyWithBufferedBlockDispatcher =
|
||||
skillCommandsHoisted.dispatchReplyWithBufferedBlockDispatcher;
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/reply-runtime", async (importOriginal) => {
|
||||
vi.doMock("openclaw/plugin-sdk/reply-runtime", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/reply-runtime")>();
|
||||
return {
|
||||
...actual,
|
||||
listSkillCommandsForAgents: skillCommandsHoisted.listSkillCommandsForAgents,
|
||||
getReplyFromConfig: skillCommandsHoisted.replySpy,
|
||||
__replySpy: skillCommandsHoisted.replySpy,
|
||||
dispatchReplyWithBufferedBlockDispatcher: vi.fn(
|
||||
async ({ ctx, replyOptions }: { ctx: MsgContext; replyOptions?: GetReplyOptions }) => {
|
||||
await skillCommandsHoisted.replySpy(ctx, replyOptions);
|
||||
return { queuedFinal: false };
|
||||
},
|
||||
),
|
||||
dispatchReplyWithBufferedBlockDispatcher:
|
||||
skillCommandsHoisted.dispatchReplyWithBufferedBlockDispatcher,
|
||||
};
|
||||
});
|
||||
|
||||
@ -114,7 +145,7 @@ const systemEventsHoisted = vi.hoisted(() => ({
|
||||
}));
|
||||
export const enqueueSystemEventSpy: AnyMock = systemEventsHoisted.enqueueSystemEventSpy;
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/infra-runtime", async (importOriginal) => {
|
||||
vi.doMock("openclaw/plugin-sdk/infra-runtime", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/infra-runtime")>();
|
||||
return {
|
||||
...actual,
|
||||
@ -127,7 +158,7 @@ const sentMessageCacheHoisted = vi.hoisted(() => ({
|
||||
}));
|
||||
export const wasSentByBot = sentMessageCacheHoisted.wasSentByBot;
|
||||
|
||||
vi.mock("./sent-message-cache.js", () => ({
|
||||
vi.doMock("./sent-message-cache.js", () => ({
|
||||
wasSentByBot: sentMessageCacheHoisted.wasSentByBot,
|
||||
recordSentMessage: vi.fn(),
|
||||
clearSentMessageCache: vi.fn(),
|
||||
@ -181,7 +212,19 @@ export const {
|
||||
getFileSpy,
|
||||
} = grammySpies;
|
||||
|
||||
vi.mock("grammy", () => ({
|
||||
const runnerHoisted = vi.hoisted(() => ({
|
||||
sequentializeMiddleware: vi.fn(async (_ctx: unknown, next?: () => Promise<void>) => {
|
||||
if (typeof next === "function") {
|
||||
await next();
|
||||
}
|
||||
}),
|
||||
sequentializeSpy: vi.fn(() => runnerHoisted.sequentializeMiddleware),
|
||||
throttlerSpy: vi.fn(() => "throttler"),
|
||||
}));
|
||||
export const sequentializeSpy: AnyMock = runnerHoisted.sequentializeSpy;
|
||||
export let sequentializeKey: ((ctx: unknown) => string) | undefined;
|
||||
export const throttlerSpy: AnyMock = runnerHoisted.throttlerSpy;
|
||||
export const telegramBotRuntimeForTest = {
|
||||
Bot: class {
|
||||
api = {
|
||||
config: { use: grammySpies.useSpy },
|
||||
@ -210,32 +253,30 @@ vi.mock("grammy", () => ({
|
||||
grammySpies.botCtorSpy(token, options);
|
||||
}
|
||||
},
|
||||
InputFile: class {},
|
||||
}));
|
||||
|
||||
const runnerHoisted = vi.hoisted(() => ({
|
||||
sequentializeMiddleware: vi.fn(async (_ctx: unknown, next?: () => Promise<void>) => {
|
||||
if (typeof next === "function") {
|
||||
await next();
|
||||
}
|
||||
}),
|
||||
sequentializeSpy: vi.fn(() => runnerHoisted.sequentializeMiddleware),
|
||||
throttlerSpy: vi.fn(() => "throttler"),
|
||||
}));
|
||||
export const sequentializeSpy: AnyMock = runnerHoisted.sequentializeSpy;
|
||||
export let sequentializeKey: ((ctx: unknown) => string) | undefined;
|
||||
vi.mock("@grammyjs/runner", () => ({
|
||||
sequentialize: (keyFn: (ctx: unknown) => string) => {
|
||||
sequentializeKey = keyFn;
|
||||
return runnerHoisted.sequentializeSpy();
|
||||
},
|
||||
}));
|
||||
|
||||
export const throttlerSpy: AnyMock = runnerHoisted.throttlerSpy;
|
||||
|
||||
vi.mock("@grammyjs/transformer-throttler", () => ({
|
||||
apiThrottler: () => runnerHoisted.throttlerSpy(),
|
||||
}));
|
||||
loadConfig,
|
||||
};
|
||||
export const telegramBotMessageDispatchRuntimeForTest = {
|
||||
dispatchReplyWithBufferedBlockDispatcher,
|
||||
};
|
||||
export const telegramBotNativeCommandsRuntimeForTest = {
|
||||
dispatchReplyWithBufferedBlockDispatcher,
|
||||
listSkillCommandsForAgents,
|
||||
};
|
||||
export const telegramBotHandlersRuntimeForTest = {
|
||||
loadConfig,
|
||||
resolveStorePath: resolveStorePathMock,
|
||||
readChannelAllowFromStore,
|
||||
enqueueSystemEvent: enqueueSystemEventSpy,
|
||||
listSkillCommandsForAgents,
|
||||
wasSentByBot,
|
||||
};
|
||||
|
||||
vi.doMock("./bot.runtime.js", () => telegramBotRuntimeForTest);
|
||||
|
||||
export const getOnHandler = (event: string) => {
|
||||
const handler = onSpy.mock.calls.find((call) => call[0] === event)?.[1];
|
||||
@ -310,6 +351,8 @@ beforeEach(() => {
|
||||
resetInboundDedupe();
|
||||
loadConfig.mockReset();
|
||||
loadConfig.mockReturnValue(DEFAULT_TELEGRAM_TEST_CONFIG);
|
||||
resolveStorePathMock.mockReset();
|
||||
resolveStorePathMock.mockImplementation((storePath?: string) => storePath ?? sessionStorePath);
|
||||
loadWebMedia.mockReset();
|
||||
readChannelAllowFromStore.mockReset();
|
||||
readChannelAllowFromStore.mockResolvedValue([]);
|
||||
@ -324,6 +367,22 @@ beforeEach(() => {
|
||||
await opts?.onReplyStart?.();
|
||||
return undefined;
|
||||
});
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockReset();
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
|
||||
async (params: DispatchReplyHarnessParams) => {
|
||||
const result: DispatchReplyWithBufferedBlockDispatcherResult = {
|
||||
queuedFinal: false,
|
||||
counts: {} as DispatchReplyWithBufferedBlockDispatcherResult["counts"],
|
||||
};
|
||||
await params.dispatcherOptions?.typingCallbacks?.start?.();
|
||||
const reply = await replySpy(params.ctx, params.replyOptions);
|
||||
const payloads = reply === undefined ? [] : Array.isArray(reply) ? reply : [reply];
|
||||
for (const payload of payloads) {
|
||||
await params.dispatcherOptions?.deliver?.(payload, { kind: "final" });
|
||||
}
|
||||
return result;
|
||||
},
|
||||
);
|
||||
|
||||
sendAnimationSpy.mockReset();
|
||||
sendAnimationSpy.mockResolvedValue({ message_id: 78 });
|
||||
|
||||
@ -5,7 +5,7 @@ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import { escapeRegExp, formatEnvelopeTimestamp } from "../../../test/helpers/envelope-timestamp.js";
|
||||
import { withEnvAsync } from "../../../test/helpers/extensions/env.js";
|
||||
import { useFrozenTime, useRealTime } from "../../../test/helpers/extensions/frozen-time.js";
|
||||
import {
|
||||
const {
|
||||
answerCallbackQuerySpy,
|
||||
botCtorSpy,
|
||||
commandSpy,
|
||||
@ -26,13 +26,26 @@ import {
|
||||
sequentializeSpy,
|
||||
setMessageReactionSpy,
|
||||
setMyCommandsSpy,
|
||||
telegramBotHandlersRuntimeForTest,
|
||||
telegramBotMessageDispatchRuntimeForTest,
|
||||
telegramBotNativeCommandsRuntimeForTest,
|
||||
telegramBotRuntimeForTest,
|
||||
throttlerSpy,
|
||||
useSpy,
|
||||
} from "./bot.create-telegram-bot.test-harness.js";
|
||||
} = await import("./bot.create-telegram-bot.test-harness.js");
|
||||
import { resolveTelegramFetch } from "./fetch.js";
|
||||
|
||||
// Import after the harness registers `vi.mock(...)` for grammY and Telegram internals.
|
||||
const { createTelegramBot, getTelegramSequentialKey } = await import("./bot.js");
|
||||
const { createTelegramBot, getTelegramSequentialKey, setTelegramBotRuntimeForTest } =
|
||||
await import("./bot.js");
|
||||
const { setBotHandlersRuntimeForTest } = await import("./bot-handlers.runtime.js");
|
||||
const { setBotMessageDispatchRuntimeForTest } = await import("./bot-message-dispatch.js");
|
||||
const { setBotNativeCommandsRuntimeForTest } = await import("./bot-native-commands.js");
|
||||
|
||||
setTelegramBotRuntimeForTest(telegramBotRuntimeForTest);
|
||||
setBotHandlersRuntimeForTest(telegramBotHandlersRuntimeForTest);
|
||||
setBotMessageDispatchRuntimeForTest(telegramBotMessageDispatchRuntimeForTest);
|
||||
setBotNativeCommandsRuntimeForTest(telegramBotNativeCommandsRuntimeForTest);
|
||||
|
||||
const loadConfig = getLoadConfigMock();
|
||||
const loadWebMedia = getLoadWebMediaMock();
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
registerPluginInteractiveHandler,
|
||||
} from "../../../src/plugins/interactive.js";
|
||||
import { escapeRegExp, formatEnvelopeTimestamp } from "../../../test/helpers/envelope-timestamp.js";
|
||||
import {
|
||||
const {
|
||||
answerCallbackQuerySpy,
|
||||
commandSpy,
|
||||
editMessageReplyMarkupSpy,
|
||||
@ -22,8 +22,12 @@ import {
|
||||
replySpy,
|
||||
sendMessageSpy,
|
||||
setMyCommandsSpy,
|
||||
telegramBotHandlersRuntimeForTest,
|
||||
telegramBotMessageDispatchRuntimeForTest,
|
||||
telegramBotNativeCommandsRuntimeForTest,
|
||||
telegramBotRuntimeForTest,
|
||||
wasSentByBot,
|
||||
} from "./bot.create-telegram-bot.test-harness.js";
|
||||
} = await import("./bot.create-telegram-bot.test-harness.js");
|
||||
|
||||
// Import after the harness registers `vi.mock(...)` for grammY and Telegram internals.
|
||||
const { listNativeCommandSpecs, listNativeCommandSpecsForConfig } =
|
||||
@ -31,7 +35,15 @@ const { listNativeCommandSpecs, listNativeCommandSpecsForConfig } =
|
||||
const { loadSessionStore } = await import("../../../src/config/sessions.js");
|
||||
const { normalizeTelegramCommandName } =
|
||||
await import("../../../src/config/telegram-custom-commands.js");
|
||||
const { createTelegramBot } = await import("./bot.js");
|
||||
const { createTelegramBot, setTelegramBotRuntimeForTest } = await import("./bot.js");
|
||||
const { setBotHandlersRuntimeForTest } = await import("./bot-handlers.runtime.js");
|
||||
const { setBotMessageDispatchRuntimeForTest } = await import("./bot-message-dispatch.js");
|
||||
const { setBotNativeCommandsRuntimeForTest } = await import("./bot-native-commands.js");
|
||||
|
||||
setTelegramBotRuntimeForTest(telegramBotRuntimeForTest);
|
||||
setBotHandlersRuntimeForTest(telegramBotHandlersRuntimeForTest);
|
||||
setBotMessageDispatchRuntimeForTest(telegramBotMessageDispatchRuntimeForTest);
|
||||
setBotNativeCommandsRuntimeForTest(telegramBotNativeCommandsRuntimeForTest);
|
||||
|
||||
const loadConfig = getLoadConfigMock();
|
||||
const readChannelAllowFromStore = getReadChannelAllowFromStoreMock();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user