Skip to content

Commit 5c080a3

Browse files
committed
test(tts): fix CI — drop retired ACP stream config keys, split oversized ACP delivery test, safe mock destructure
- Remove retired coalesceIdleMs/maxChunkChars keys from ACP stream test configs (they were retired in 783a5d2 and are asserted dead in dead-config-keys.test.ts; the runtime ignores them), fixing TS2353. - Replace unsafe [[x]] tuple destructures of mock.calls with the existing expectDefined(...) accessor to fix TS2488. - Extract shared setup/fixtures from dispatch-acp-delivery.test.ts into dispatch-acp-delivery.test-utils.ts to satisfy the max-lines lint rule without weakening any test; keep vi.mock in the test file for hoisting. - Reformat the touched test-utils file.
1 parent dec6553 commit 5c080a3

6 files changed

Lines changed: 256 additions & 320 deletions
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
// Shared mocks, fixtures, and coordinator helpers for dispatch-acp-delivery.test.ts.
2+
//
3+
// The vi.hoisted mock objects live here and are consumed by the vi.mock factories in the
4+
// test file. The vi.mock calls themselves must stay in the test file: Vitest hoists a test
5+
// file's vi.mock above all of its imports, so the mocks register before dispatch-acp-delivery.js
6+
// (whose reply-threading dependency binds getChannelPlugin at module-eval time) is loaded.
7+
import { vi } from "vitest";
8+
import type { OpenClawConfig } from "../../config/config.js";
9+
import { createAcpDispatchDeliveryCoordinator } from "./dispatch-acp-delivery.js";
10+
import type { ReplyDispatcher } from "./reply-dispatcher.types.js";
11+
import { buildTestCtx } from "./test-ctx.js";
12+
import { createAcpTestConfig } from "./test-fixtures/acp-runtime.js";
13+
14+
const ttsMocks = vi.hoisted(() => ({
15+
maybeApplyTtsToPayload: vi.fn(async (paramsUnknown: unknown) => {
16+
const params = paramsUnknown as { payload: unknown };
17+
return params.payload;
18+
}),
19+
}));
20+
21+
const deliveryMocks = vi.hoisted(() => ({
22+
routeReply: vi.fn(
23+
async (
24+
_params: unknown,
25+
): Promise<{
26+
ok: boolean;
27+
messageId?: string;
28+
suppressed?: boolean;
29+
reason?: string;
30+
}> => ({ ok: true, messageId: "mock-message" }),
31+
),
32+
runMessageAction: vi.fn(async (_params: unknown) => ({ ok: true as const })),
33+
}));
34+
35+
const channelPluginMocks = vi.hoisted(() => ({
36+
accountIds: ["default"] as string[],
37+
defaultAccountId: undefined as string | undefined,
38+
replyToModeForAccount: undefined as
39+
| ((accountId: string | null | undefined) => "all" | "off")
40+
| undefined,
41+
shouldTreatDeliveredTextAsVisible: (({
42+
kind,
43+
text,
44+
}: {
45+
kind: "tool" | "block" | "final";
46+
text?: string;
47+
}) => kind === "block" && typeof text === "string" && text.trim().length > 0) as
48+
| ((params: { kind: "tool" | "block" | "final"; text?: string }) => boolean)
49+
| undefined,
50+
shouldTreatRoutedTextAsVisible: undefined as
51+
| ((params: { kind: "tool" | "block" | "final"; text?: string }) => boolean)
52+
| undefined,
53+
getChannelPlugin: vi.fn((channelId: string) => {
54+
if (channelId !== "visiblechat") {
55+
return undefined;
56+
}
57+
return {
58+
config: {
59+
listAccountIds: () => channelPluginMocks.accountIds,
60+
resolveAccount: () => ({}),
61+
...(channelPluginMocks.defaultAccountId
62+
? { defaultAccountId: () => channelPluginMocks.defaultAccountId ?? "default" }
63+
: {}),
64+
},
65+
...(channelPluginMocks.replyToModeForAccount
66+
? {
67+
threading: {
68+
resolveReplyToMode: ({ accountId }: { accountId?: string | null }) =>
69+
channelPluginMocks.replyToModeForAccount?.(accountId) ?? "all",
70+
},
71+
}
72+
: {}),
73+
outbound: {
74+
shouldTreatDeliveredTextAsVisible: channelPluginMocks.shouldTreatDeliveredTextAsVisible,
75+
shouldTreatRoutedTextAsVisible: channelPluginMocks.shouldTreatRoutedTextAsVisible,
76+
},
77+
};
78+
}),
79+
}));
80+
81+
export { channelPluginMocks, deliveryMocks, ttsMocks };
82+
83+
/** Resets delivery/channel mocks to their default per-test behavior. */
84+
export function resetAcpDeliveryMocks(): void {
85+
deliveryMocks.routeReply.mockClear();
86+
deliveryMocks.routeReply.mockResolvedValue({ ok: true, messageId: "mock-message" });
87+
deliveryMocks.runMessageAction.mockClear();
88+
deliveryMocks.runMessageAction.mockResolvedValue({ ok: true as const });
89+
channelPluginMocks.getChannelPlugin.mockClear();
90+
channelPluginMocks.accountIds = ["default"];
91+
channelPluginMocks.defaultAccountId = undefined;
92+
channelPluginMocks.replyToModeForAccount = undefined;
93+
channelPluginMocks.shouldTreatDeliveredTextAsVisible = ({
94+
kind,
95+
text,
96+
}: {
97+
kind: "tool" | "block" | "final";
98+
text?: string;
99+
}) => kind === "block" && typeof text === "string" && text.trim().length > 0;
100+
channelPluginMocks.shouldTreatRoutedTextAsVisible = undefined;
101+
}
102+
103+
export function createDispatcher(): ReplyDispatcher {
104+
return {
105+
sendToolResult: vi.fn(() => true),
106+
sendBlockReply: vi.fn(() => true),
107+
sendFinalReply: vi.fn(() => true),
108+
waitForIdle: vi.fn(async () => {}),
109+
getQueuedCounts: vi.fn(() => ({ tool: 0, block: 0, final: 0 })),
110+
getFailedCounts: vi.fn(() => ({ tool: 0, block: 0, final: 0 })),
111+
markComplete: vi.fn(),
112+
};
113+
}
114+
115+
/** Standard visiblechat delivery coordinator for a caller-provided dispatcher. */
116+
export function createDefaultCoordinator(dispatcher: ReplyDispatcher) {
117+
return createAcpDispatchDeliveryCoordinator({
118+
cfg: createAcpTestConfig(),
119+
ctx: visibleChatCtx(),
120+
dispatcher,
121+
inboundAudio: false,
122+
shouldRouteToOriginating: false,
123+
});
124+
}
125+
126+
export function createCoordinator(onReplyStart?: (...args: unknown[]) => Promise<void>) {
127+
return createAcpDispatchDeliveryCoordinator({
128+
cfg: createAcpTestConfig(),
129+
ctx: visibleChatCtx(),
130+
dispatcher: createDispatcher(),
131+
inboundAudio: false,
132+
shouldRouteToOriginating: false,
133+
...(onReplyStart ? { onReplyStart } : {}),
134+
});
135+
}
136+
137+
export async function raceWithTimeoutResult<T>(
138+
promise: Promise<T>,
139+
timeoutMs: number,
140+
timeoutResult: T,
141+
): Promise<T> {
142+
let timer: ReturnType<typeof setTimeout> | undefined;
143+
try {
144+
return await Promise.race([
145+
promise,
146+
new Promise<T>((resolve) => {
147+
timer = setTimeout(() => resolve(timeoutResult), timeoutMs);
148+
}),
149+
]);
150+
} finally {
151+
if (timer) {
152+
clearTimeout(timer);
153+
}
154+
}
155+
}
156+
157+
/** Canonical visiblechat message context shared by most coordinator fixtures. */
158+
export function visibleChatCtx() {
159+
return buildTestCtx({
160+
Provider: "visiblechat",
161+
Surface: "visiblechat",
162+
SessionKey: "agent:codex-acp:session-1",
163+
});
164+
}
165+
166+
export function createVisibleChatAcpCoordinator(cfg: OpenClawConfig) {
167+
return createAcpDispatchDeliveryCoordinator({
168+
cfg,
169+
ctx: visibleChatCtx(),
170+
dispatcher: createDispatcher(),
171+
inboundAudio: false,
172+
shouldRouteToOriginating: true,
173+
originatingChannel: "visiblechat",
174+
originatingTo: "channel:thread-1",
175+
});
176+
}

0 commit comments

Comments
 (0)