test(agents): dedupe agent and cron test scaffolds

This commit is contained in:
Peter Steinberger
2026-03-02 06:40:42 +00:00
parent 281494ae52
commit 7e29d604ba
38 changed files with 3114 additions and 4486 deletions

View File

@@ -85,6 +85,7 @@ vi.mock("../../infra/outbound/session-binding-service.js", () => ({
}));
const { tryDispatchAcpReply } = await import("./dispatch-acp.js");
const sessionKey = "agent:codex-acp:session-1";
function createDispatcher(): {
dispatcher: ReplyDispatcher;
@@ -105,7 +106,7 @@ function createDispatcher(): {
function setReadyAcpResolution() {
managerMocks.resolveSession.mockReturnValue({
kind: "ready",
sessionKey: "agent:codex-acp:session-1",
sessionKey,
meta: createAcpSessionMeta(),
});
}
@@ -124,6 +125,84 @@ function createAcpConfigWithVisibleToolTags(): OpenClawConfig {
});
}
async function runDispatch(params: {
bodyForAgent: string;
cfg?: OpenClawConfig;
dispatcher?: ReplyDispatcher;
shouldRouteToOriginating?: boolean;
onReplyStart?: () => void;
}) {
return tryDispatchAcpReply({
ctx: buildTestCtx({
Provider: "discord",
Surface: "discord",
SessionKey: sessionKey,
BodyForAgent: params.bodyForAgent,
}),
cfg: params.cfg ?? createAcpTestConfig(),
dispatcher: params.dispatcher ?? createDispatcher().dispatcher,
sessionKey,
inboundAudio: false,
shouldRouteToOriginating: params.shouldRouteToOriginating ?? false,
...(params.shouldRouteToOriginating
? { originatingChannel: "telegram", originatingTo: "telegram:thread-1" }
: {}),
shouldSendToolSummaries: true,
bypassForCommand: false,
...(params.onReplyStart ? { onReplyStart: params.onReplyStart } : {}),
recordProcessed: vi.fn(),
markIdle: vi.fn(),
});
}
async function emitToolLifecycleEvents(
onEvent: (event: unknown) => Promise<void>,
toolCallId: string,
) {
await onEvent({
type: "tool_call",
tag: "tool_call",
toolCallId,
status: "in_progress",
title: "Run command",
text: "Run command (in_progress)",
});
await onEvent({
type: "tool_call",
tag: "tool_call_update",
toolCallId,
status: "completed",
title: "Run command",
text: "Run command (completed)",
});
await onEvent({ type: "done" });
}
function mockToolLifecycleTurn(toolCallId: string) {
managerMocks.runTurn.mockImplementation(
async ({ onEvent }: { onEvent: (event: unknown) => Promise<void> }) => {
await emitToolLifecycleEvents(onEvent, toolCallId);
},
);
}
function mockVisibleTextTurn(text = "visible") {
managerMocks.runTurn.mockImplementationOnce(
async ({ onEvent }: { onEvent: (event: unknown) => Promise<void> }) => {
await onEvent({ type: "text_delta", text, tag: "agent_message_chunk" });
await onEvent({ type: "done" });
},
);
}
async function dispatchVisibleTurn(onReplyStart: () => void) {
await runDispatch({
bodyForAgent: "visible",
dispatcher: createDispatcher().dispatcher,
onReplyStart,
});
}
describe("tryDispatchAcpReply", () => {
beforeEach(() => {
managerMocks.resolveSession.mockReset();
@@ -160,24 +239,10 @@ describe("tryDispatchAcpReply", () => {
);
const { dispatcher } = createDispatcher();
const result = await tryDispatchAcpReply({
ctx: buildTestCtx({
Provider: "discord",
Surface: "discord",
SessionKey: "agent:codex-acp:session-1",
BodyForAgent: "reply",
}),
cfg: createAcpTestConfig(),
const result = await runDispatch({
bodyForAgent: "reply",
dispatcher,
sessionKey: "agent:codex-acp:session-1",
inboundAudio: false,
shouldRouteToOriginating: true,
originatingChannel: "telegram",
originatingTo: "telegram:thread-1",
shouldSendToolSummaries: true,
bypassForCommand: false,
recordProcessed: vi.fn(),
markIdle: vi.fn(),
});
expect(result?.counts.block).toBe(1);
@@ -192,48 +257,15 @@ describe("tryDispatchAcpReply", () => {
it("edits ACP tool lifecycle updates in place when supported", async () => {
setReadyAcpResolution();
managerMocks.runTurn.mockImplementation(
async ({ onEvent }: { onEvent: (event: unknown) => Promise<void> }) => {
await onEvent({
type: "tool_call",
tag: "tool_call",
toolCallId: "call-1",
status: "in_progress",
title: "Run command",
text: "Run command (in_progress)",
});
await onEvent({
type: "tool_call",
tag: "tool_call_update",
toolCallId: "call-1",
status: "completed",
title: "Run command",
text: "Run command (completed)",
});
await onEvent({ type: "done" });
},
);
mockToolLifecycleTurn("call-1");
routeMocks.routeReply.mockResolvedValueOnce({ ok: true, messageId: "tool-msg-1" });
const { dispatcher } = createDispatcher();
await tryDispatchAcpReply({
ctx: buildTestCtx({
Provider: "discord",
Surface: "discord",
SessionKey: "agent:codex-acp:session-1",
BodyForAgent: "run tool",
}),
await runDispatch({
bodyForAgent: "run tool",
cfg: createAcpConfigWithVisibleToolTags(),
dispatcher,
sessionKey: "agent:codex-acp:session-1",
inboundAudio: false,
shouldRouteToOriginating: true,
originatingChannel: "telegram",
originatingTo: "telegram:thread-1",
shouldSendToolSummaries: true,
bypassForCommand: false,
recordProcessed: vi.fn(),
markIdle: vi.fn(),
});
expect(routeMocks.routeReply).toHaveBeenCalledTimes(1);
@@ -249,51 +281,18 @@ describe("tryDispatchAcpReply", () => {
it("falls back to new tool message when edit fails", async () => {
setReadyAcpResolution();
managerMocks.runTurn.mockImplementation(
async ({ onEvent }: { onEvent: (event: unknown) => Promise<void> }) => {
await onEvent({
type: "tool_call",
tag: "tool_call",
toolCallId: "call-2",
status: "in_progress",
title: "Run command",
text: "Run command (in_progress)",
});
await onEvent({
type: "tool_call",
tag: "tool_call_update",
toolCallId: "call-2",
status: "completed",
title: "Run command",
text: "Run command (completed)",
});
await onEvent({ type: "done" });
},
);
mockToolLifecycleTurn("call-2");
routeMocks.routeReply
.mockResolvedValueOnce({ ok: true, messageId: "tool-msg-2" })
.mockResolvedValueOnce({ ok: true, messageId: "tool-msg-2-fallback" });
messageActionMocks.runMessageAction.mockRejectedValueOnce(new Error("edit unsupported"));
const { dispatcher } = createDispatcher();
await tryDispatchAcpReply({
ctx: buildTestCtx({
Provider: "discord",
Surface: "discord",
SessionKey: "agent:codex-acp:session-1",
BodyForAgent: "run tool",
}),
await runDispatch({
bodyForAgent: "run tool",
cfg: createAcpConfigWithVisibleToolTags(),
dispatcher,
sessionKey: "agent:codex-acp:session-1",
inboundAudio: false,
shouldRouteToOriginating: true,
originatingChannel: "telegram",
originatingTo: "telegram:thread-1",
shouldSendToolSummaries: true,
bypassForCommand: false,
recordProcessed: vi.fn(),
markIdle: vi.fn(),
});
expect(messageActionMocks.runMessageAction).toHaveBeenCalledTimes(1);
@@ -317,50 +316,15 @@ describe("tryDispatchAcpReply", () => {
await onEvent({ type: "done" });
},
);
await tryDispatchAcpReply({
ctx: buildTestCtx({
Provider: "discord",
Surface: "discord",
SessionKey: "agent:codex-acp:session-1",
BodyForAgent: "hidden",
}),
cfg: createAcpTestConfig(),
await runDispatch({
bodyForAgent: "hidden",
dispatcher,
sessionKey: "agent:codex-acp:session-1",
inboundAudio: false,
shouldRouteToOriginating: false,
shouldSendToolSummaries: true,
bypassForCommand: false,
onReplyStart,
recordProcessed: vi.fn(),
markIdle: vi.fn(),
});
expect(onReplyStart).toHaveBeenCalledTimes(1);
managerMocks.runTurn.mockImplementationOnce(
async ({ onEvent }: { onEvent: (event: unknown) => Promise<void> }) => {
await onEvent({ type: "text_delta", text: "visible", tag: "agent_message_chunk" });
await onEvent({ type: "done" });
},
);
await tryDispatchAcpReply({
ctx: buildTestCtx({
Provider: "discord",
Surface: "discord",
SessionKey: "agent:codex-acp:session-1",
BodyForAgent: "visible",
}),
cfg: createAcpTestConfig(),
dispatcher: createDispatcher().dispatcher,
sessionKey: "agent:codex-acp:session-1",
inboundAudio: false,
shouldRouteToOriginating: false,
shouldSendToolSummaries: true,
bypassForCommand: false,
onReplyStart,
recordProcessed: vi.fn(),
markIdle: vi.fn(),
});
mockVisibleTextTurn();
await dispatchVisibleTurn(onReplyStart);
expect(onReplyStart).toHaveBeenCalledTimes(2);
});
@@ -368,31 +332,8 @@ describe("tryDispatchAcpReply", () => {
setReadyAcpResolution();
const onReplyStart = vi.fn();
managerMocks.runTurn.mockImplementationOnce(
async ({ onEvent }: { onEvent: (event: unknown) => Promise<void> }) => {
await onEvent({ type: "text_delta", text: "visible", tag: "agent_message_chunk" });
await onEvent({ type: "done" });
},
);
await tryDispatchAcpReply({
ctx: buildTestCtx({
Provider: "discord",
Surface: "discord",
SessionKey: "agent:codex-acp:session-1",
BodyForAgent: "visible",
}),
cfg: createAcpTestConfig(),
dispatcher: createDispatcher().dispatcher,
sessionKey: "agent:codex-acp:session-1",
inboundAudio: false,
shouldRouteToOriginating: false,
shouldSendToolSummaries: true,
bypassForCommand: false,
onReplyStart,
recordProcessed: vi.fn(),
markIdle: vi.fn(),
});
mockVisibleTextTurn();
await dispatchVisibleTurn(onReplyStart);
expect(onReplyStart).toHaveBeenCalledTimes(1);
});
@@ -402,23 +343,10 @@ describe("tryDispatchAcpReply", () => {
const onReplyStart = vi.fn();
const { dispatcher } = createDispatcher();
await tryDispatchAcpReply({
ctx: buildTestCtx({
Provider: "discord",
Surface: "discord",
SessionKey: "agent:codex-acp:session-1",
BodyForAgent: " ",
}),
cfg: createAcpTestConfig(),
await runDispatch({
bodyForAgent: " ",
dispatcher,
sessionKey: "agent:codex-acp:session-1",
inboundAudio: false,
shouldRouteToOriginating: false,
shouldSendToolSummaries: true,
bypassForCommand: false,
onReplyStart,
recordProcessed: vi.fn(),
markIdle: vi.fn(),
});
expect(managerMocks.runTurn).not.toHaveBeenCalled();
@@ -432,22 +360,9 @@ describe("tryDispatchAcpReply", () => {
);
const { dispatcher } = createDispatcher();
await tryDispatchAcpReply({
ctx: buildTestCtx({
Provider: "discord",
Surface: "discord",
SessionKey: "agent:codex-acp:session-1",
BodyForAgent: "test",
}),
cfg: createAcpTestConfig(),
await runDispatch({
bodyForAgent: "test",
dispatcher,
sessionKey: "agent:codex-acp:session-1",
inboundAudio: false,
shouldRouteToOriginating: false,
shouldSendToolSummaries: true,
bypassForCommand: false,
recordProcessed: vi.fn(),
markIdle: vi.fn(),
});
expect(managerMocks.runTurn).not.toHaveBeenCalled();