@llblab/pi-telegram 0.2.9 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,132 +0,0 @@
1
- /**
2
- * Regression tests for the Telegram turn-building domain
3
- * Covers queue-summary formatting, prompt construction, and prompt-turn assembly from messages and downloaded files
4
- */
5
-
6
- import assert from "node:assert/strict";
7
- import test from "node:test";
8
-
9
- import {
10
- buildTelegramPromptTurn,
11
- buildTelegramTurnPrompt,
12
- formatTelegramTurnStatusSummary,
13
- truncateTelegramQueueSummary,
14
- } from "../lib/turns.ts";
15
-
16
- test("Turn helpers truncate queue summaries predictably", () => {
17
- assert.equal(
18
- truncateTelegramQueueSummary("one two three four"),
19
- "one two three four",
20
- );
21
- assert.equal(
22
- truncateTelegramQueueSummary("one two three four five six"),
23
- "one two three four five…",
24
- );
25
- assert.equal(truncateTelegramQueueSummary(" "), "");
26
- });
27
-
28
- test("Turn helpers build prompt text with history and attachments", () => {
29
- const prompt = buildTelegramTurnPrompt({
30
- telegramPrefix: "[telegram]",
31
- rawText: "current message",
32
- files: [{ path: "/tmp/demo.png", fileName: "demo.png", isImage: true }],
33
- historyTurns: [{ historyText: "older message" }],
34
- });
35
- assert.match(prompt, /^\[telegram\]/);
36
- assert.match(
37
- prompt,
38
- /Earlier Telegram messages arrived after an aborted turn/,
39
- );
40
- assert.match(prompt, /1\. older message/);
41
- assert.match(prompt, /Current Telegram message:\ncurrent message/);
42
- assert.match(
43
- prompt,
44
- /Telegram attachments were saved locally:\n- \/tmp\/demo.png/,
45
- );
46
- });
47
-
48
- test("Turn helpers summarize text and attachment-only turns", () => {
49
- assert.equal(
50
- formatTelegramTurnStatusSummary("hello there from telegram", []),
51
- "hello there from telegram",
52
- );
53
- assert.equal(
54
- formatTelegramTurnStatusSummary("", [
55
- {
56
- path: "/tmp/report-final-version.txt",
57
- fileName: "report-final-version.txt",
58
- isImage: false,
59
- },
60
- ]),
61
- "📎 report-final-version.txt",
62
- );
63
- assert.equal(
64
- formatTelegramTurnStatusSummary("", [
65
- { path: "/tmp/a.txt", fileName: "a.txt", isImage: false },
66
- { path: "/tmp/b.txt", fileName: "b.txt", isImage: false },
67
- ]),
68
- "📎 2 attachments",
69
- );
70
- });
71
-
72
- test("Turn helpers assemble prompt turns with text, ids, history, and image payloads", async () => {
73
- const turn = await buildTelegramPromptTurn({
74
- telegramPrefix: "[telegram]",
75
- messages: [
76
- { message_id: 10, chat: { id: 99 } },
77
- { message_id: 11, chat: { id: 99 } },
78
- ],
79
- historyTurns: [
80
- {
81
- kind: "prompt",
82
- chatId: 99,
83
- replyToMessageId: 1,
84
- sourceMessageIds: [1],
85
- queueOrder: 1,
86
- queueLane: "default",
87
- laneOrder: 1,
88
- queuedAttachments: [],
89
- content: [{ type: "text", text: "ignored" }],
90
- historyText: "older message",
91
- statusSummary: "older",
92
- },
93
- ],
94
- queueOrder: 7,
95
- rawText: "current message",
96
- files: [
97
- {
98
- path: "/tmp/demo.png",
99
- fileName: "demo.png",
100
- isImage: true,
101
- mimeType: "image/png",
102
- },
103
- {
104
- path: "/tmp/report.txt",
105
- fileName: "report.txt",
106
- isImage: false,
107
- },
108
- ],
109
- readBinaryFile: async () => new Uint8Array([1, 2, 3]),
110
- inferImageMimeType: () => undefined,
111
- });
112
- assert.equal(turn.chatId, 99);
113
- assert.equal(turn.replyToMessageId, 10);
114
- assert.deepEqual(turn.sourceMessageIds, [10, 11]);
115
- assert.equal(turn.queueOrder, 7);
116
- assert.equal(turn.statusSummary, "current message");
117
- assert.equal(
118
- turn.historyText,
119
- "current message\nAttachments:\n- /tmp/demo.png\n- /tmp/report.txt",
120
- );
121
- assert.equal(turn.content.length, 2);
122
- assert.equal(turn.content[0]?.type, "text");
123
- assert.match(
124
- (turn.content[0] as { type: "text"; text: string }).text,
125
- /Earlier Telegram messages arrived after an aborted turn/,
126
- );
127
- assert.deepEqual(turn.content[1], {
128
- type: "image",
129
- data: Buffer.from([1, 2, 3]).toString("base64"),
130
- mimeType: "image/png",
131
- });
132
- });
@@ -1,357 +0,0 @@
1
- /**
2
- * Regression tests for the Telegram updates domain
3
- * Covers extraction, authorization, flow classification, execution planning, and runtime execution in one suite
4
- */
5
-
6
- import test from "node:test";
7
- import assert from "node:assert/strict";
8
-
9
- import {
10
- buildTelegramUpdateExecutionPlan,
11
- buildTelegramUpdateExecutionPlanFromUpdate,
12
- buildTelegramUpdateFlowAction,
13
- collectTelegramReactionEmojis,
14
- executeTelegramUpdate,
15
- executeTelegramUpdatePlan,
16
- extractDeletedTelegramMessageIds,
17
- getAuthorizedTelegramCallbackQuery,
18
- getAuthorizedTelegramMessage,
19
- getTelegramAuthorizationState,
20
- normalizeTelegramReactionEmoji,
21
- } from "../lib/updates.ts";
22
-
23
- test("Update helpers normalize emoji reactions and collect emoji-only entries", () => {
24
- assert.equal(normalizeTelegramReactionEmoji("👍️"), "👍");
25
- const emojis = collectTelegramReactionEmojis([
26
- { type: "emoji", emoji: "👍️" },
27
- { type: "emoji", emoji: "👎" },
28
- { type: "custom_emoji" },
29
- ]);
30
- assert.deepEqual([...emojis], ["👍", "👎"]);
31
- });
32
-
33
- test("Update helpers extract deleted business-message ids only from Bot API shapes", () => {
34
- assert.deepEqual(
35
- extractDeletedTelegramMessageIds({
36
- deleted_business_messages: { message_ids: [1, 2] },
37
- }),
38
- [1, 2],
39
- );
40
- assert.deepEqual(
41
- extractDeletedTelegramMessageIds({
42
- deleted_business_messages: { message_ids: [3, "bad"] },
43
- }),
44
- [],
45
- );
46
- assert.deepEqual(extractDeletedTelegramMessageIds({}), []);
47
- });
48
-
49
- test("Update routing classifies authorization state for pair, allow, and deny", () => {
50
- assert.deepEqual(getTelegramAuthorizationState(10), {
51
- kind: "pair",
52
- userId: 10,
53
- });
54
- assert.deepEqual(getTelegramAuthorizationState(10, 10), { kind: "allow" });
55
- assert.deepEqual(getTelegramAuthorizationState(10, 11), { kind: "deny" });
56
- });
57
-
58
- test("Update routing extracts only private human callback queries", () => {
59
- assert.equal(
60
- getAuthorizedTelegramCallbackQuery({
61
- callback_query: {
62
- from: { id: 1, is_bot: true },
63
- message: { chat: { type: "private" } },
64
- },
65
- }),
66
- undefined,
67
- );
68
- const query = getAuthorizedTelegramCallbackQuery({
69
- callback_query: {
70
- from: { id: 1, is_bot: false },
71
- message: { chat: { type: "private" } },
72
- },
73
- });
74
- assert.ok(query);
75
- });
76
-
77
- test("Update routing extracts private human messages from message or edited_message", () => {
78
- assert.equal(
79
- getAuthorizedTelegramMessage({
80
- message: {
81
- chat: { type: "group" },
82
- from: { id: 1, is_bot: false },
83
- },
84
- }),
85
- undefined,
86
- );
87
- const directMessage = getAuthorizedTelegramMessage({
88
- edited_message: {
89
- chat: { type: "private" },
90
- from: { id: 1, is_bot: false },
91
- },
92
- });
93
- assert.ok(directMessage);
94
- });
95
-
96
- test("Update flow prioritizes deleted business-message handling over other update kinds", () => {
97
- const action = buildTelegramUpdateFlowAction(
98
- {
99
- deleted_business_messages: { message_ids: [1, 2] },
100
- message_reaction: {
101
- chat: { type: "private" },
102
- user: { id: 1, is_bot: false },
103
- },
104
- },
105
- 1,
106
- );
107
- assert.deepEqual(action, { kind: "deleted", messageIds: [1, 2] });
108
- });
109
-
110
- test("Update flow returns authorized callback and message actions", () => {
111
- const callbackAction = buildTelegramUpdateFlowAction(
112
- {
113
- callback_query: {
114
- from: { id: 7, is_bot: false },
115
- message: { chat: { type: "private" } },
116
- },
117
- },
118
- 7,
119
- );
120
- assert.equal(callbackAction.kind, "callback");
121
- assert.deepEqual(
122
- callbackAction.kind === "callback"
123
- ? callbackAction.authorization
124
- : undefined,
125
- { kind: "allow" },
126
- );
127
- const messageAction = buildTelegramUpdateFlowAction({
128
- message: {
129
- chat: { type: "private" },
130
- from: { id: 9, is_bot: false },
131
- },
132
- });
133
- assert.equal(messageAction.kind, "message");
134
- assert.deepEqual(
135
- messageAction.kind === "message" ? messageAction.authorization : undefined,
136
- { kind: "pair", userId: 9 },
137
- );
138
- });
139
-
140
- test("Update flow ignores unauthorized transport shapes and preserves reaction events", () => {
141
- const reactionAction = buildTelegramUpdateFlowAction({
142
- message_reaction: {
143
- chat: { type: "private" },
144
- user: { id: 1, is_bot: false },
145
- },
146
- });
147
- assert.equal(reactionAction.kind, "reaction");
148
- const ignored = buildTelegramUpdateFlowAction({
149
- callback_query: {
150
- from: { id: 1, is_bot: true },
151
- message: { chat: { type: "private" } },
152
- },
153
- });
154
- assert.deepEqual(ignored, { kind: "ignore" });
155
- });
156
-
157
- test("Update execution plan maps callback and message authorization to side-effect flags", () => {
158
- const callbackPlan = buildTelegramUpdateExecutionPlan({
159
- kind: "callback",
160
- query: {
161
- from: { id: 1, is_bot: false },
162
- message: { chat: { type: "private" } },
163
- },
164
- authorization: { kind: "deny" },
165
- });
166
- assert.deepEqual(callbackPlan, {
167
- kind: "callback",
168
- query: {
169
- from: { id: 1, is_bot: false },
170
- message: { chat: { type: "private" } },
171
- },
172
- shouldPair: false,
173
- shouldDeny: true,
174
- });
175
- const messagePlan = buildTelegramUpdateExecutionPlan({
176
- kind: "message",
177
- message: {
178
- chat: { type: "private" },
179
- from: { id: 2, is_bot: false },
180
- },
181
- authorization: { kind: "pair", userId: 2 },
182
- });
183
- assert.equal(messagePlan.kind, "message");
184
- assert.equal(messagePlan.shouldPair, true);
185
- assert.equal(messagePlan.shouldNotifyPaired, true);
186
- assert.equal(messagePlan.shouldDeny, false);
187
- });
188
-
189
- test("Update execution plan preserves deleted and reaction actions", () => {
190
- assert.deepEqual(
191
- buildTelegramUpdateExecutionPlan({ kind: "deleted", messageIds: [1, 2] }),
192
- { kind: "deleted", messageIds: [1, 2] },
193
- );
194
- const reactionUpdate = {
195
- chat: { type: "private" },
196
- user: { id: 1, is_bot: false },
197
- };
198
- assert.deepEqual(
199
- buildTelegramUpdateExecutionPlan({
200
- kind: "reaction",
201
- reactionUpdate,
202
- }),
203
- { kind: "reaction", reactionUpdate },
204
- );
205
- });
206
-
207
- test("Update execution plan can be built directly from updates", () => {
208
- const plan = buildTelegramUpdateExecutionPlanFromUpdate(
209
- {
210
- callback_query: {
211
- from: { id: 4, is_bot: false },
212
- message: { chat: { type: "private" } },
213
- },
214
- },
215
- 5,
216
- );
217
- assert.equal(plan.kind, "callback");
218
- assert.equal(plan.kind === "callback" ? plan.shouldDeny : false, true);
219
- });
220
-
221
- test("Update runtime executes delete and reaction plans through the right side effects", async () => {
222
- const events: string[] = [];
223
- await executeTelegramUpdatePlan(
224
- { kind: "deleted", messageIds: [1, 2] },
225
- {
226
- ctx: {} as never,
227
- removePendingMediaGroupMessages: (ids) => {
228
- events.push(`media:${ids.join(",")}`);
229
- },
230
- removeQueuedTelegramTurnsByMessageIds: (ids) => {
231
- events.push(`queue:${ids.join(",")}`);
232
- return ids.length;
233
- },
234
- handleAuthorizedTelegramReactionUpdate: async () => {
235
- events.push("reaction");
236
- },
237
- pairTelegramUserIfNeeded: async () => false,
238
- answerCallbackQuery: async () => {},
239
- handleAuthorizedTelegramCallbackQuery: async () => {},
240
- sendTextReply: async () => undefined,
241
- handleAuthorizedTelegramMessage: async () => {},
242
- },
243
- );
244
- assert.deepEqual(events, ["media:1,2", "queue:1,2"]);
245
- });
246
-
247
- test("Update runtime can execute directly from raw updates", async () => {
248
- const events: string[] = [];
249
- await executeTelegramUpdate(
250
- {
251
- message: {
252
- chat: { id: 10, type: "private" },
253
- message_id: 20,
254
- from: { id: 7, is_bot: false },
255
- },
256
- },
257
- undefined,
258
- {
259
- ctx: {} as never,
260
- removePendingMediaGroupMessages: () => {},
261
- removeQueuedTelegramTurnsByMessageIds: () => 0,
262
- handleAuthorizedTelegramReactionUpdate: async () => {},
263
- pairTelegramUserIfNeeded: async () => {
264
- events.push("pair");
265
- return true;
266
- },
267
- answerCallbackQuery: async () => {},
268
- handleAuthorizedTelegramCallbackQuery: async () => {},
269
- sendTextReply: async (_chatId, _replyToMessageId, text) => {
270
- events.push(`reply:${text}`);
271
- return undefined;
272
- },
273
- handleAuthorizedTelegramMessage: async () => {
274
- events.push("message");
275
- },
276
- },
277
- );
278
- assert.deepEqual(events, [
279
- "pair",
280
- "reply:Telegram bridge paired with this account.",
281
- "message",
282
- ]);
283
- });
284
-
285
- test("Update runtime handles callback deny and message pair flows", async () => {
286
- const events: string[] = [];
287
- await executeTelegramUpdatePlan(
288
- {
289
- kind: "callback",
290
- query: {
291
- id: "cb",
292
- from: { id: 1, is_bot: false },
293
- message: { chat: { type: "private" } },
294
- },
295
- shouldPair: true,
296
- shouldDeny: true,
297
- },
298
- {
299
- ctx: {} as never,
300
- removePendingMediaGroupMessages: () => {},
301
- removeQueuedTelegramTurnsByMessageIds: () => 0,
302
- handleAuthorizedTelegramReactionUpdate: async () => {},
303
- pairTelegramUserIfNeeded: async (userId) => {
304
- events.push(`pair:${userId}`);
305
- return true;
306
- },
307
- answerCallbackQuery: async (id, text) => {
308
- events.push(`answer:${id}:${text}`);
309
- },
310
- handleAuthorizedTelegramCallbackQuery: async () => {
311
- events.push("callback");
312
- },
313
- sendTextReply: async (chatId, replyToMessageId, text) => {
314
- events.push(`reply:${chatId}:${replyToMessageId}:${text}`);
315
- return undefined;
316
- },
317
- handleAuthorizedTelegramMessage: async () => {
318
- events.push("message");
319
- },
320
- },
321
- );
322
- await executeTelegramUpdatePlan(
323
- {
324
- kind: "message",
325
- message: {
326
- chat: { id: 7, type: "private" },
327
- from: { id: 2, is_bot: false },
328
- message_id: 9,
329
- },
330
- shouldPair: true,
331
- shouldNotifyPaired: true,
332
- shouldDeny: false,
333
- },
334
- {
335
- ctx: {} as never,
336
- removePendingMediaGroupMessages: () => {},
337
- removeQueuedTelegramTurnsByMessageIds: () => 0,
338
- handleAuthorizedTelegramReactionUpdate: async () => {},
339
- pairTelegramUserIfNeeded: async () => true,
340
- answerCallbackQuery: async () => {},
341
- handleAuthorizedTelegramCallbackQuery: async () => {},
342
- sendTextReply: async (chatId, replyToMessageId, text) => {
343
- events.push(`reply:${chatId}:${replyToMessageId}:${text}`);
344
- return undefined;
345
- },
346
- handleAuthorizedTelegramMessage: async () => {
347
- events.push("message");
348
- },
349
- },
350
- );
351
- assert.deepEqual(events, [
352
- "pair:1",
353
- "answer:cb:This bot is not authorized for your account.",
354
- "reply:7:9:Telegram bridge paired with this account.",
355
- "message",
356
- ]);
357
- });