mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-17 10:46:01 +00:00
test: clear feishu reply dispatcher broad matchers
This commit is contained in:
@@ -199,6 +199,64 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
};
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function requireRecord(value: unknown, label: string): Record<string, unknown> {
|
||||
expect(isRecord(value), `${label} must be an object`).toBe(true);
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function expectRecordFields(
|
||||
value: unknown,
|
||||
label: string,
|
||||
expected: Record<string, unknown>,
|
||||
): Record<string, unknown> {
|
||||
const record = requireRecord(value, label);
|
||||
for (const [key, expectedValue] of Object.entries(expected)) {
|
||||
expect(record[key], `${label}.${key}`).toEqual(expectedValue);
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
function expectMockArgFields(
|
||||
mock: ReturnType<typeof vi.fn>,
|
||||
label: string,
|
||||
expected: Record<string, unknown>,
|
||||
callIndex = 0,
|
||||
argIndex = 0,
|
||||
): Record<string, unknown> {
|
||||
return expectRecordFields(mock.mock.calls[callIndex]?.[argIndex], label, expected);
|
||||
}
|
||||
|
||||
function expectLastMockArgFields(
|
||||
mock: ReturnType<typeof vi.fn>,
|
||||
label: string,
|
||||
expected: Record<string, unknown>,
|
||||
argIndex = 0,
|
||||
): Record<string, unknown> {
|
||||
const callIndex = mock.mock.calls.length - 1;
|
||||
return expectMockArgFields(mock, label, expected, callIndex, argIndex);
|
||||
}
|
||||
|
||||
function expectStreamingStartOptions(
|
||||
instanceIndex: number,
|
||||
expected: Record<string, unknown>,
|
||||
): Record<string, unknown> {
|
||||
const start = streamingInstances[instanceIndex]?.start;
|
||||
expect(start, "streaming instance must exist").toBeDefined();
|
||||
expect(start.mock.calls[0]?.[0]).toBe("oc_chat");
|
||||
expect(start.mock.calls[0]?.[1]).toBe("chat_id");
|
||||
return expectRecordFields(start.mock.calls[0]?.[2], "streaming start options", expected);
|
||||
}
|
||||
|
||||
function streamingUpdateTexts(instanceIndex = 0): string[] {
|
||||
return streamingInstances[instanceIndex].update.mock.calls.map((call: unknown[]) =>
|
||||
typeof call[0] === "string" ? call[0] : "",
|
||||
);
|
||||
}
|
||||
|
||||
it("skips typing indicator when account typingIndicator is disabled", async () => {
|
||||
resolveFeishuAccountMock.mockReturnValue({
|
||||
accountId: "main",
|
||||
@@ -272,11 +330,9 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
await options.onReplyStart?.();
|
||||
|
||||
expect(addTypingIndicatorMock).toHaveBeenCalledTimes(1);
|
||||
expect(addTypingIndicatorMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
messageId: "om_parent",
|
||||
}),
|
||||
);
|
||||
expectMockArgFields(addTypingIndicatorMock, "typing indicator params", {
|
||||
messageId: "om_parent",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps auto mode plain text on non-streaming send path", async () => {
|
||||
@@ -413,17 +469,13 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
|
||||
expect(streamingInstances).toHaveLength(1);
|
||||
expect(streamingInstances[0].start).toHaveBeenCalledTimes(1);
|
||||
expect(streamingInstances[0].start).toHaveBeenCalledWith(
|
||||
"oc_chat",
|
||||
"chat_id",
|
||||
expect.objectContaining({
|
||||
replyToMessageId: undefined,
|
||||
replyInThread: undefined,
|
||||
rootId: "om_root_topic",
|
||||
header: { title: "agent", template: "blue" },
|
||||
note: "Agent: agent",
|
||||
}),
|
||||
);
|
||||
expectStreamingStartOptions(0, {
|
||||
replyToMessageId: undefined,
|
||||
replyInThread: undefined,
|
||||
rootId: "om_root_topic",
|
||||
header: { title: "agent", template: "blue" },
|
||||
note: "Agent: agent",
|
||||
});
|
||||
expect(streamingInstances[0].close).toHaveBeenCalledTimes(1);
|
||||
expect(sendMessageFeishuMock).not.toHaveBeenCalled();
|
||||
expect(sendMarkdownCardFeishuMock).not.toHaveBeenCalled();
|
||||
@@ -545,11 +597,9 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
expect(sendMarkdownCardFeishuMock).not.toHaveBeenCalled();
|
||||
expect(sendStructuredCardFeishuMock).not.toHaveBeenCalled();
|
||||
expect(sendMediaFeishuMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMediaFeishuMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
mediaUrl: "https://example.com/a.png",
|
||||
}),
|
||||
);
|
||||
expectMockArgFields(sendMediaFeishuMock, "media send params", {
|
||||
mediaUrl: "https://example.com/a.png",
|
||||
});
|
||||
});
|
||||
|
||||
it("suppresses duplicate final text while still sending media", async () => {
|
||||
@@ -561,17 +611,13 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
);
|
||||
|
||||
expect(sendMessageFeishuMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMessageFeishuMock).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
text: "plain final",
|
||||
}),
|
||||
);
|
||||
expectLastMockArgFields(sendMessageFeishuMock, "message send params", {
|
||||
text: "plain final",
|
||||
});
|
||||
expect(sendMediaFeishuMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMediaFeishuMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
mediaUrl: "https://example.com/a.png",
|
||||
}),
|
||||
);
|
||||
expectMockArgFields(sendMediaFeishuMock, "media send params", {
|
||||
mediaUrl: "https://example.com/a.png",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps distinct non-streaming final payloads", async () => {
|
||||
@@ -580,13 +626,16 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
await options.deliver({ text: "actual answer body" }, { kind: "final" });
|
||||
|
||||
expect(sendMessageFeishuMock).toHaveBeenCalledTimes(2);
|
||||
expect(sendMessageFeishuMock).toHaveBeenNthCalledWith(
|
||||
expectMockArgFields(sendMessageFeishuMock, "first message send params", {
|
||||
text: "notice header",
|
||||
});
|
||||
expectMockArgFields(
|
||||
sendMessageFeishuMock,
|
||||
"second message send params",
|
||||
{
|
||||
text: "actual answer body",
|
||||
},
|
||||
1,
|
||||
expect.objectContaining({ text: "notice header" }),
|
||||
);
|
||||
expect(sendMessageFeishuMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.objectContaining({ text: "actual answer body" }),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -707,12 +756,10 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
await options.deliver({ mediaUrl: "https://example.com/a.png" }, { kind: "final" });
|
||||
|
||||
expect(sendMediaFeishuMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMediaFeishuMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: "oc_chat",
|
||||
mediaUrl: "https://example.com/a.png",
|
||||
}),
|
||||
);
|
||||
expectMockArgFields(sendMediaFeishuMock, "media send params", {
|
||||
to: "oc_chat",
|
||||
mediaUrl: "https://example.com/a.png",
|
||||
});
|
||||
expect(sendMessageFeishuMock).not.toHaveBeenCalled();
|
||||
expect(sendMarkdownCardFeishuMock).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -724,12 +771,10 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
{ kind: "final" },
|
||||
);
|
||||
|
||||
expect(sendMediaFeishuMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
mediaUrl: "https://example.com/reply.mp3",
|
||||
audioAsVoice: true,
|
||||
}),
|
||||
);
|
||||
expectMockArgFields(sendMediaFeishuMock, "media send params", {
|
||||
mediaUrl: "https://example.com/reply.mp3",
|
||||
audioAsVoice: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("suppresses duplicate text when final replies send voice media", async () => {
|
||||
@@ -746,12 +791,10 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
expect(sendMessageFeishuMock).not.toHaveBeenCalled();
|
||||
expect(sendStructuredCardFeishuMock).not.toHaveBeenCalled();
|
||||
expect(sendMediaFeishuMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMediaFeishuMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
mediaUrl: "https://example.com/reply.mp3",
|
||||
audioAsVoice: true,
|
||||
}),
|
||||
);
|
||||
expectMockArgFields(sendMediaFeishuMock, "media send params", {
|
||||
mediaUrl: "https://example.com/reply.mp3",
|
||||
audioAsVoice: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("sends skipped voice text when final voice media degrades to a file attachment", async () => {
|
||||
@@ -771,18 +814,14 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
);
|
||||
|
||||
expect(sendMediaFeishuMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMediaFeishuMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
mediaUrl: "https://example.com/reply.mp3",
|
||||
audioAsVoice: true,
|
||||
}),
|
||||
);
|
||||
expectMockArgFields(sendMediaFeishuMock, "media send params", {
|
||||
mediaUrl: "https://example.com/reply.mp3",
|
||||
audioAsVoice: true,
|
||||
});
|
||||
expect(sendMessageFeishuMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMessageFeishuMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
text: "spoken reply",
|
||||
}),
|
||||
);
|
||||
expectMockArgFields(sendMessageFeishuMock, "message send params", {
|
||||
text: "spoken reply",
|
||||
});
|
||||
});
|
||||
|
||||
it("suppresses duplicate text for native voice media without audioAsVoice", async () => {
|
||||
@@ -797,11 +836,9 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
|
||||
expect(sendMessageFeishuMock).not.toHaveBeenCalled();
|
||||
expect(sendMediaFeishuMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMediaFeishuMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
mediaUrl: "https://example.com/reply.opus?download=1",
|
||||
}),
|
||||
);
|
||||
expectMockArgFields(sendMediaFeishuMock, "media send params", {
|
||||
mediaUrl: "https://example.com/reply.opus?download=1",
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves captions for regular audio attachments", async () => {
|
||||
@@ -815,17 +852,13 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
);
|
||||
|
||||
expect(sendMessageFeishuMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMessageFeishuMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
text: "caption text",
|
||||
}),
|
||||
);
|
||||
expectMockArgFields(sendMessageFeishuMock, "message send params", {
|
||||
text: "caption text",
|
||||
});
|
||||
expect(sendMediaFeishuMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMediaFeishuMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
mediaUrl: "https://example.com/song.mp3",
|
||||
}),
|
||||
);
|
||||
expectMockArgFields(sendMediaFeishuMock, "media send params", {
|
||||
mediaUrl: "https://example.com/song.mp3",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps skipped voice text in the upload failure fallback", async () => {
|
||||
@@ -842,11 +875,9 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
);
|
||||
|
||||
expect(sendMessageFeishuMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMessageFeishuMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
text: "spoken reply\n\n📎 https://example.com/reply.mp3",
|
||||
}),
|
||||
);
|
||||
expectMockArgFields(sendMessageFeishuMock, "message send params", {
|
||||
text: "spoken reply\n\n📎 https://example.com/reply.mp3",
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to legacy mediaUrl when mediaUrls is an empty array", async () => {
|
||||
@@ -858,11 +889,9 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
|
||||
expect(sendMessageFeishuMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMediaFeishuMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMediaFeishuMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
mediaUrl: "https://example.com/a.png",
|
||||
}),
|
||||
);
|
||||
expectMockArgFields(sendMediaFeishuMock, "media send params", {
|
||||
mediaUrl: "https://example.com/a.png",
|
||||
});
|
||||
});
|
||||
|
||||
it("sends attachments after streaming final markdown replies", async () => {
|
||||
@@ -879,11 +908,9 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
expect(streamingInstances[0].start).toHaveBeenCalledTimes(1);
|
||||
expect(streamingInstances[0].close).toHaveBeenCalledTimes(1);
|
||||
expect(sendMediaFeishuMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMediaFeishuMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
mediaUrl: "https://example.com/a.png",
|
||||
}),
|
||||
);
|
||||
expectMockArgFields(sendMediaFeishuMock, "media send params", {
|
||||
mediaUrl: "https://example.com/a.png",
|
||||
});
|
||||
});
|
||||
|
||||
it("passes replyInThread to sendMessageFeishu for plain text", async () => {
|
||||
@@ -893,12 +920,10 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
});
|
||||
await options.deliver({ text: "plain text" }, { kind: "final" });
|
||||
|
||||
expect(sendMessageFeishuMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
replyToMessageId: "om_msg",
|
||||
replyInThread: true,
|
||||
}),
|
||||
);
|
||||
expectMockArgFields(sendMessageFeishuMock, "message send params", {
|
||||
replyToMessageId: "om_msg",
|
||||
replyInThread: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("passes replyInThread to sendStructuredCardFeishu for card text", async () => {
|
||||
@@ -919,12 +944,10 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
});
|
||||
await options.deliver({ text: "card text" }, { kind: "final" });
|
||||
|
||||
expect(sendStructuredCardFeishuMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
replyToMessageId: "om_msg",
|
||||
replyInThread: true,
|
||||
}),
|
||||
);
|
||||
expectMockArgFields(sendStructuredCardFeishuMock, "structured card params", {
|
||||
replyToMessageId: "om_msg",
|
||||
replyInThread: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("streams reasoning content as blockquote before answer", async () => {
|
||||
@@ -1074,16 +1097,12 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
await options.deliver({ text: "```ts\nconst x = 1\n```" }, { kind: "final" });
|
||||
|
||||
expect(streamingInstances).toHaveLength(1);
|
||||
expect(streamingInstances[0].start).toHaveBeenCalledWith(
|
||||
"oc_chat",
|
||||
"chat_id",
|
||||
expect.objectContaining({
|
||||
replyToMessageId: "om_msg",
|
||||
replyInThread: true,
|
||||
header: { title: "agent", template: "blue" },
|
||||
note: "Agent: agent",
|
||||
}),
|
||||
);
|
||||
expectStreamingStartOptions(0, {
|
||||
replyToMessageId: "om_msg",
|
||||
replyInThread: true,
|
||||
header: { title: "agent", template: "blue" },
|
||||
note: "Agent: agent",
|
||||
});
|
||||
});
|
||||
|
||||
it("uses streaming cards for thread replies and keeps topic metadata", async () => {
|
||||
@@ -1097,15 +1116,11 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
await options.deliver({ text: "```ts\nconst x = 1\n```" }, { kind: "final" });
|
||||
|
||||
expect(streamingInstances).toHaveLength(1);
|
||||
expect(streamingInstances[0].start).toHaveBeenCalledWith(
|
||||
"oc_chat",
|
||||
"chat_id",
|
||||
expect.objectContaining({
|
||||
replyToMessageId: "om_msg",
|
||||
replyInThread: true,
|
||||
rootId: "om_root_topic",
|
||||
}),
|
||||
);
|
||||
expectStreamingStartOptions(0, {
|
||||
replyToMessageId: "om_msg",
|
||||
replyInThread: true,
|
||||
rootId: "om_root_topic",
|
||||
});
|
||||
expect(sendStructuredCardFeishuMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -1128,13 +1143,9 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
await options.deliver({ text: "streamed card" }, { kind: "final" });
|
||||
await options.onIdle?.();
|
||||
|
||||
expect(streamingInstances[0].start).toHaveBeenCalledWith(
|
||||
"oc_chat",
|
||||
"chat_id",
|
||||
expect.objectContaining({
|
||||
header: undefined,
|
||||
}),
|
||||
);
|
||||
expectStreamingStartOptions(0, {
|
||||
header: undefined,
|
||||
});
|
||||
|
||||
resolveFeishuAccountMock.mockReturnValue({
|
||||
accountId: "main",
|
||||
@@ -1153,11 +1164,9 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
});
|
||||
await staticOptions.deliver({ text: "static card" }, { kind: "final" });
|
||||
|
||||
expect(sendStructuredCardFeishuMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
header: undefined,
|
||||
}),
|
||||
);
|
||||
expectLastMockArgFields(sendStructuredCardFeishuMock, "structured card params", {
|
||||
header: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("shows shared transient tool status on streaming cards but omits it from the final close", async () => {
|
||||
@@ -1180,10 +1189,8 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
result.replyOptions.onPartialReply?.({ text: "final answer" });
|
||||
await options.onIdle?.();
|
||||
|
||||
const updateTexts = streamingInstances[0].update.mock.calls.map((call: unknown[]) =>
|
||||
typeof call[0] === "string" ? call[0] : "",
|
||||
);
|
||||
expect(updateTexts).toEqual(expect.arrayContaining([expect.stringContaining("🔎 Web Search")]));
|
||||
const updateTexts = streamingUpdateTexts();
|
||||
expect(updateTexts.some((text) => text.includes("🔎 Web Search"))).toBe(true);
|
||||
expect(streamingInstances[0].close).toHaveBeenCalledWith("final answer", {
|
||||
note: "Agent: agent",
|
||||
});
|
||||
@@ -1213,14 +1220,10 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
result.replyOptions.onPartialReply?.({ text: "final answer" });
|
||||
await options.onIdle?.();
|
||||
|
||||
const updateTexts = streamingInstances[0].update.mock.calls.map((call: unknown[]) =>
|
||||
typeof call[0] === "string" ? call[0] : "",
|
||||
);
|
||||
expect(updateTexts).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.stringContaining("🛠️ run tests, `pnpm test -- --watch=false`"),
|
||||
]),
|
||||
);
|
||||
const updateTexts = streamingUpdateTexts();
|
||||
expect(
|
||||
updateTexts.some((text) => text.includes("🛠️ run tests, `pnpm test -- --watch=false`")),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("omits message-like tools from streaming card status", async () => {
|
||||
@@ -1243,10 +1246,8 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
result.replyOptions.onPartialReply?.({ text: "final answer" });
|
||||
await options.onIdle?.();
|
||||
|
||||
const updateTexts = streamingInstances[0].update.mock.calls.map((call: unknown[]) =>
|
||||
typeof call[0] === "string" ? call[0] : "",
|
||||
);
|
||||
expect(updateTexts).not.toEqual(expect.arrayContaining([expect.stringContaining("Message")]));
|
||||
const updateTexts = streamingUpdateTexts();
|
||||
expect(updateTexts.some((text) => text.includes("Message"))).toBe(false);
|
||||
});
|
||||
|
||||
it("does not suppress a later final after error closeout", async () => {
|
||||
@@ -1366,12 +1367,10 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
});
|
||||
await options.deliver({ mediaUrl: "https://example.com/a.png" }, { kind: "final" });
|
||||
|
||||
expect(sendMediaFeishuMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
replyToMessageId: "om_msg",
|
||||
replyInThread: true,
|
||||
}),
|
||||
);
|
||||
expectMockArgFields(sendMediaFeishuMock, "media send params", {
|
||||
replyToMessageId: "om_msg",
|
||||
replyInThread: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("backs off streaming retries after start() throws (HTTP 400)", async () => {
|
||||
@@ -1406,7 +1405,12 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
|
||||
// Wait for the async error to propagate
|
||||
await vi.waitFor(() => {
|
||||
expect(errorMock).toHaveBeenCalledWith(expect.stringContaining("streaming start failed"));
|
||||
expect(
|
||||
errorMock.mock.calls.some(
|
||||
([message]) =>
|
||||
typeof message === "string" && message.includes("streaming start failed"),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
expect(streamingInstances).toHaveLength(1);
|
||||
expect(sendStructuredCardFeishuMock).toHaveBeenCalledTimes(1);
|
||||
|
||||
Reference in New Issue
Block a user