Skip to content

Commit 3ffb9f1

Browse files
committed
test: reduce feishu reply dispatcher duplication
1 parent d347a44 commit 3ffb9f1

File tree

1 file changed

+44
-109
lines changed

1 file changed

+44
-109
lines changed

extensions/feishu/src/reply-dispatcher.test.ts

Lines changed: 44 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ vi.mock("./streaming-card.js", () => ({
6363
import { createFeishuReplyDispatcher } from "./reply-dispatcher.js";
6464

6565
describe("createFeishuReplyDispatcher streaming behavior", () => {
66+
type ReplyDispatcherArgs = Parameters<typeof createFeishuReplyDispatcher>[0];
67+
6668
beforeEach(() => {
6769
vi.clearAllMocks();
6870
streamingInstances.length = 0;
@@ -128,6 +130,25 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
128130
return createReplyDispatcherWithTypingMock.mock.calls[0]?.[0];
129131
}
130132

133+
function createRuntimeLogger() {
134+
return { log: vi.fn(), error: vi.fn() } as never;
135+
}
136+
137+
function createDispatcherHarness(overrides: Partial<ReplyDispatcherArgs> = {}) {
138+
const result = createFeishuReplyDispatcher({
139+
cfg: {} as never,
140+
agentId: "agent",
141+
runtime: {} as never,
142+
chatId: "oc_chat",
143+
...overrides,
144+
});
145+
146+
return {
147+
result,
148+
options: createReplyDispatcherWithTypingMock.mock.calls.at(-1)?.[0],
149+
};
150+
}
151+
131152
it("skips typing indicator when account typingIndicator is disabled", async () => {
132153
resolveFeishuAccountMock.mockReturnValue({
133154
accountId: "main",
@@ -209,14 +230,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
209230
});
210231

211232
it("keeps auto mode plain text on non-streaming send path", async () => {
212-
createFeishuReplyDispatcher({
213-
cfg: {} as never,
214-
agentId: "agent",
215-
runtime: {} as never,
216-
chatId: "oc_chat",
217-
});
218-
219-
const options = createReplyDispatcherWithTypingMock.mock.calls[0]?.[0];
233+
const { options } = createDispatcherHarness();
220234
await options.deliver({ text: "plain text" }, { kind: "final" });
221235

222236
expect(streamingInstances).toHaveLength(0);
@@ -225,14 +239,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
225239
});
226240

227241
it("suppresses internal block payload delivery", async () => {
228-
createFeishuReplyDispatcher({
229-
cfg: {} as never,
230-
agentId: "agent",
231-
runtime: {} as never,
232-
chatId: "oc_chat",
233-
});
234-
235-
const options = createReplyDispatcherWithTypingMock.mock.calls[0]?.[0];
242+
const { options } = createDispatcherHarness();
236243
await options.deliver({ text: "internal reasoning chunk" }, { kind: "block" });
237244

238245
expect(streamingInstances).toHaveLength(0);
@@ -253,15 +260,10 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
253260
});
254261

255262
it("uses streaming session for auto mode markdown payloads", async () => {
256-
createFeishuReplyDispatcher({
257-
cfg: {} as never,
258-
agentId: "agent",
259-
runtime: { log: vi.fn(), error: vi.fn() } as never,
260-
chatId: "oc_chat",
263+
const { options } = createDispatcherHarness({
264+
runtime: createRuntimeLogger(),
261265
rootId: "om_root_topic",
262266
});
263-
264-
const options = createReplyDispatcherWithTypingMock.mock.calls[0]?.[0];
265267
await options.deliver({ text: "```ts\nconst x = 1\n```" }, { kind: "final" });
266268

267269
expect(streamingInstances).toHaveLength(1);
@@ -277,14 +279,9 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
277279
});
278280

279281
it("closes streaming with block text when final reply is missing", async () => {
280-
createFeishuReplyDispatcher({
281-
cfg: {} as never,
282-
agentId: "agent",
283-
runtime: { log: vi.fn(), error: vi.fn() } as never,
284-
chatId: "oc_chat",
282+
const { options } = createDispatcherHarness({
283+
runtime: createRuntimeLogger(),
285284
});
286-
287-
const options = createReplyDispatcherWithTypingMock.mock.calls[0]?.[0];
288285
await options.deliver({ text: "```md\npartial answer\n```" }, { kind: "block" });
289286
await options.onIdle?.();
290287

@@ -295,14 +292,9 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
295292
});
296293

297294
it("delivers distinct final payloads after streaming close", async () => {
298-
createFeishuReplyDispatcher({
299-
cfg: {} as never,
300-
agentId: "agent",
301-
runtime: { log: vi.fn(), error: vi.fn() } as never,
302-
chatId: "oc_chat",
295+
const { options } = createDispatcherHarness({
296+
runtime: createRuntimeLogger(),
303297
});
304-
305-
const options = createReplyDispatcherWithTypingMock.mock.calls[0]?.[0];
306298
await options.deliver({ text: "```md\n完整回复第一段\n```" }, { kind: "final" });
307299
await options.deliver({ text: "```md\n完整回复第一段 + 第二段\n```" }, { kind: "final" });
308300

@@ -316,14 +308,9 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
316308
});
317309

318310
it("skips exact duplicate final text after streaming close", async () => {
319-
createFeishuReplyDispatcher({
320-
cfg: {} as never,
321-
agentId: "agent",
322-
runtime: { log: vi.fn(), error: vi.fn() } as never,
323-
chatId: "oc_chat",
311+
const { options } = createDispatcherHarness({
312+
runtime: createRuntimeLogger(),
324313
});
325-
326-
const options = createReplyDispatcherWithTypingMock.mock.calls[0]?.[0];
327314
await options.deliver({ text: "```md\n同一条回复\n```" }, { kind: "final" });
328315
await options.deliver({ text: "```md\n同一条回复\n```" }, { kind: "final" });
329316

@@ -383,14 +370,9 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
383370
},
384371
});
385372

386-
const result = createFeishuReplyDispatcher({
387-
cfg: {} as never,
388-
agentId: "agent",
389-
runtime: { log: vi.fn(), error: vi.fn() } as never,
390-
chatId: "oc_chat",
373+
const { result, options } = createDispatcherHarness({
374+
runtime: createRuntimeLogger(),
391375
});
392-
393-
const options = createReplyDispatcherWithTypingMock.mock.calls[0]?.[0];
394376
await options.onReplyStart?.();
395377
await result.replyOptions.onPartialReply?.({ text: "hello" });
396378
await options.deliver({ text: "lo world" }, { kind: "block" });
@@ -402,14 +384,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
402384
});
403385

404386
it("sends media-only payloads as attachments", async () => {
405-
createFeishuReplyDispatcher({
406-
cfg: {} as never,
407-
agentId: "agent",
408-
runtime: {} as never,
409-
chatId: "oc_chat",
410-
});
411-
412-
const options = createReplyDispatcherWithTypingMock.mock.calls[0]?.[0];
387+
const { options } = createDispatcherHarness();
413388
await options.deliver({ mediaUrl: "https://example.com/a.png" }, { kind: "final" });
414389

415390
expect(sendMediaFeishuMock).toHaveBeenCalledTimes(1);
@@ -424,14 +399,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
424399
});
425400

426401
it("falls back to legacy mediaUrl when mediaUrls is an empty array", async () => {
427-
createFeishuReplyDispatcher({
428-
cfg: {} as never,
429-
agentId: "agent",
430-
runtime: {} as never,
431-
chatId: "oc_chat",
432-
});
433-
434-
const options = createReplyDispatcherWithTypingMock.mock.calls[0]?.[0];
402+
const { options } = createDispatcherHarness();
435403
await options.deliver(
436404
{ text: "caption", mediaUrl: "https://example.com/a.png", mediaUrls: [] },
437405
{ kind: "final" },
@@ -447,14 +415,9 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
447415
});
448416

449417
it("sends attachments after streaming final markdown replies", async () => {
450-
createFeishuReplyDispatcher({
451-
cfg: {} as never,
452-
agentId: "agent",
453-
runtime: { log: vi.fn(), error: vi.fn() } as never,
454-
chatId: "oc_chat",
418+
const { options } = createDispatcherHarness({
419+
runtime: createRuntimeLogger(),
455420
});
456-
457-
const options = createReplyDispatcherWithTypingMock.mock.calls[0]?.[0];
458421
await options.deliver(
459422
{ text: "```ts\nconst x = 1\n```", mediaUrls: ["https://example.com/a.png"] },
460423
{ kind: "final" },
@@ -472,16 +435,10 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
472435
});
473436

474437
it("passes replyInThread to sendMessageFeishu for plain text", async () => {
475-
createFeishuReplyDispatcher({
476-
cfg: {} as never,
477-
agentId: "agent",
478-
runtime: {} as never,
479-
chatId: "oc_chat",
438+
const { options } = createDispatcherHarness({
480439
replyToMessageId: "om_msg",
481440
replyInThread: true,
482441
});
483-
484-
const options = createReplyDispatcherWithTypingMock.mock.calls[0]?.[0];
485442
await options.deliver({ text: "plain text" }, { kind: "final" });
486443

487444
expect(sendMessageFeishuMock).toHaveBeenCalledWith(
@@ -504,16 +461,10 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
504461
},
505462
});
506463

507-
createFeishuReplyDispatcher({
508-
cfg: {} as never,
509-
agentId: "agent",
510-
runtime: {} as never,
511-
chatId: "oc_chat",
464+
const { options } = createDispatcherHarness({
512465
replyToMessageId: "om_msg",
513466
replyInThread: true,
514467
});
515-
516-
const options = createReplyDispatcherWithTypingMock.mock.calls[0]?.[0];
517468
await options.deliver({ text: "card text" }, { kind: "final" });
518469

519470
expect(sendMarkdownCardFeishuMock).toHaveBeenCalledWith(
@@ -525,16 +476,11 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
525476
});
526477

527478
it("passes replyToMessageId and replyInThread to streaming.start()", async () => {
528-
createFeishuReplyDispatcher({
529-
cfg: {} as never,
530-
agentId: "agent",
531-
runtime: { log: vi.fn(), error: vi.fn() } as never,
532-
chatId: "oc_chat",
479+
const { options } = createDispatcherHarness({
480+
runtime: createRuntimeLogger(),
533481
replyToMessageId: "om_msg",
534482
replyInThread: true,
535483
});
536-
537-
const options = createReplyDispatcherWithTypingMock.mock.calls[0]?.[0];
538484
await options.deliver({ text: "```ts\nconst x = 1\n```" }, { kind: "final" });
539485

540486
expect(streamingInstances).toHaveLength(1);
@@ -545,18 +491,13 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
545491
});
546492

547493
it("disables streaming for thread replies and keeps reply metadata", async () => {
548-
createFeishuReplyDispatcher({
549-
cfg: {} as never,
550-
agentId: "agent",
551-
runtime: { log: vi.fn(), error: vi.fn() } as never,
552-
chatId: "oc_chat",
494+
const { options } = createDispatcherHarness({
495+
runtime: createRuntimeLogger(),
553496
replyToMessageId: "om_msg",
554497
replyInThread: false,
555498
threadReply: true,
556499
rootId: "om_root_topic",
557500
});
558-
559-
const options = createReplyDispatcherWithTypingMock.mock.calls[0]?.[0];
560501
await options.deliver({ text: "```ts\nconst x = 1\n```" }, { kind: "final" });
561502

562503
expect(streamingInstances).toHaveLength(0);
@@ -569,16 +510,10 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
569510
});
570511

571512
it("passes replyInThread to media attachments", async () => {
572-
createFeishuReplyDispatcher({
573-
cfg: {} as never,
574-
agentId: "agent",
575-
runtime: {} as never,
576-
chatId: "oc_chat",
513+
const { options } = createDispatcherHarness({
577514
replyToMessageId: "om_msg",
578515
replyInThread: true,
579516
});
580-
581-
const options = createReplyDispatcherWithTypingMock.mock.calls[0]?.[0];
582517
await options.deliver({ mediaUrl: "https://example.com/a.png" }, { kind: "final" });
583518

584519
expect(sendMediaFeishuMock).toHaveBeenCalledWith(

0 commit comments

Comments
 (0)