mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 17:20:45 +00:00
perf: dedupe telegram dispatch tests
This commit is contained in:
@@ -784,27 +784,24 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it.each(["block", "partial"] as const)(
|
||||
"forces new message when assistant message restarts (%s mode)",
|
||||
async (streamMode) => {
|
||||
const draftStream = createDraftStream(999);
|
||||
createTelegramDraftStream.mockReturnValue(draftStream);
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
|
||||
async ({ dispatcherOptions, replyOptions }) => {
|
||||
await replyOptions?.onPartialReply?.({ text: "First response" });
|
||||
await replyOptions?.onAssistantMessageStart?.();
|
||||
await replyOptions?.onPartialReply?.({ text: "After tool call" });
|
||||
await dispatcherOptions.deliver({ text: "After tool call" }, { kind: "final" });
|
||||
return { queuedFinal: true };
|
||||
},
|
||||
);
|
||||
deliverReplies.mockResolvedValue({ delivered: true });
|
||||
it("forces new message when assistant message restarts", async () => {
|
||||
const draftStream = createDraftStream(999);
|
||||
createTelegramDraftStream.mockReturnValue(draftStream);
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
|
||||
async ({ dispatcherOptions, replyOptions }) => {
|
||||
await replyOptions?.onPartialReply?.({ text: "First response" });
|
||||
await replyOptions?.onAssistantMessageStart?.();
|
||||
await replyOptions?.onPartialReply?.({ text: "After tool call" });
|
||||
await dispatcherOptions.deliver({ text: "After tool call" }, { kind: "final" });
|
||||
return { queuedFinal: true };
|
||||
},
|
||||
);
|
||||
deliverReplies.mockResolvedValue({ delivered: true });
|
||||
|
||||
await dispatchWithContext({ context: createContext(), streamMode });
|
||||
await dispatchWithContext({ context: createContext(), streamMode: "partial" });
|
||||
|
||||
expect(draftStream.forceNewMessage).toHaveBeenCalledTimes(1);
|
||||
},
|
||||
);
|
||||
expect(draftStream.forceNewMessage).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("materializes boundary preview and keeps it when no matching final arrives", async () => {
|
||||
const answerDraftStream = createDraftStream(999);
|
||||
@@ -1758,51 +1755,48 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
expect(answerDraftStream.clear).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it.each(["partial", "block"] as const)(
|
||||
"keeps finalized text preview when the next assistant message is media-only (%s mode)",
|
||||
async (streamMode) => {
|
||||
let answerMessageId: number | undefined = 1001;
|
||||
const answerDraftStream = {
|
||||
update: vi.fn(),
|
||||
flush: vi.fn().mockResolvedValue(undefined),
|
||||
messageId: vi.fn().mockImplementation(() => answerMessageId),
|
||||
clear: vi.fn().mockResolvedValue(undefined),
|
||||
stop: vi.fn().mockResolvedValue(undefined),
|
||||
forceNewMessage: vi.fn().mockImplementation(() => {
|
||||
answerMessageId = undefined;
|
||||
}),
|
||||
};
|
||||
const reasoningDraftStream = createDraftStream();
|
||||
createTelegramDraftStream
|
||||
.mockImplementationOnce(() => answerDraftStream)
|
||||
.mockImplementationOnce(() => reasoningDraftStream);
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
|
||||
async ({ dispatcherOptions, replyOptions }) => {
|
||||
await replyOptions?.onPartialReply?.({ text: "First message preview" });
|
||||
await dispatcherOptions.deliver({ text: "First message final" }, { kind: "final" });
|
||||
await replyOptions?.onAssistantMessageStart?.();
|
||||
await dispatcherOptions.deliver({ mediaUrl: "file:///tmp/voice.ogg" }, { kind: "final" });
|
||||
return { queuedFinal: true };
|
||||
},
|
||||
);
|
||||
deliverReplies.mockResolvedValue({ delivered: true });
|
||||
editMessageTelegram.mockResolvedValue({ ok: true, chatId: "123", messageId: "1001" });
|
||||
const bot = createBot();
|
||||
it("keeps finalized text preview when the next assistant message is media-only", async () => {
|
||||
let answerMessageId: number | undefined = 1001;
|
||||
const answerDraftStream = {
|
||||
update: vi.fn(),
|
||||
flush: vi.fn().mockResolvedValue(undefined),
|
||||
messageId: vi.fn().mockImplementation(() => answerMessageId),
|
||||
clear: vi.fn().mockResolvedValue(undefined),
|
||||
stop: vi.fn().mockResolvedValue(undefined),
|
||||
forceNewMessage: vi.fn().mockImplementation(() => {
|
||||
answerMessageId = undefined;
|
||||
}),
|
||||
};
|
||||
const reasoningDraftStream = createDraftStream();
|
||||
createTelegramDraftStream
|
||||
.mockImplementationOnce(() => answerDraftStream)
|
||||
.mockImplementationOnce(() => reasoningDraftStream);
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
|
||||
async ({ dispatcherOptions, replyOptions }) => {
|
||||
await replyOptions?.onPartialReply?.({ text: "First message preview" });
|
||||
await dispatcherOptions.deliver({ text: "First message final" }, { kind: "final" });
|
||||
await replyOptions?.onAssistantMessageStart?.();
|
||||
await dispatcherOptions.deliver({ mediaUrl: "file:///tmp/voice.ogg" }, { kind: "final" });
|
||||
return { queuedFinal: true };
|
||||
},
|
||||
);
|
||||
deliverReplies.mockResolvedValue({ delivered: true });
|
||||
editMessageTelegram.mockResolvedValue({ ok: true, chatId: "123", messageId: "1001" });
|
||||
const bot = createBot();
|
||||
|
||||
await dispatchWithContext({ context: createContext(), streamMode, bot });
|
||||
await dispatchWithContext({ context: createContext(), streamMode: "partial", bot });
|
||||
|
||||
expect(editMessageTelegram).toHaveBeenCalledWith(
|
||||
123,
|
||||
1001,
|
||||
"First message final",
|
||||
expect.any(Object),
|
||||
);
|
||||
const deleteMessageCalls = (
|
||||
bot.api as unknown as { deleteMessage: { mock: { calls: unknown[][] } } }
|
||||
).deleteMessage.mock.calls;
|
||||
expect(deleteMessageCalls).not.toContainEqual([123, 1001]);
|
||||
},
|
||||
);
|
||||
expect(editMessageTelegram).toHaveBeenCalledWith(
|
||||
123,
|
||||
1001,
|
||||
"First message final",
|
||||
expect.any(Object),
|
||||
);
|
||||
const deleteMessageCalls = (
|
||||
bot.api as unknown as { deleteMessage: { mock: { calls: unknown[][] } } }
|
||||
).deleteMessage.mock.calls;
|
||||
expect(deleteMessageCalls).not.toContainEqual([123, 1001]);
|
||||
});
|
||||
|
||||
it("maps finals correctly when archived preview id arrives during final flush", async () => {
|
||||
let answerMessageId: number | undefined;
|
||||
@@ -1873,32 +1867,29 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
expect(deliverReplies).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each(["block", "partial"] as const)(
|
||||
"splits reasoning lane only when a later reasoning block starts (%s mode)",
|
||||
async (streamMode) => {
|
||||
const { reasoningDraftStream } = setupDraftStreams({
|
||||
answerMessageId: 999,
|
||||
reasoningMessageId: 111,
|
||||
});
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
|
||||
async ({ dispatcherOptions, replyOptions }) => {
|
||||
await replyOptions?.onReasoningStream?.({ text: "Reasoning:\n_first block_" });
|
||||
await replyOptions?.onReasoningEnd?.();
|
||||
expect(reasoningDraftStream.forceNewMessage).not.toHaveBeenCalled();
|
||||
await replyOptions?.onPartialReply?.({ text: "checking files..." });
|
||||
await replyOptions?.onReasoningStream?.({ text: "Reasoning:\n_second block_" });
|
||||
await dispatcherOptions.deliver({ text: "Done" }, { kind: "final" });
|
||||
return { queuedFinal: true };
|
||||
},
|
||||
);
|
||||
deliverReplies.mockResolvedValue({ delivered: true });
|
||||
editMessageTelegram.mockResolvedValue({ ok: true, chatId: "123", messageId: "999" });
|
||||
it("splits reasoning lane only when a later reasoning block starts", async () => {
|
||||
const { reasoningDraftStream } = setupDraftStreams({
|
||||
answerMessageId: 999,
|
||||
reasoningMessageId: 111,
|
||||
});
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
|
||||
async ({ dispatcherOptions, replyOptions }) => {
|
||||
await replyOptions?.onReasoningStream?.({ text: "Reasoning:\n_first block_" });
|
||||
await replyOptions?.onReasoningEnd?.();
|
||||
expect(reasoningDraftStream.forceNewMessage).not.toHaveBeenCalled();
|
||||
await replyOptions?.onPartialReply?.({ text: "checking files..." });
|
||||
await replyOptions?.onReasoningStream?.({ text: "Reasoning:\n_second block_" });
|
||||
await dispatcherOptions.deliver({ text: "Done" }, { kind: "final" });
|
||||
return { queuedFinal: true };
|
||||
},
|
||||
);
|
||||
deliverReplies.mockResolvedValue({ delivered: true });
|
||||
editMessageTelegram.mockResolvedValue({ ok: true, chatId: "123", messageId: "999" });
|
||||
|
||||
await dispatchWithContext({ context: createReasoningStreamContext(), streamMode });
|
||||
await dispatchWithContext({ context: createReasoningStreamContext(), streamMode: "partial" });
|
||||
|
||||
expect(reasoningDraftStream.forceNewMessage).toHaveBeenCalledTimes(1);
|
||||
},
|
||||
);
|
||||
expect(reasoningDraftStream.forceNewMessage).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("queues reasoning-end split decisions behind queued reasoning deltas", async () => {
|
||||
const { reasoningDraftStream } = setupDraftStreams({
|
||||
@@ -1970,29 +1961,26 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
expect(deleteMessageCalls).toContainEqual([123, 4444]);
|
||||
});
|
||||
|
||||
it.each(["block", "partial"] as const)(
|
||||
"does not split reasoning lane on reasoning end without a later reasoning block (%s mode)",
|
||||
async (streamMode) => {
|
||||
const { reasoningDraftStream } = setupDraftStreams({
|
||||
answerMessageId: 999,
|
||||
reasoningMessageId: 111,
|
||||
});
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
|
||||
async ({ dispatcherOptions, replyOptions }) => {
|
||||
await replyOptions?.onReasoningStream?.({ text: "Reasoning:\n_first block_" });
|
||||
await replyOptions?.onReasoningEnd?.();
|
||||
await replyOptions?.onPartialReply?.({ text: "Here's the answer" });
|
||||
await dispatcherOptions.deliver({ text: "Here's the answer" }, { kind: "final" });
|
||||
return { queuedFinal: true };
|
||||
},
|
||||
);
|
||||
deliverReplies.mockResolvedValue({ delivered: true });
|
||||
it("does not split reasoning lane on reasoning end without a later reasoning block", async () => {
|
||||
const { reasoningDraftStream } = setupDraftStreams({
|
||||
answerMessageId: 999,
|
||||
reasoningMessageId: 111,
|
||||
});
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
|
||||
async ({ dispatcherOptions, replyOptions }) => {
|
||||
await replyOptions?.onReasoningStream?.({ text: "Reasoning:\n_first block_" });
|
||||
await replyOptions?.onReasoningEnd?.();
|
||||
await replyOptions?.onPartialReply?.({ text: "Here's the answer" });
|
||||
await dispatcherOptions.deliver({ text: "Here's the answer" }, { kind: "final" });
|
||||
return { queuedFinal: true };
|
||||
},
|
||||
);
|
||||
deliverReplies.mockResolvedValue({ delivered: true });
|
||||
|
||||
await dispatchWithContext({ context: createReasoningStreamContext(), streamMode });
|
||||
await dispatchWithContext({ context: createReasoningStreamContext(), streamMode: "partial" });
|
||||
|
||||
expect(reasoningDraftStream.forceNewMessage).not.toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
expect(reasoningDraftStream.forceNewMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("suppresses reasoning-only final payloads when reasoning level is off", async () => {
|
||||
setupDraftStreams({ answerMessageId: 999 });
|
||||
@@ -2175,50 +2163,47 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it.each(["partial", "block"] as const)(
|
||||
"does not duplicate reasoning final after reasoning end (%s mode)",
|
||||
async (streamMode) => {
|
||||
let reasoningMessageId: number | undefined = 111;
|
||||
const reasoningDraftStream = {
|
||||
update: vi.fn(),
|
||||
flush: vi.fn().mockResolvedValue(undefined),
|
||||
messageId: vi.fn().mockImplementation(() => reasoningMessageId),
|
||||
clear: vi.fn().mockResolvedValue(undefined),
|
||||
stop: vi.fn().mockResolvedValue(undefined),
|
||||
forceNewMessage: vi.fn().mockImplementation(() => {
|
||||
reasoningMessageId = undefined;
|
||||
}),
|
||||
};
|
||||
const answerDraftStream = createDraftStream(999);
|
||||
createTelegramDraftStream
|
||||
.mockImplementationOnce(() => answerDraftStream)
|
||||
.mockImplementationOnce(() => reasoningDraftStream);
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
|
||||
async ({ dispatcherOptions, replyOptions }) => {
|
||||
await replyOptions?.onReasoningStream?.({ text: "Reasoning:\n_step one_" });
|
||||
await replyOptions?.onReasoningEnd?.();
|
||||
await dispatcherOptions.deliver(
|
||||
{ text: "Reasoning:\n_step one expanded_" },
|
||||
{ kind: "final" },
|
||||
);
|
||||
return { queuedFinal: true };
|
||||
},
|
||||
);
|
||||
deliverReplies.mockResolvedValue({ delivered: true });
|
||||
editMessageTelegram.mockResolvedValue({ ok: true, chatId: "123", messageId: "111" });
|
||||
it("does not duplicate reasoning final after reasoning end", async () => {
|
||||
let reasoningMessageId: number | undefined = 111;
|
||||
const reasoningDraftStream = {
|
||||
update: vi.fn(),
|
||||
flush: vi.fn().mockResolvedValue(undefined),
|
||||
messageId: vi.fn().mockImplementation(() => reasoningMessageId),
|
||||
clear: vi.fn().mockResolvedValue(undefined),
|
||||
stop: vi.fn().mockResolvedValue(undefined),
|
||||
forceNewMessage: vi.fn().mockImplementation(() => {
|
||||
reasoningMessageId = undefined;
|
||||
}),
|
||||
};
|
||||
const answerDraftStream = createDraftStream(999);
|
||||
createTelegramDraftStream
|
||||
.mockImplementationOnce(() => answerDraftStream)
|
||||
.mockImplementationOnce(() => reasoningDraftStream);
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
|
||||
async ({ dispatcherOptions, replyOptions }) => {
|
||||
await replyOptions?.onReasoningStream?.({ text: "Reasoning:\n_step one_" });
|
||||
await replyOptions?.onReasoningEnd?.();
|
||||
await dispatcherOptions.deliver(
|
||||
{ text: "Reasoning:\n_step one expanded_" },
|
||||
{ kind: "final" },
|
||||
);
|
||||
return { queuedFinal: true };
|
||||
},
|
||||
);
|
||||
deliverReplies.mockResolvedValue({ delivered: true });
|
||||
editMessageTelegram.mockResolvedValue({ ok: true, chatId: "123", messageId: "111" });
|
||||
|
||||
await dispatchWithContext({ context: createReasoningStreamContext(), streamMode });
|
||||
await dispatchWithContext({ context: createReasoningStreamContext(), streamMode: "partial" });
|
||||
|
||||
expect(reasoningDraftStream.forceNewMessage).not.toHaveBeenCalled();
|
||||
expect(editMessageTelegram).toHaveBeenCalledWith(
|
||||
123,
|
||||
111,
|
||||
"Reasoning:\n_step one expanded_",
|
||||
expect.any(Object),
|
||||
);
|
||||
expect(deliverReplies).not.toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
expect(reasoningDraftStream.forceNewMessage).not.toHaveBeenCalled();
|
||||
expect(editMessageTelegram).toHaveBeenCalledWith(
|
||||
123,
|
||||
111,
|
||||
"Reasoning:\n_step one expanded_",
|
||||
expect.any(Object),
|
||||
);
|
||||
expect(deliverReplies).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("updates reasoning preview for reasoning block payloads instead of sending duplicates", async () => {
|
||||
setupDraftStreams({ answerMessageId: 999, reasoningMessageId: 111 });
|
||||
|
||||
Reference in New Issue
Block a user