test(telegram): trim setup resets and table-drive edit fallback cases
This commit is contained in:
@@ -154,8 +154,8 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("blocks callback_query when inline buttons are allowlist-only and sender not authorized", async () => {
|
||||
onSpy.mockReset();
|
||||
replySpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
replySpy.mockClear();
|
||||
|
||||
createTelegramBot({
|
||||
token: "tok",
|
||||
@@ -194,8 +194,8 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("edits commands list for pagination callbacks", async () => {
|
||||
onSpy.mockReset();
|
||||
listSkillCommandsForAgents.mockReset();
|
||||
onSpy.mockClear();
|
||||
listSkillCommandsForAgents.mockClear();
|
||||
|
||||
createTelegramBot({ token: "tok" });
|
||||
const callbackHandler = onSpy.mock.calls.find((call) => call[0] === "callback_query")?.[1] as (
|
||||
@@ -235,8 +235,8 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("blocks pagination callbacks when allowlist rejects sender", async () => {
|
||||
onSpy.mockReset();
|
||||
editMessageTextSpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
editMessageTextSpy.mockClear();
|
||||
|
||||
createTelegramBot({
|
||||
token: "tok",
|
||||
@@ -275,8 +275,8 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("includes sender identity in group envelope headers", async () => {
|
||||
onSpy.mockReset();
|
||||
replySpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
replySpy.mockClear();
|
||||
|
||||
loadConfig.mockReturnValue({
|
||||
agents: {
|
||||
@@ -326,9 +326,9 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("uses quote text when a Telegram partial reply is received", async () => {
|
||||
onSpy.mockReset();
|
||||
sendMessageSpy.mockReset();
|
||||
replySpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
sendMessageSpy.mockClear();
|
||||
replySpy.mockClear();
|
||||
|
||||
createTelegramBot({ token: "tok" });
|
||||
const handler = getOnHandler("message") as (ctx: Record<string, unknown>) => Promise<void>;
|
||||
@@ -361,9 +361,9 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("handles quote-only replies without reply metadata", async () => {
|
||||
onSpy.mockReset();
|
||||
sendMessageSpy.mockReset();
|
||||
replySpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
sendMessageSpy.mockClear();
|
||||
replySpy.mockClear();
|
||||
|
||||
createTelegramBot({ token: "tok" });
|
||||
const handler = getOnHandler("message") as (ctx: Record<string, unknown>) => Promise<void>;
|
||||
@@ -391,9 +391,9 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("uses external_reply quote text for partial replies", async () => {
|
||||
onSpy.mockReset();
|
||||
sendMessageSpy.mockReset();
|
||||
replySpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
sendMessageSpy.mockClear();
|
||||
replySpy.mockClear();
|
||||
|
||||
createTelegramBot({ token: "tok" });
|
||||
const handler = getOnHandler("message") as (ctx: Record<string, unknown>) => Promise<void>;
|
||||
@@ -426,8 +426,8 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("accepts group replies to the bot without explicit mention when requireMention is enabled", async () => {
|
||||
onSpy.mockReset();
|
||||
replySpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
replySpy.mockClear();
|
||||
loadConfig.mockReturnValue({
|
||||
channels: {
|
||||
telegram: { groups: { "*": { requireMention: true } } },
|
||||
@@ -458,8 +458,8 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("inherits group allowlist + requireMention in topics", async () => {
|
||||
onSpy.mockReset();
|
||||
replySpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
replySpy.mockClear();
|
||||
loadConfig.mockReturnValue({
|
||||
channels: {
|
||||
telegram: {
|
||||
@@ -501,8 +501,8 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("prefers topic allowFrom over group allowFrom", async () => {
|
||||
onSpy.mockReset();
|
||||
replySpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
replySpy.mockClear();
|
||||
loadConfig.mockReturnValue({
|
||||
channels: {
|
||||
telegram: {
|
||||
@@ -543,8 +543,8 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("allows group messages for per-group groupPolicy open override (global groupPolicy allowlist)", async () => {
|
||||
onSpy.mockReset();
|
||||
replySpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
replySpy.mockClear();
|
||||
loadConfig.mockReturnValue({
|
||||
channels: {
|
||||
telegram: {
|
||||
@@ -578,8 +578,8 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("blocks control commands from unauthorized senders in per-group open groups", async () => {
|
||||
onSpy.mockReset();
|
||||
replySpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
replySpy.mockClear();
|
||||
loadConfig.mockReturnValue({
|
||||
channels: {
|
||||
telegram: {
|
||||
@@ -612,10 +612,10 @@ describe("createTelegramBot", () => {
|
||||
expect(replySpy).not.toHaveBeenCalled();
|
||||
});
|
||||
it("sets command target session key for dm topic commands", async () => {
|
||||
onSpy.mockReset();
|
||||
sendMessageSpy.mockReset();
|
||||
commandSpy.mockReset();
|
||||
replySpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
sendMessageSpy.mockClear();
|
||||
commandSpy.mockClear();
|
||||
replySpy.mockClear();
|
||||
replySpy.mockResolvedValue({ text: "response" });
|
||||
|
||||
loadConfig.mockReturnValue({
|
||||
@@ -654,10 +654,10 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("allows native DM commands for paired users", async () => {
|
||||
onSpy.mockReset();
|
||||
sendMessageSpy.mockReset();
|
||||
commandSpy.mockReset();
|
||||
replySpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
sendMessageSpy.mockClear();
|
||||
commandSpy.mockClear();
|
||||
replySpy.mockClear();
|
||||
replySpy.mockResolvedValue({ text: "response" });
|
||||
|
||||
loadConfig.mockReturnValue({
|
||||
@@ -698,10 +698,10 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("blocks native DM commands for unpaired users", async () => {
|
||||
onSpy.mockReset();
|
||||
sendMessageSpy.mockReset();
|
||||
commandSpy.mockReset();
|
||||
replySpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
sendMessageSpy.mockClear();
|
||||
commandSpy.mockClear();
|
||||
replySpy.mockClear();
|
||||
|
||||
loadConfig.mockReturnValue({
|
||||
commands: { native: true },
|
||||
@@ -740,15 +740,15 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("registers message_reaction handler", () => {
|
||||
onSpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
createTelegramBot({ token: "tok" });
|
||||
const reactionHandler = onSpy.mock.calls.find((call) => call[0] === "message_reaction");
|
||||
expect(reactionHandler).toBeDefined();
|
||||
});
|
||||
|
||||
it("enqueues system event for reaction", async () => {
|
||||
onSpy.mockReset();
|
||||
enqueueSystemEventSpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
enqueueSystemEventSpy.mockClear();
|
||||
|
||||
loadConfig.mockReturnValue({
|
||||
channels: {
|
||||
@@ -783,8 +783,8 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("skips reaction when reactionNotifications is off", async () => {
|
||||
onSpy.mockReset();
|
||||
enqueueSystemEventSpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
enqueueSystemEventSpy.mockClear();
|
||||
wasSentByBot.mockReturnValue(true);
|
||||
|
||||
loadConfig.mockReturnValue({
|
||||
@@ -814,8 +814,8 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("defaults reactionNotifications to own", async () => {
|
||||
onSpy.mockReset();
|
||||
enqueueSystemEventSpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
enqueueSystemEventSpy.mockClear();
|
||||
wasSentByBot.mockReturnValue(true);
|
||||
|
||||
loadConfig.mockReturnValue({
|
||||
@@ -845,8 +845,8 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("allows reaction in all mode regardless of message sender", async () => {
|
||||
onSpy.mockReset();
|
||||
enqueueSystemEventSpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
enqueueSystemEventSpy.mockClear();
|
||||
wasSentByBot.mockReturnValue(false);
|
||||
|
||||
loadConfig.mockReturnValue({
|
||||
@@ -880,8 +880,8 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("skips reaction in own mode when message is not sent by bot", async () => {
|
||||
onSpy.mockReset();
|
||||
enqueueSystemEventSpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
enqueueSystemEventSpy.mockClear();
|
||||
wasSentByBot.mockReturnValue(false);
|
||||
|
||||
loadConfig.mockReturnValue({
|
||||
@@ -911,8 +911,8 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("allows reaction in own mode when message is sent by bot", async () => {
|
||||
onSpy.mockReset();
|
||||
enqueueSystemEventSpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
enqueueSystemEventSpy.mockClear();
|
||||
wasSentByBot.mockReturnValue(true);
|
||||
|
||||
loadConfig.mockReturnValue({
|
||||
@@ -942,8 +942,8 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("skips reaction from bot users", async () => {
|
||||
onSpy.mockReset();
|
||||
enqueueSystemEventSpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
enqueueSystemEventSpy.mockClear();
|
||||
wasSentByBot.mockReturnValue(true);
|
||||
|
||||
loadConfig.mockReturnValue({
|
||||
@@ -973,8 +973,8 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("skips reaction removal (only processes added reactions)", async () => {
|
||||
onSpy.mockReset();
|
||||
enqueueSystemEventSpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
enqueueSystemEventSpy.mockClear();
|
||||
|
||||
loadConfig.mockReturnValue({
|
||||
channels: {
|
||||
@@ -1003,8 +1003,8 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("enqueues one event per added emoji reaction", async () => {
|
||||
onSpy.mockReset();
|
||||
enqueueSystemEventSpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
enqueueSystemEventSpy.mockClear();
|
||||
|
||||
loadConfig.mockReturnValue({
|
||||
channels: {
|
||||
@@ -1041,8 +1041,8 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("routes forum group reactions to the general topic (thread id not available on reactions)", async () => {
|
||||
onSpy.mockReset();
|
||||
enqueueSystemEventSpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
enqueueSystemEventSpy.mockClear();
|
||||
|
||||
loadConfig.mockReturnValue({
|
||||
channels: {
|
||||
@@ -1080,8 +1080,8 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("uses correct session key for forum group reactions in general topic", async () => {
|
||||
onSpy.mockReset();
|
||||
enqueueSystemEventSpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
enqueueSystemEventSpy.mockClear();
|
||||
|
||||
loadConfig.mockReturnValue({
|
||||
channels: {
|
||||
@@ -1118,8 +1118,8 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("uses correct session key for regular group reactions without topic", async () => {
|
||||
onSpy.mockReset();
|
||||
enqueueSystemEventSpy.mockReset();
|
||||
onSpy.mockClear();
|
||||
enqueueSystemEventSpy.mockClear();
|
||||
|
||||
loadConfig.mockReturnValue({
|
||||
channels: {
|
||||
|
||||
@@ -1271,88 +1271,70 @@ describe("shared send behaviors", () => {
|
||||
});
|
||||
|
||||
describe("editMessageTelegram", () => {
|
||||
it("handles button payload + parse fallback behavior", async () => {
|
||||
const cases: Array<{
|
||||
name: string;
|
||||
setup: () => {
|
||||
text: string;
|
||||
buttons: Parameters<typeof buildInlineKeyboard>[0];
|
||||
};
|
||||
expectedCalls: number;
|
||||
firstExpectNoReplyMarkup?: boolean;
|
||||
firstExpectReplyMarkup?: Record<string, unknown>;
|
||||
secondExpectReplyMarkup?: Record<string, unknown>;
|
||||
}> = [
|
||||
{
|
||||
name: "buttons undefined keeps existing keyboard",
|
||||
setup: () => {
|
||||
botApi.editMessageText.mockResolvedValue({ message_id: 1, chat: { id: "123" } });
|
||||
return { text: "hi", buttons: undefined };
|
||||
},
|
||||
expectedCalls: 1,
|
||||
firstExpectNoReplyMarkup: true,
|
||||
},
|
||||
{
|
||||
name: "buttons empty clears keyboard",
|
||||
setup: () => {
|
||||
botApi.editMessageText.mockResolvedValue({ message_id: 1, chat: { id: "123" } });
|
||||
return { text: "hi", buttons: [] };
|
||||
},
|
||||
expectedCalls: 1,
|
||||
firstExpectReplyMarkup: { inline_keyboard: [] },
|
||||
},
|
||||
{
|
||||
name: "parse error fallback preserves cleared keyboard",
|
||||
setup: () => {
|
||||
botApi.editMessageText
|
||||
.mockRejectedValueOnce(new Error("400: Bad Request: can't parse entities"))
|
||||
.mockResolvedValueOnce({ message_id: 1, chat: { id: "123" } });
|
||||
return { text: "<bad> html", buttons: [] };
|
||||
},
|
||||
expectedCalls: 2,
|
||||
firstExpectReplyMarkup: { inline_keyboard: [] },
|
||||
secondExpectReplyMarkup: { inline_keyboard: [] },
|
||||
},
|
||||
];
|
||||
it.each([
|
||||
{
|
||||
name: "buttons undefined keeps existing keyboard",
|
||||
text: "hi",
|
||||
buttons: undefined as Parameters<typeof buildInlineKeyboard>[0],
|
||||
expectedCalls: 1,
|
||||
firstExpectNoReplyMarkup: true,
|
||||
parseFallback: false,
|
||||
},
|
||||
{
|
||||
name: "buttons empty clears keyboard",
|
||||
text: "hi",
|
||||
buttons: [] as Parameters<typeof buildInlineKeyboard>[0],
|
||||
expectedCalls: 1,
|
||||
firstExpectReplyMarkup: { inline_keyboard: [] } as Record<string, unknown>,
|
||||
parseFallback: false,
|
||||
},
|
||||
{
|
||||
name: "parse error fallback preserves cleared keyboard",
|
||||
text: "<bad> html",
|
||||
buttons: [] as Parameters<typeof buildInlineKeyboard>[0],
|
||||
expectedCalls: 2,
|
||||
firstExpectReplyMarkup: { inline_keyboard: [] } as Record<string, unknown>,
|
||||
secondExpectReplyMarkup: { inline_keyboard: [] } as Record<string, unknown>,
|
||||
parseFallback: true,
|
||||
},
|
||||
])("$name", async (testCase) => {
|
||||
if (testCase.parseFallback) {
|
||||
botApi.editMessageText
|
||||
.mockRejectedValueOnce(new Error("400: Bad Request: can't parse entities"))
|
||||
.mockResolvedValueOnce({ message_id: 1, chat: { id: "123" } });
|
||||
} else {
|
||||
botApi.editMessageText.mockResolvedValue({ message_id: 1, chat: { id: "123" } });
|
||||
}
|
||||
|
||||
for (const testCase of cases) {
|
||||
botApi.editMessageText.mockReset();
|
||||
botCtorSpy.mockReset();
|
||||
const input = testCase.setup();
|
||||
await editMessageTelegram("123", 1, testCase.text, {
|
||||
token: "tok",
|
||||
cfg: {},
|
||||
buttons: testCase.buttons ? testCase.buttons.map((row) => [...row]) : testCase.buttons,
|
||||
});
|
||||
|
||||
await editMessageTelegram("123", 1, input.text, {
|
||||
token: "tok",
|
||||
cfg: {},
|
||||
buttons: input.buttons ? input.buttons.map((row) => [...row]) : input.buttons,
|
||||
});
|
||||
expect(botCtorSpy, testCase.name).toHaveBeenCalledTimes(1);
|
||||
expect(botCtorSpy.mock.calls[0]?.[0], testCase.name).toBe("tok");
|
||||
expect(botApi.editMessageText, testCase.name).toHaveBeenCalledTimes(testCase.expectedCalls);
|
||||
|
||||
expect(botCtorSpy, testCase.name).toHaveBeenCalledTimes(1);
|
||||
expect(botCtorSpy.mock.calls[0]?.[0], testCase.name).toBe("tok");
|
||||
expect(botApi.editMessageText, testCase.name).toHaveBeenCalledTimes(testCase.expectedCalls);
|
||||
const firstParams = (botApi.editMessageText.mock.calls[0] ?? [])[3] as Record<string, unknown>;
|
||||
expect(firstParams, testCase.name).toEqual(expect.objectContaining({ parse_mode: "HTML" }));
|
||||
if ("firstExpectNoReplyMarkup" in testCase && testCase.firstExpectNoReplyMarkup) {
|
||||
expect(firstParams, testCase.name).not.toHaveProperty("reply_markup");
|
||||
}
|
||||
if ("firstExpectReplyMarkup" in testCase && testCase.firstExpectReplyMarkup) {
|
||||
expect(firstParams, testCase.name).toEqual(
|
||||
expect.objectContaining({ reply_markup: testCase.firstExpectReplyMarkup }),
|
||||
);
|
||||
}
|
||||
|
||||
const firstParams = (botApi.editMessageText.mock.calls[0] ?? [])[3] as Record<
|
||||
if ("secondExpectReplyMarkup" in testCase && testCase.secondExpectReplyMarkup) {
|
||||
const secondParams = (botApi.editMessageText.mock.calls[1] ?? [])[3] as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
expect(firstParams, testCase.name).toEqual(expect.objectContaining({ parse_mode: "HTML" }));
|
||||
if ("firstExpectNoReplyMarkup" in testCase && testCase.firstExpectNoReplyMarkup) {
|
||||
expect(firstParams, testCase.name).not.toHaveProperty("reply_markup");
|
||||
}
|
||||
if ("firstExpectReplyMarkup" in testCase && testCase.firstExpectReplyMarkup) {
|
||||
expect(firstParams, testCase.name).toEqual(
|
||||
expect.objectContaining({ reply_markup: testCase.firstExpectReplyMarkup }),
|
||||
);
|
||||
}
|
||||
|
||||
if ("secondExpectReplyMarkup" in testCase && testCase.secondExpectReplyMarkup) {
|
||||
const secondParams = (botApi.editMessageText.mock.calls[1] ?? [])[3] as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
expect(secondParams, testCase.name).toEqual(
|
||||
expect.objectContaining({ reply_markup: testCase.secondExpectReplyMarkup }),
|
||||
);
|
||||
}
|
||||
expect(secondParams, testCase.name).toEqual(
|
||||
expect.objectContaining({ reply_markup: testCase.secondExpectReplyMarkup }),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user