test: merge redundant telegram media path scenarios

This commit is contained in:
Peter Steinberger
2026-02-23 21:57:23 +00:00
parent ca761d6225
commit ecd278b67b

View File

@@ -112,35 +112,69 @@ describe("telegram inbound media", () => {
const INBOUND_MEDIA_TEST_TIMEOUT_MS = process.platform === "win32" ? 120_000 : 90_000; const INBOUND_MEDIA_TEST_TIMEOUT_MS = process.platform === "win32" ? 120_000 : 90_000;
it( it(
"downloads media via file_path (no file.download)", "handles file_path media downloads and missing file_path safely",
async () => { async () => {
const { handler, replySpy, runtimeError } = await createBotHandler(); for (const scenario of [
const fetchSpy = mockTelegramFileDownload({ {
contentType: "image/jpeg", name: "downloads via file_path",
bytes: new Uint8Array([0xff, 0xd8, 0xff, 0x00]), getFile: async () => ({ file_path: "photos/1.jpg" }),
}); setupFetch: () =>
mockTelegramFileDownload({
await handler({ contentType: "image/jpeg",
message: { bytes: new Uint8Array([0xff, 0xd8, 0xff, 0x00]),
message_id: 1, }),
chat: { id: 1234, type: "private" }, assert: (params: {
photo: [{ file_id: "fid" }], fetchSpy: ReturnType<typeof vi.spyOn>;
date: 1736380800, // 2025-01-09T00:00:00Z replySpy: ReturnType<typeof vi.fn>;
runtimeError: ReturnType<typeof vi.fn>;
}) => {
expect(params.runtimeError).not.toHaveBeenCalled();
expect(params.fetchSpy).toHaveBeenCalledWith(
"https://api.telegram.org/file/bottok/photos/1.jpg",
expect.objectContaining({ redirect: "manual" }),
);
expect(params.replySpy).toHaveBeenCalledTimes(1);
const payload = params.replySpy.mock.calls[0][0];
expect(payload.Body).toContain("<media:image>");
},
}, },
me: { username: "openclaw_bot" }, {
getFile: async () => ({ file_path: "photos/1.jpg" }), name: "skips when file_path is missing",
}); getFile: async () => ({}),
setupFetch: () => vi.spyOn(globalThis, "fetch"),
assert: (params: {
fetchSpy: ReturnType<typeof vi.spyOn>;
replySpy: ReturnType<typeof vi.fn>;
runtimeError: ReturnType<typeof vi.fn>;
}) => {
expect(params.fetchSpy).not.toHaveBeenCalled();
expect(params.replySpy).not.toHaveBeenCalled();
expect(params.runtimeError).not.toHaveBeenCalled();
},
},
]) {
const runtimeLog = vi.fn();
const runtimeError = vi.fn();
const { handler, replySpy } = await createBotHandlerWithOptions({
runtimeLog,
runtimeError,
});
const fetchSpy = scenario.setupFetch();
expect(runtimeError).not.toHaveBeenCalled(); await handler({
expect(fetchSpy).toHaveBeenCalledWith( message: {
"https://api.telegram.org/file/bottok/photos/1.jpg", message_id: 1,
expect.objectContaining({ redirect: "manual" }), chat: { id: 1234, type: "private" },
); photo: [{ file_id: "fid" }],
expect(replySpy).toHaveBeenCalledTimes(1); date: 1736380800, // 2025-01-09T00:00:00Z
const payload = replySpy.mock.calls[0][0]; },
expect(payload.Body).toContain("<media:image>"); me: { username: "openclaw_bot" },
getFile: scenario.getFile,
});
fetchSpy.mockRestore(); scenario.assert({ fetchSpy, replySpy, runtimeError });
fetchSpy.mockRestore();
}
}, },
INBOUND_MEDIA_TEST_TIMEOUT_MS, INBOUND_MEDIA_TEST_TIMEOUT_MS,
); );
@@ -184,32 +218,6 @@ describe("telegram inbound media", () => {
globalFetchSpy.mockRestore(); globalFetchSpy.mockRestore();
}); });
it("handles missing file_path from getFile without crashing", async () => {
const runtimeLog = vi.fn();
const runtimeError = vi.fn();
const { handler, replySpy } = await createBotHandlerWithOptions({
runtimeLog,
runtimeError,
});
const fetchSpy = vi.spyOn(globalThis, "fetch");
await handler({
message: {
message_id: 3,
chat: { id: 1234, type: "private" },
photo: [{ file_id: "fid" }],
},
me: { username: "openclaw_bot" },
getFile: async () => ({}),
});
expect(fetchSpy).not.toHaveBeenCalled();
expect(replySpy).not.toHaveBeenCalled();
expect(runtimeError).not.toHaveBeenCalled();
fetchSpy.mockRestore();
});
it("captures pin and venue location payload fields", async () => { it("captures pin and venue location payload fields", async () => {
const { handler, replySpy } = await createBotHandler(); const { handler, replySpy } = await createBotHandler();
@@ -279,99 +287,87 @@ describe("telegram media groups", () => {
const MEDIA_GROUP_FLUSH_MS = TELEGRAM_TEST_TIMINGS.mediaGroupFlushMs + 60; const MEDIA_GROUP_FLUSH_MS = TELEGRAM_TEST_TIMINGS.mediaGroupFlushMs + 60;
it( it(
"buffers messages with same media_group_id and processes them together", "handles same-group buffering and separate-group independence",
async () => { async () => {
const runtimeError = vi.fn(); for (const scenario of [
const { handler, replySpy } = await createBotHandlerWithOptions({ runtimeError }); {
const fetchSpy = mockTelegramPngDownload(); messages: [
const first = handler({ {
message: { chat: { id: 42, type: "private" as const },
chat: { id: 42, type: "private" }, message_id: 1,
message_id: 1, caption: "Here are my photos",
caption: "Here are my photos", date: 1736380800,
date: 1736380800, media_group_id: "album123",
media_group_id: "album123", photo: [{ file_id: "photo1" }],
photo: [{ file_id: "photo1" }], filePath: "photos/photo1.jpg",
},
{
chat: { id: 42, type: "private" as const },
message_id: 2,
date: 1736380801,
media_group_id: "album123",
photo: [{ file_id: "photo2" }],
filePath: "photos/photo2.jpg",
},
],
expectedReplyCount: 1,
assert: (replySpy: ReturnType<typeof vi.fn>) => {
const payload = replySpy.mock.calls[0]?.[0];
expect(payload?.Body).toContain("Here are my photos");
expect(payload?.MediaPaths).toHaveLength(2);
},
}, },
me: { username: "openclaw_bot" }, {
getFile: async () => ({ file_path: "photos/photo1.jpg" }), messages: [
}); {
chat: { id: 42, type: "private" as const },
const second = handler({ message_id: 11,
message: { caption: "Album A",
chat: { id: 42, type: "private" }, date: 1736380800,
message_id: 2, media_group_id: "albumA",
date: 1736380801, photo: [{ file_id: "photoA1" }],
media_group_id: "album123", filePath: "photos/photoA1.jpg",
photo: [{ file_id: "photo2" }], },
{
chat: { id: 42, type: "private" as const },
message_id: 12,
caption: "Album B",
date: 1736380801,
media_group_id: "albumB",
photo: [{ file_id: "photoB1" }],
filePath: "photos/photoB1.jpg",
},
],
expectedReplyCount: 2,
assert: () => {},
}, },
me: { username: "openclaw_bot" }, ]) {
getFile: async () => ({ file_path: "photos/photo2.jpg" }), const runtimeError = vi.fn();
}); const { handler, replySpy } = await createBotHandlerWithOptions({ runtimeError });
const fetchSpy = mockTelegramPngDownload();
await first; await Promise.all(
await second; scenario.messages.map((message) =>
handler({
message,
me: { username: "openclaw_bot" },
getFile: async () => ({ file_path: message.filePath }),
}),
),
);
expect(replySpy).not.toHaveBeenCalled(); expect(replySpy).not.toHaveBeenCalled();
await vi.waitFor( await vi.waitFor(
() => { () => {
expect(replySpy).toHaveBeenCalledTimes(1); expect(replySpy).toHaveBeenCalledTimes(scenario.expectedReplyCount);
}, },
{ timeout: MEDIA_GROUP_FLUSH_MS * 2, interval: 10 }, { timeout: MEDIA_GROUP_FLUSH_MS * 2, interval: 10 },
); );
expect(runtimeError).not.toHaveBeenCalled(); expect(runtimeError).not.toHaveBeenCalled();
const payload = replySpy.mock.calls[0][0]; scenario.assert(replySpy);
expect(payload.Body).toContain("Here are my photos"); fetchSpy.mockRestore();
expect(payload.MediaPaths).toHaveLength(2); }
fetchSpy.mockRestore();
},
MEDIA_GROUP_TEST_TIMEOUT_MS,
);
it(
"processes separate media groups independently",
async () => {
const { handler, replySpy } = await createBotHandler();
const fetchSpy = mockTelegramPngDownload();
const first = handler({
message: {
chat: { id: 42, type: "private" },
message_id: 1,
caption: "Album A",
date: 1736380800,
media_group_id: "albumA",
photo: [{ file_id: "photoA1" }],
},
me: { username: "openclaw_bot" },
getFile: async () => ({ file_path: "photos/photoA1.jpg" }),
});
const second = handler({
message: {
chat: { id: 42, type: "private" },
message_id: 2,
caption: "Album B",
date: 1736380801,
media_group_id: "albumB",
photo: [{ file_id: "photoB1" }],
},
me: { username: "openclaw_bot" },
getFile: async () => ({ file_path: "photos/photoB1.jpg" }),
});
await Promise.all([first, second]);
expect(replySpy).not.toHaveBeenCalled();
await vi.waitFor(
() => {
expect(replySpy).toHaveBeenCalledTimes(2);
},
{ timeout: MEDIA_GROUP_FLUSH_MS * 2, interval: 10 },
);
fetchSpy.mockRestore();
}, },
MEDIA_GROUP_TEST_TIMEOUT_MS, MEDIA_GROUP_TEST_TIMEOUT_MS,
); );
@@ -556,53 +552,25 @@ describe("telegram stickers", () => {
); );
it( it(
"skips animated stickers (TGS format)", "skips animated and video sticker formats that cannot be downloaded",
async () => { async () => {
const { handler, replySpy, runtimeError } = await createBotHandler(); for (const scenario of [
const fetchSpy = vi.spyOn(globalThis, "fetch"); {
filePath: "stickers/animated.tgs",
await handler({
message: {
message_id: 101,
chat: { id: 1234, type: "private" },
sticker: { sticker: {
file_id: "animated_sticker_id", file_id: "animated_sticker_id",
file_unique_id: "animated_unique", file_unique_id: "animated_unique",
type: "regular", type: "regular",
width: 512, width: 512,
height: 512, height: 512,
is_animated: true, // TGS format is_animated: true,
is_video: false, is_video: false,
emoji: "😎", emoji: "😎",
set_name: "AnimatedPack", set_name: "AnimatedPack",
}, },
date: 1736380800,
}, },
me: { username: "openclaw_bot" }, {
getFile: async () => ({ file_path: "stickers/animated.tgs" }), filePath: "stickers/video.webm",
});
// Should not attempt to download animated stickers
expect(fetchSpy).not.toHaveBeenCalled();
// Should still process the message (as text-only, no media)
expect(replySpy).not.toHaveBeenCalled(); // No text content, so no reply generated
expect(runtimeError).not.toHaveBeenCalled();
fetchSpy.mockRestore();
},
STICKER_TEST_TIMEOUT_MS,
);
it(
"skips video stickers (WEBM format)",
async () => {
const { handler, replySpy, runtimeError } = await createBotHandler();
const fetchSpy = vi.spyOn(globalThis, "fetch");
await handler({
message: {
message_id: 102,
chat: { id: 1234, type: "private" },
sticker: { sticker: {
file_id: "video_sticker_id", file_id: "video_sticker_id",
file_unique_id: "video_unique", file_unique_id: "video_unique",
@@ -610,22 +578,31 @@ describe("telegram stickers", () => {
width: 512, width: 512,
height: 512, height: 512,
is_animated: false, is_animated: false,
is_video: true, // WEBM format is_video: true,
emoji: "🎬", emoji: "🎬",
set_name: "VideoPack", set_name: "VideoPack",
}, },
date: 1736380800,
}, },
me: { username: "openclaw_bot" }, ]) {
getFile: async () => ({ file_path: "stickers/video.webm" }), const { handler, replySpy, runtimeError } = await createBotHandler();
}); const fetchSpy = vi.spyOn(globalThis, "fetch");
// Should not attempt to download video stickers await handler({
expect(fetchSpy).not.toHaveBeenCalled(); message: {
expect(replySpy).not.toHaveBeenCalled(); message_id: 101,
expect(runtimeError).not.toHaveBeenCalled(); chat: { id: 1234, type: "private" },
sticker: scenario.sticker,
date: 1736380800,
},
me: { username: "openclaw_bot" },
getFile: async () => ({ file_path: scenario.filePath }),
});
fetchSpy.mockRestore(); expect(fetchSpy).not.toHaveBeenCalled();
expect(replySpy).not.toHaveBeenCalled();
expect(runtimeError).not.toHaveBeenCalled();
fetchSpy.mockRestore();
}
}, },
STICKER_TEST_TIMEOUT_MS, STICKER_TEST_TIMEOUT_MS,
); );