

























@@ -5,10 +5,12 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
5566const sendMessageTelegramMock = vi.fn();
77const pinMessageTelegramMock = vi.fn();
8+const reactMessageTelegramMock = vi.fn();
89const sendPollTelegramMock = vi.fn();
9101011vi.mock("./send.js", () => ({
1112pinMessageTelegram: (...args: unknown[]) => pinMessageTelegramMock(...args),
13+reactMessageTelegram: (...args: unknown[]) => reactMessageTelegramMock(...args),
1214sendPollTelegram: (...args: unknown[]) => sendPollTelegramMock(...args),
1315sendMessageTelegram: (...args: unknown[]) => sendMessageTelegramMock(...args),
1416}));
@@ -60,6 +62,7 @@ function callOptionsFromEnd(
6062describe("telegramOutbound", () => {
6163beforeEach(() => {
6264pinMessageTelegramMock.mockReset();
65+reactMessageTelegramMock.mockReset();
6366sendPollTelegramMock.mockReset();
6467sendMessageTelegramMock.mockReset();
6568});
@@ -155,6 +158,151 @@ describe("telegramOutbound", () => {
155158expect(result).toEqual({ channel: "telegram", messageId: "tg-buttons", chatId: "12345" });
156159});
157160161+it("applies reaction-only payloads without sending empty Telegram text", async () => {
162+reactMessageTelegramMock.mockResolvedValueOnce({ ok: true });
163+164+const result = await telegramOutbound.sendPayload!({
165+cfg: {} as never,
166+to: "12345",
167+text: "",
168+replyToId: "777",
169+payload: {
170+channelData: {
171+telegram: {
172+reaction: { emoji: "🔥" },
173+},
174+},
175+},
176+deps: { sendTelegram: sendMessageTelegramMock },
177+});
178+179+expect(reactMessageTelegramMock).toHaveBeenCalledWith("12345", 777, "🔥", {
180+cfg: {},
181+verbose: false,
182+accountId: undefined,
183+gatewayClientScopes: undefined,
184+});
185+expect(sendMessageTelegramMock).not.toHaveBeenCalled();
186+expect(result).toEqual({ channel: "telegram", messageId: "777", chatId: "12345" });
187+});
188+189+it("applies reaction payloads before sending visible text", async () => {
190+reactMessageTelegramMock.mockResolvedValueOnce({ ok: true });
191+sendMessageTelegramMock.mockResolvedValueOnce({ messageId: "tg-text", chatId: "12345" });
192+193+await telegramOutbound.sendPayload!({
194+cfg: {} as never,
195+to: "12345",
196+text: "",
197+accountId: "ops",
198+replyToId: "777",
199+payload: {
200+text: "Done",
201+channelData: {
202+telegram: {
203+reaction: { emoji: "✅" },
204+},
205+},
206+},
207+deps: { sendTelegram: sendMessageTelegramMock },
208+});
209+210+expect(reactMessageTelegramMock).toHaveBeenCalledWith(
211+"12345",
212+777,
213+"✅",
214+expect.objectContaining({ accountId: "ops" }),
215+);
216+expect(sendMessageTelegramMock).toHaveBeenCalledWith(
217+"12345",
218+"Done",
219+expect.objectContaining({ accountId: "ops", replyToMessageId: 777 }),
220+);
221+expect(reactMessageTelegramMock.mock.invocationCallOrder[0]).toBeLessThan(
222+sendMessageTelegramMock.mock.invocationCallOrder[0],
223+);
224+});
225+226+it("rejects text plus reaction payloads without a reply target", async () => {
227+await expect(
228+telegramOutbound.sendPayload!({
229+cfg: {} as never,
230+to: "12345",
231+text: "",
232+payload: {
233+text: "Done",
234+channelData: { telegram: { reaction: { emoji: "🔥" } } },
235+},
236+deps: { sendTelegram: sendMessageTelegramMock },
237+}),
238+).rejects.toThrow("Telegram reaction requires a reply target");
239+240+expect(reactMessageTelegramMock).not.toHaveBeenCalled();
241+expect(sendMessageTelegramMock).not.toHaveBeenCalled();
242+});
243+244+it("rejects text plus reaction payloads when Telegram refuses the emoji", async () => {
245+reactMessageTelegramMock.mockResolvedValueOnce({
246+ok: false,
247+warning: "Reaction unavailable: not-supported",
248+});
249+250+await expect(
251+telegramOutbound.sendPayload!({
252+cfg: {} as never,
253+to: "12345",
254+text: "",
255+replyToId: "777",
256+payload: {
257+text: "Done",
258+channelData: { telegram: { reaction: { emoji: "not-supported" } } },
259+},
260+deps: { sendTelegram: sendMessageTelegramMock },
261+}),
262+).rejects.toThrow("Reaction unavailable: not-supported");
263+264+expect(sendMessageTelegramMock).not.toHaveBeenCalled();
265+});
266+267+it("rejects reaction-only payloads without a reply target", async () => {
268+await expect(
269+telegramOutbound.sendPayload!({
270+cfg: {} as never,
271+to: "12345",
272+text: "",
273+payload: {
274+channelData: { telegram: { reaction: { emoji: "🔥" } } },
275+},
276+deps: { sendTelegram: sendMessageTelegramMock },
277+}),
278+).rejects.toThrow("Telegram reaction requires a reply target");
279+280+expect(reactMessageTelegramMock).not.toHaveBeenCalled();
281+expect(sendMessageTelegramMock).not.toHaveBeenCalled();
282+});
283+284+it("rejects reaction-only payloads when Telegram refuses the emoji", async () => {
285+reactMessageTelegramMock.mockResolvedValueOnce({
286+ok: false,
287+warning: "Reaction unavailable: not-supported",
288+});
289+290+await expect(
291+telegramOutbound.sendPayload!({
292+cfg: {} as never,
293+to: "12345",
294+text: "",
295+replyToId: "777",
296+payload: {
297+channelData: { telegram: { reaction: { emoji: "not-supported" } } },
298+},
299+deps: { sendTelegram: sendMessageTelegramMock },
300+}),
301+).rejects.toThrow("Reaction unavailable: not-supported");
302+303+expect(sendMessageTelegramMock).not.toHaveBeenCalled();
304+});
305+158306it("uses presentation button labels as fallback text for presentation-only payloads", async () => {
159307sendMessageTelegramMock.mockResolvedValueOnce({
160308messageId: "tg-presentation-buttons",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。