Files
pi-telegram/tests/updates.test.ts
T
2026-04-11 11:32:44 +04:00

367 lines
10 KiB
TypeScript

/**
* Regression tests for the Telegram updates domain
* Covers extraction, authorization, flow classification, execution planning, and runtime execution in one suite
*/
import test from "node:test";
import assert from "node:assert/strict";
import {
buildTelegramUpdateExecutionPlan,
buildTelegramUpdateExecutionPlanFromUpdate,
buildTelegramUpdateFlowAction,
collectTelegramReactionEmojis,
executeTelegramUpdate,
executeTelegramUpdatePlan,
extractDeletedTelegramMessageIds,
getAuthorizedTelegramCallbackQuery,
getAuthorizedTelegramMessage,
getTelegramAuthorizationState,
normalizeTelegramReactionEmoji,
} from "../lib/updates.ts";
test("Update helpers normalize emoji reactions and collect emoji-only entries", () => {
assert.equal(normalizeTelegramReactionEmoji("👍️"), "👍");
const emojis = collectTelegramReactionEmojis([
{ type: "emoji", emoji: "👍️" },
{ type: "emoji", emoji: "👎" },
{ type: "custom_emoji" },
]);
assert.deepEqual([...emojis], ["👍", "👎"]);
});
test("Update helpers extract deleted message ids from Telegram update variants", () => {
assert.deepEqual(
extractDeletedTelegramMessageIds({
_: "other",
deleted_business_messages: { message_ids: [1, 2] },
}),
[1, 2],
);
assert.deepEqual(
extractDeletedTelegramMessageIds({
_: "updateDeleteMessages",
messages: [3, 4],
}),
[3, 4],
);
assert.deepEqual(
extractDeletedTelegramMessageIds({
_: "updateDeleteMessages",
messages: [3, "bad"],
}),
[],
);
});
test("Update routing classifies authorization state for pair, allow, and deny", () => {
assert.deepEqual(getTelegramAuthorizationState(10), {
kind: "pair",
userId: 10,
});
assert.deepEqual(getTelegramAuthorizationState(10, 10), { kind: "allow" });
assert.deepEqual(getTelegramAuthorizationState(10, 11), { kind: "deny" });
});
test("Update routing extracts only private human callback queries", () => {
assert.equal(
getAuthorizedTelegramCallbackQuery({
callback_query: {
from: { id: 1, is_bot: true },
message: { chat: { type: "private" } },
},
}),
undefined,
);
const query = getAuthorizedTelegramCallbackQuery({
callback_query: {
from: { id: 1, is_bot: false },
message: { chat: { type: "private" } },
},
});
assert.ok(query);
});
test("Update routing extracts private human messages from message or edited_message", () => {
assert.equal(
getAuthorizedTelegramMessage({
message: {
chat: { type: "group" },
from: { id: 1, is_bot: false },
},
}),
undefined,
);
const directMessage = getAuthorizedTelegramMessage({
edited_message: {
chat: { type: "private" },
from: { id: 1, is_bot: false },
},
});
assert.ok(directMessage);
});
test("Update flow prioritizes deleted-message handling over other update kinds", () => {
const action = buildTelegramUpdateFlowAction(
{
_: "updateDeleteMessages",
messages: [1, 2],
message_reaction: {
chat: { type: "private" },
user: { id: 1, is_bot: false },
},
},
1,
);
assert.deepEqual(action, { kind: "deleted", messageIds: [1, 2] });
});
test("Update flow returns authorized callback and message actions", () => {
const callbackAction = buildTelegramUpdateFlowAction(
{
_: "other",
callback_query: {
from: { id: 7, is_bot: false },
message: { chat: { type: "private" } },
},
},
7,
);
assert.equal(callbackAction.kind, "callback");
assert.deepEqual(
callbackAction.kind === "callback" ? callbackAction.authorization : undefined,
{ kind: "allow" },
);
const messageAction = buildTelegramUpdateFlowAction({
_: "other",
message: {
chat: { type: "private" },
from: { id: 9, is_bot: false },
},
});
assert.equal(messageAction.kind, "message");
assert.deepEqual(
messageAction.kind === "message" ? messageAction.authorization : undefined,
{ kind: "pair", userId: 9 },
);
});
test("Update flow ignores unauthorized transport shapes and preserves reaction events", () => {
const reactionAction = buildTelegramUpdateFlowAction({
_: "other",
message_reaction: {
chat: { type: "private" },
user: { id: 1, is_bot: false },
},
});
assert.equal(reactionAction.kind, "reaction");
const ignored = buildTelegramUpdateFlowAction({
_: "other",
callback_query: {
from: { id: 1, is_bot: true },
message: { chat: { type: "private" } },
},
});
assert.deepEqual(ignored, { kind: "ignore" });
});
test("Update execution plan maps callback and message authorization to side-effect flags", () => {
const callbackPlan = buildTelegramUpdateExecutionPlan({
kind: "callback",
query: {
from: { id: 1, is_bot: false },
message: { chat: { type: "private" } },
},
authorization: { kind: "deny" },
});
assert.deepEqual(callbackPlan, {
kind: "callback",
query: {
from: { id: 1, is_bot: false },
message: { chat: { type: "private" } },
},
shouldPair: false,
shouldDeny: true,
});
const messagePlan = buildTelegramUpdateExecutionPlan({
kind: "message",
message: {
chat: { type: "private" },
from: { id: 2, is_bot: false },
},
authorization: { kind: "pair", userId: 2 },
});
assert.equal(messagePlan.kind, "message");
assert.equal(messagePlan.shouldPair, true);
assert.equal(messagePlan.shouldNotifyPaired, true);
assert.equal(messagePlan.shouldDeny, false);
});
test("Update execution plan preserves deleted and reaction actions", () => {
assert.deepEqual(
buildTelegramUpdateExecutionPlan({ kind: "deleted", messageIds: [1, 2] }),
{ kind: "deleted", messageIds: [1, 2] },
);
const reactionUpdate = {
chat: { type: "private" },
user: { id: 1, is_bot: false },
};
assert.deepEqual(
buildTelegramUpdateExecutionPlan({
kind: "reaction",
reactionUpdate,
}),
{ kind: "reaction", reactionUpdate },
);
});
test("Update execution plan can be built directly from updates", () => {
const plan = buildTelegramUpdateExecutionPlanFromUpdate(
{
_: "other",
callback_query: {
from: { id: 4, is_bot: false },
message: { chat: { type: "private" } },
},
},
5,
);
assert.equal(plan.kind, "callback");
assert.equal(plan.kind === "callback" ? plan.shouldDeny : false, true);
});
test("Update runtime executes delete and reaction plans through the right side effects", async () => {
const events: string[] = [];
await executeTelegramUpdatePlan(
{ kind: "deleted", messageIds: [1, 2] },
{
ctx: {} as never,
removePendingMediaGroupMessages: (ids) => {
events.push(`media:${ids.join(',')}`);
},
removeQueuedTelegramTurnsByMessageIds: (ids) => {
events.push(`queue:${ids.join(',')}`);
return ids.length;
},
handleAuthorizedTelegramReactionUpdate: async () => {
events.push("reaction");
},
pairTelegramUserIfNeeded: async () => false,
answerCallbackQuery: async () => {},
handleAuthorizedTelegramCallbackQuery: async () => {},
sendTextReply: async () => undefined,
handleAuthorizedTelegramMessage: async () => {},
},
);
assert.deepEqual(events, ["media:1,2", "queue:1,2"]);
});
test("Update runtime can execute directly from raw updates", async () => {
const events: string[] = [];
await executeTelegramUpdate(
{
_: "other",
message: {
chat: { id: 10, type: "private" },
message_id: 20,
from: { id: 7, is_bot: false },
},
},
undefined,
{
ctx: {} as never,
removePendingMediaGroupMessages: () => {},
removeQueuedTelegramTurnsByMessageIds: () => 0,
handleAuthorizedTelegramReactionUpdate: async () => {},
pairTelegramUserIfNeeded: async () => {
events.push("pair");
return true;
},
answerCallbackQuery: async () => {},
handleAuthorizedTelegramCallbackQuery: async () => {},
sendTextReply: async (_chatId, _replyToMessageId, text) => {
events.push(`reply:${text}`);
return undefined;
},
handleAuthorizedTelegramMessage: async () => {
events.push("message");
},
},
);
assert.deepEqual(events, ["pair", "reply:Telegram bridge paired with this account.", "message"]);
});
test("Update runtime handles callback deny and message pair flows", async () => {
const events: string[] = [];
await executeTelegramUpdatePlan(
{
kind: "callback",
query: {
id: "cb",
from: { id: 1, is_bot: false },
message: { chat: { type: "private" } },
},
shouldPair: true,
shouldDeny: true,
},
{
ctx: {} as never,
removePendingMediaGroupMessages: () => {},
removeQueuedTelegramTurnsByMessageIds: () => 0,
handleAuthorizedTelegramReactionUpdate: async () => {},
pairTelegramUserIfNeeded: async (userId) => {
events.push(`pair:${userId}`);
return true;
},
answerCallbackQuery: async (id, text) => {
events.push(`answer:${id}:${text}`);
},
handleAuthorizedTelegramCallbackQuery: async () => {
events.push("callback");
},
sendTextReply: async (chatId, replyToMessageId, text) => {
events.push(`reply:${chatId}:${replyToMessageId}:${text}`);
return undefined;
},
handleAuthorizedTelegramMessage: async () => {
events.push("message");
},
},
);
await executeTelegramUpdatePlan(
{
kind: "message",
message: {
chat: { id: 7, type: "private" },
from: { id: 2, is_bot: false },
message_id: 9,
},
shouldPair: true,
shouldNotifyPaired: true,
shouldDeny: false,
},
{
ctx: {} as never,
removePendingMediaGroupMessages: () => {},
removeQueuedTelegramTurnsByMessageIds: () => 0,
handleAuthorizedTelegramReactionUpdate: async () => {},
pairTelegramUserIfNeeded: async () => true,
answerCallbackQuery: async () => {},
handleAuthorizedTelegramCallbackQuery: async () => {},
sendTextReply: async (chatId, replyToMessageId, text) => {
events.push(`reply:${chatId}:${replyToMessageId}:${text}`);
return undefined;
},
handleAuthorizedTelegramMessage: async () => {
events.push("message");
},
},
);
assert.deepEqual(events, [
"pair:1",
"answer:cb:This bot is not authorized for your account.",
"reply:7:9:Telegram bridge paired with this account.",
"message",
]);
});