mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-04 07:20:23 +00:00
test(auto-reply): isolate dispatch runtime mocks
This commit is contained in:
@@ -1,12 +1,38 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import {
|
||||
import type { ReplyDispatcher } from "./reply/reply-dispatcher.js";
|
||||
import { buildTestCtx } from "./reply/test-ctx.js";
|
||||
|
||||
const hoisted = vi.hoisted(() => ({
|
||||
dispatchReplyFromConfigMock: vi.fn(),
|
||||
finalizeInboundContextMock: vi.fn((ctx: unknown) => ctx),
|
||||
createReplyDispatcherWithTypingMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./reply/dispatch-from-config.js", () => ({
|
||||
dispatchReplyFromConfig: (...args: unknown[]) => hoisted.dispatchReplyFromConfigMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock("./reply/inbound-context.js", () => ({
|
||||
finalizeInboundContext: (...args: unknown[]) => hoisted.finalizeInboundContextMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock("./reply/reply-dispatcher.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("./reply/reply-dispatcher.js")>(
|
||||
"./reply/reply-dispatcher.js",
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
createReplyDispatcherWithTyping: (...args: unknown[]) =>
|
||||
hoisted.createReplyDispatcherWithTypingMock(...args),
|
||||
};
|
||||
});
|
||||
|
||||
const {
|
||||
dispatchInboundMessage,
|
||||
dispatchInboundMessageWithBufferedDispatcher,
|
||||
withReplyDispatcher,
|
||||
} from "./dispatch.js";
|
||||
import type { ReplyDispatcher } from "./reply/reply-dispatcher.js";
|
||||
import { buildTestCtx } from "./reply/test-ctx.js";
|
||||
} = await import("./dispatch.js");
|
||||
|
||||
function createDispatcher(record: string[]): ReplyDispatcher {
|
||||
return {
|
||||
@@ -25,6 +51,39 @@ function createDispatcher(record: string[]): ReplyDispatcher {
|
||||
}
|
||||
|
||||
describe("withReplyDispatcher", () => {
|
||||
it("dispatchInboundMessage owns dispatcher lifecycle", async () => {
|
||||
const order: string[] = [];
|
||||
const dispatcher = {
|
||||
sendToolResult: () => true,
|
||||
sendBlockReply: () => true,
|
||||
sendFinalReply: () => {
|
||||
order.push("sendFinalReply");
|
||||
return true;
|
||||
},
|
||||
getQueuedCounts: () => ({ tool: 0, block: 0, final: 0 }),
|
||||
getFailedCounts: () => ({ tool: 0, block: 0, final: 0 }),
|
||||
markComplete: () => {
|
||||
order.push("markComplete");
|
||||
},
|
||||
waitForIdle: async () => {
|
||||
order.push("waitForIdle");
|
||||
},
|
||||
} satisfies ReplyDispatcher;
|
||||
hoisted.dispatchReplyFromConfigMock.mockImplementationOnce(async ({ dispatcher }) => {
|
||||
dispatcher.sendFinalReply({ text: "ok" });
|
||||
return { text: "ok" };
|
||||
});
|
||||
|
||||
await dispatchInboundMessage({
|
||||
ctx: buildTestCtx(),
|
||||
cfg: {} as OpenClawConfig,
|
||||
dispatcher,
|
||||
replyResolver: async () => ({ text: "ok" }),
|
||||
});
|
||||
|
||||
expect(order).toEqual(["sendFinalReply", "markComplete", "waitForIdle"]);
|
||||
});
|
||||
|
||||
it("always marks complete and waits for idle after success", async () => {
|
||||
const order: string[] = [];
|
||||
const dispatcher = createDispatcher(order);
|
||||
@@ -66,35 +125,6 @@ describe("withReplyDispatcher", () => {
|
||||
expect(order).toEqual(["run", "markComplete", "waitForIdle", "onSettled"]);
|
||||
});
|
||||
|
||||
it("dispatchInboundMessage owns dispatcher lifecycle", async () => {
|
||||
const order: string[] = [];
|
||||
const dispatcher = {
|
||||
sendToolResult: () => true,
|
||||
sendBlockReply: () => true,
|
||||
sendFinalReply: () => {
|
||||
order.push("sendFinalReply");
|
||||
return true;
|
||||
},
|
||||
getQueuedCounts: () => ({ tool: 0, block: 0, final: 0 }),
|
||||
getFailedCounts: () => ({ tool: 0, block: 0, final: 0 }),
|
||||
markComplete: () => {
|
||||
order.push("markComplete");
|
||||
},
|
||||
waitForIdle: async () => {
|
||||
order.push("waitForIdle");
|
||||
},
|
||||
} satisfies ReplyDispatcher;
|
||||
|
||||
await dispatchInboundMessage({
|
||||
ctx: buildTestCtx(),
|
||||
cfg: {} as OpenClawConfig,
|
||||
dispatcher,
|
||||
replyResolver: async () => ({ text: "ok" }),
|
||||
});
|
||||
|
||||
expect(order).toEqual(["sendFinalReply", "markComplete", "waitForIdle"]);
|
||||
});
|
||||
|
||||
it("dispatchInboundMessageWithBufferedDispatcher cleans up typing after a resolver starts it", async () => {
|
||||
const typing = {
|
||||
onReplyStart: vi.fn(async () => {}),
|
||||
@@ -106,6 +136,13 @@ describe("withReplyDispatcher", () => {
|
||||
markDispatchIdle: vi.fn(),
|
||||
cleanup: vi.fn(),
|
||||
};
|
||||
hoisted.createReplyDispatcherWithTypingMock.mockReturnValueOnce({
|
||||
dispatcher: createDispatcher([]),
|
||||
replyOptions: {},
|
||||
markDispatchIdle: typing.markDispatchIdle,
|
||||
markRunComplete: typing.markRunComplete,
|
||||
});
|
||||
hoisted.dispatchReplyFromConfigMock.mockResolvedValueOnce({ text: "ok" });
|
||||
|
||||
await dispatchInboundMessageWithBufferedDispatcher({
|
||||
ctx: buildTestCtx(),
|
||||
|
||||
@@ -66,6 +66,7 @@ const acpMocks = vi.hoisted(() => ({
|
||||
readAcpSessionEntry: vi.fn<(params: { sessionKey: string; cfg?: OpenClawConfig }) => unknown>(
|
||||
() => null,
|
||||
),
|
||||
getAcpRuntimeBackend: vi.fn<() => unknown>(() => null),
|
||||
upsertAcpSessionMeta: vi.fn<
|
||||
(params: {
|
||||
sessionKey: string;
|
||||
@@ -185,6 +186,7 @@ vi.mock("../../acp/runtime/session-meta.js", () => ({
|
||||
upsertAcpSessionMeta: acpMocks.upsertAcpSessionMeta,
|
||||
}));
|
||||
vi.mock("../../acp/runtime/registry.js", () => ({
|
||||
getAcpRuntimeBackend: acpMocks.getAcpRuntimeBackend,
|
||||
requireAcpRuntimeBackend: acpMocks.requireAcpRuntimeBackend,
|
||||
}));
|
||||
vi.mock("../../infra/outbound/session-binding-service.js", () => ({
|
||||
|
||||
@@ -70,6 +70,7 @@ const acpMocks = vi.hoisted(() => ({
|
||||
readAcpSessionEntry: vi.fn<(params: { sessionKey: string; cfg?: OpenClawConfig }) => unknown>(
|
||||
() => null,
|
||||
),
|
||||
getAcpRuntimeBackend: vi.fn<() => unknown>(() => null),
|
||||
upsertAcpSessionMeta: vi.fn<
|
||||
(params: {
|
||||
sessionKey: string;
|
||||
@@ -248,6 +249,7 @@ vi.mock("../../acp/runtime/session-meta.js", () => ({
|
||||
upsertAcpSessionMeta: acpMocks.upsertAcpSessionMeta,
|
||||
}));
|
||||
vi.mock("../../acp/runtime/registry.js", () => ({
|
||||
getAcpRuntimeBackend: acpMocks.getAcpRuntimeBackend,
|
||||
requireAcpRuntimeBackend: acpMocks.requireAcpRuntimeBackend,
|
||||
}));
|
||||
vi.mock("../../infra/outbound/session-binding-service.js", () => ({
|
||||
|
||||
Reference in New Issue
Block a user