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