mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 18:10:45 +00:00
refactor: trim feishu helper exports
This commit is contained in:
@@ -64,7 +64,7 @@ export function formatFeishuApiError(
|
||||
});
|
||||
}
|
||||
|
||||
export function formatFeishuApiFailure(
|
||||
function formatFeishuApiFailure(
|
||||
error: unknown,
|
||||
errorPrefix: string,
|
||||
options: {
|
||||
|
||||
@@ -118,7 +118,7 @@ export async function tryRecordMessagePersistent(
|
||||
});
|
||||
}
|
||||
|
||||
export async function hasRecordedMessagePersistent(
|
||||
async function hasRecordedMessagePersistent(
|
||||
messageId: string,
|
||||
namespace = "global",
|
||||
log?: (...args: unknown[]) => void,
|
||||
|
||||
@@ -8,15 +8,12 @@ vi.mock("./client.js", () => ({
|
||||
createFeishuClient: createFeishuClientMock,
|
||||
}));
|
||||
|
||||
const {
|
||||
listFeishuDirectoryGroups,
|
||||
listFeishuDirectoryGroupsLive,
|
||||
listFeishuDirectoryPeers,
|
||||
listFeishuDirectoryPeersLive,
|
||||
} = await importFreshModule<typeof import("./directory.js")>(
|
||||
import.meta.url,
|
||||
"./directory.js?directory-test",
|
||||
);
|
||||
const { listFeishuDirectoryGroupsLive, listFeishuDirectoryPeersLive } = await importFreshModule<
|
||||
typeof import("./directory.js")
|
||||
>(import.meta.url, "./directory.js?directory-test");
|
||||
const { listFeishuDirectoryGroups, listFeishuDirectoryPeers } = await importFreshModule<
|
||||
typeof import("./directory.static.js")
|
||||
>(import.meta.url, "./directory.static.js?directory-test");
|
||||
|
||||
function makeStaticCfg(): ClawdbotConfig {
|
||||
return {
|
||||
|
||||
@@ -9,8 +9,6 @@ import {
|
||||
type FeishuDirectoryPeer,
|
||||
} from "./directory.static.js";
|
||||
|
||||
export { listFeishuDirectoryGroups, listFeishuDirectoryPeers } from "./directory.static.js";
|
||||
|
||||
export async function listFeishuDirectoryPeersLive(params: {
|
||||
cfg: ClawdbotConfig;
|
||||
query?: string;
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
import { createRuntimeEnv } from "openclaw/plugin-sdk/plugin-test-runtime";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import "./lifecycle.test-support.js";
|
||||
import {
|
||||
getFeishuLifecycleTestMocks,
|
||||
resetFeishuLifecycleTestMocks,
|
||||
} from "./lifecycle.test-support.js";
|
||||
import {
|
||||
createFeishuLifecycleConfig,
|
||||
createFeishuLifecycleReplyDispatcher,
|
||||
createFeishuTextMessageEvent,
|
||||
expectFeishuReplyDispatcherSentFinalReplyOnce,
|
||||
expectFeishuReplyPipelineDedupedAcrossReplay,
|
||||
expectFeishuReplyPipelineDedupedAfterPostSendFailure,
|
||||
installFeishuLifecycleReplyRuntime,
|
||||
mockFeishuReplyOnceDispatch,
|
||||
restoreFeishuLifecycleStateDir,
|
||||
setFeishuLifecycleStateDir,
|
||||
setupFeishuMessageReceiveLifecycleHandler,
|
||||
} from "./test-support/lifecycle-test-support.js";
|
||||
|
||||
const {
|
||||
createFeishuReplyDispatcherMock,
|
||||
dispatchReplyFromConfigMock,
|
||||
finalizeInboundContextMock,
|
||||
resolveAgentRouteMock,
|
||||
withReplyDispatcherMock,
|
||||
} = getFeishuLifecycleTestMocks();
|
||||
|
||||
let lastRuntime = createRuntimeEnv();
|
||||
let lifecycleCore: ReturnType<typeof installFeishuLifecycleReplyRuntime>;
|
||||
const handleMessageMock = vi.fn();
|
||||
const originalStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
const lifecycleConfig = createFeishuLifecycleConfig({
|
||||
accountId: "acct-lifecycle",
|
||||
appId: "cli_test",
|
||||
appSecret: "secret_test",
|
||||
accountConfig: {
|
||||
groupPolicy: "open",
|
||||
groups: {
|
||||
oc_group_1: {
|
||||
requireMention: false,
|
||||
groupSessionScope: "group_topic_sender",
|
||||
replyInThread: "enabled",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
async function setupLifecycleMonitor() {
|
||||
lastRuntime = createRuntimeEnv();
|
||||
return setupFeishuMessageReceiveLifecycleHandler({
|
||||
runtime: lastRuntime,
|
||||
core: lifecycleCore,
|
||||
cfg: lifecycleConfig,
|
||||
accountId: "acct-lifecycle",
|
||||
handleMessage: handleMessageMock,
|
||||
resolveDebounceText: ({ event }) => {
|
||||
const parsed = JSON.parse(event.message.content) as { text?: string };
|
||||
return parsed.text ?? "";
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
describe("Feishu reply-once lifecycle", () => {
|
||||
beforeEach(() => {
|
||||
vi.useRealTimers();
|
||||
resetFeishuLifecycleTestMocks();
|
||||
handleMessageMock.mockReset();
|
||||
lastRuntime = createRuntimeEnv();
|
||||
setFeishuLifecycleStateDir("openclaw-feishu-lifecycle");
|
||||
|
||||
createFeishuReplyDispatcherMock.mockReturnValue(createFeishuLifecycleReplyDispatcher());
|
||||
|
||||
resolveAgentRouteMock.mockReturnValue({
|
||||
agentId: "main",
|
||||
channel: "feishu",
|
||||
accountId: "acct-lifecycle",
|
||||
sessionKey: "agent:main:feishu:group:oc_group_1",
|
||||
mainSessionKey: "agent:main:main",
|
||||
matchedBy: "default",
|
||||
});
|
||||
|
||||
mockFeishuReplyOnceDispatch({
|
||||
dispatchReplyFromConfigMock,
|
||||
replyText: "reply once",
|
||||
});
|
||||
|
||||
withReplyDispatcherMock.mockImplementation(async ({ run }) => await run());
|
||||
handleMessageMock.mockImplementation(async ({ event }) => {
|
||||
const reply = createFeishuReplyDispatcherMock({
|
||||
accountId: "acct-lifecycle",
|
||||
chatId: event.message.chat_id,
|
||||
replyToMessageId: event.message.root_id ?? event.message.message_id,
|
||||
replyInThread: true,
|
||||
rootId: event.message.root_id,
|
||||
});
|
||||
try {
|
||||
await withReplyDispatcherMock({
|
||||
dispatcher: reply.dispatcher,
|
||||
onSettled: () => reply.markDispatchIdle(),
|
||||
run: () =>
|
||||
dispatchReplyFromConfigMock({
|
||||
ctx: {
|
||||
AccountId: "acct-lifecycle",
|
||||
MessageSid: event.message.message_id,
|
||||
},
|
||||
dispatcher: reply.dispatcher,
|
||||
}),
|
||||
});
|
||||
} catch (err) {
|
||||
lastRuntime?.error(`feishu[acct-lifecycle]: failed to dispatch message: ${String(err)}`);
|
||||
}
|
||||
});
|
||||
|
||||
lifecycleCore = installFeishuLifecycleReplyRuntime({
|
||||
resolveAgentRouteMock,
|
||||
finalizeInboundContextMock,
|
||||
dispatchReplyFromConfigMock,
|
||||
withReplyDispatcherMock,
|
||||
storePath: "/tmp/feishu-lifecycle-sessions.json",
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
restoreFeishuLifecycleStateDir(originalStateDir);
|
||||
});
|
||||
|
||||
it("routes a topic-bound inbound event and emits one reply across duplicate replay", async () => {
|
||||
const onMessage = await setupLifecycleMonitor();
|
||||
const event = createFeishuTextMessageEvent({
|
||||
messageId: "om_lifecycle_once",
|
||||
chatId: "oc_group_1",
|
||||
rootId: "om_root_topic_1",
|
||||
threadId: "omt_topic_1",
|
||||
text: "hello from topic",
|
||||
});
|
||||
|
||||
await expectFeishuReplyPipelineDedupedAcrossReplay({
|
||||
handler: onMessage,
|
||||
event,
|
||||
dispatchReplyFromConfigMock,
|
||||
createFeishuReplyDispatcherMock,
|
||||
});
|
||||
|
||||
expect(lastRuntime?.error).not.toHaveBeenCalled();
|
||||
expect(handleMessageMock).toHaveBeenCalledTimes(1);
|
||||
expect(dispatchReplyFromConfigMock).toHaveBeenCalledTimes(1);
|
||||
expect(createFeishuReplyDispatcherMock).toHaveBeenCalledTimes(1);
|
||||
expect(createFeishuReplyDispatcherMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
accountId: "acct-lifecycle",
|
||||
chatId: "oc_group_1",
|
||||
replyToMessageId: "om_root_topic_1",
|
||||
replyInThread: true,
|
||||
rootId: "om_root_topic_1",
|
||||
}),
|
||||
);
|
||||
expectFeishuReplyDispatcherSentFinalReplyOnce({ createFeishuReplyDispatcherMock });
|
||||
});
|
||||
|
||||
it("does not duplicate delivery when the first attempt fails after sending the reply", async () => {
|
||||
const onMessage = await setupLifecycleMonitor();
|
||||
const event = createFeishuTextMessageEvent({
|
||||
messageId: "om_lifecycle_retry",
|
||||
chatId: "oc_group_1",
|
||||
rootId: "om_root_topic_1",
|
||||
threadId: "omt_topic_1",
|
||||
text: "hello from topic",
|
||||
});
|
||||
|
||||
dispatchReplyFromConfigMock.mockImplementationOnce(async ({ dispatcher }) => {
|
||||
await dispatcher.sendFinalReply({ text: "reply once" });
|
||||
throw new Error("post-send failure");
|
||||
});
|
||||
|
||||
await expectFeishuReplyPipelineDedupedAfterPostSendFailure({
|
||||
handler: onMessage,
|
||||
event,
|
||||
dispatchReplyFromConfigMock,
|
||||
runtimeErrorMock: lastRuntime?.error as ReturnType<typeof vi.fn>,
|
||||
});
|
||||
|
||||
expect(lastRuntime?.error).toHaveBeenCalledTimes(1);
|
||||
expect(handleMessageMock).toHaveBeenCalledTimes(1);
|
||||
expect(dispatchReplyFromConfigMock).toHaveBeenCalledTimes(1);
|
||||
expectFeishuReplyDispatcherSentFinalReplyOnce({ createFeishuReplyDispatcherMock });
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,6 @@ import { randomUUID } from "node:crypto";
|
||||
import { createPluginRuntimeMock } from "openclaw/plugin-sdk/channel-test-helpers";
|
||||
import { expect, vi, type Mock } from "vitest";
|
||||
import type { ClawdbotConfig, PluginRuntime, RuntimeEnv } from "../../runtime-api.js";
|
||||
import { createFeishuMessageReceiveHandler } from "../monitor.message-handler.js";
|
||||
import { setFeishuRuntime } from "../runtime.js";
|
||||
import type { ResolvedFeishuAccount } from "../types.js";
|
||||
|
||||
@@ -411,31 +410,6 @@ async function loadMonitorSingleAccount() {
|
||||
return module.monitorSingleAccount;
|
||||
}
|
||||
|
||||
export async function setupFeishuMessageReceiveLifecycleHandler(params: {
|
||||
runtime: RuntimeEnv;
|
||||
core: PluginRuntime;
|
||||
cfg: ClawdbotConfig;
|
||||
accountId: string;
|
||||
fireAndForget?: boolean;
|
||||
handleMessage: Parameters<typeof createFeishuMessageReceiveHandler>[0]["handleMessage"];
|
||||
resolveDebounceText: Parameters<
|
||||
typeof createFeishuMessageReceiveHandler
|
||||
>[0]["resolveDebounceText"];
|
||||
}): Promise<(data: unknown) => Promise<void>> {
|
||||
return createFeishuMessageReceiveHandler({
|
||||
cfg: params.cfg,
|
||||
core: params.core,
|
||||
accountId: params.accountId,
|
||||
runtime: params.runtime,
|
||||
chatHistories: new Map(),
|
||||
fireAndForget: params.fireAndForget,
|
||||
handleMessage: params.handleMessage,
|
||||
resolveDebounceText: params.resolveDebounceText,
|
||||
hasProcessedMessage: vi.fn(async () => false),
|
||||
recordProcessedMessage: vi.fn(async () => true),
|
||||
});
|
||||
}
|
||||
|
||||
export async function setupFeishuLifecycleHandler(params: {
|
||||
createEventDispatcherMock: {
|
||||
mockReturnValue: (value: unknown) => unknown;
|
||||
|
||||
Reference in New Issue
Block a user