@ai-devkit/channel-connector 0.13.2 → 0.13.4
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.
- package/dist/ChannelConfigRepository.d.ts +1 -1
- package/dist/ChannelConfigRepository.d.ts.map +1 -1
- package/dist/ChannelConfigRepository.js +13 -13
- package/dist/ChannelConfigRepository.js.map +1 -1
- package/dist/ChannelManager.d.ts +1 -1
- package/dist/ChannelManager.d.ts.map +1 -1
- package/dist/ChannelManager.js +9 -9
- package/dist/ChannelManager.js.map +1 -1
- package/dist/__tests__/ChannelConfigRepository.test.js +72 -72
- package/dist/__tests__/ChannelConfigRepository.test.js.map +1 -1
- package/dist/__tests__/ChannelManager.test.js +26 -26
- package/dist/__tests__/ChannelManager.test.js.map +1 -1
- package/dist/__tests__/adapters/slack/SlackAdapter.test.js +137 -137
- package/dist/__tests__/adapters/slack/SlackAdapter.test.js.map +1 -1
- package/dist/__tests__/adapters/slack/SlackMessageDelivery.test.js +36 -36
- package/dist/__tests__/adapters/slack/SlackMessageDelivery.test.js.map +1 -1
- package/dist/__tests__/adapters/slack/slackMarkdown.test.js +25 -25
- package/dist/__tests__/adapters/slack/slackMarkdown.test.js.map +1 -1
- package/dist/__tests__/adapters/slack/slackQuestion.test.js +41 -41
- package/dist/__tests__/adapters/slack/slackQuestion.test.js.map +1 -1
- package/dist/__tests__/adapters/telegram/TelegramAdapter.test.js +154 -154
- package/dist/__tests__/adapters/telegram/TelegramAdapter.test.js.map +1 -1
- package/dist/__tests__/adapters/telegram/telegramHtml.test.js +109 -109
- package/dist/__tests__/adapters/telegram/telegramHtml.test.js.map +1 -1
- package/dist/adapters/ChannelAdapter.d.ts +1 -1
- package/dist/adapters/ChannelAdapter.d.ts.map +1 -1
- package/dist/adapters/ChannelAdapter.js +1 -1
- package/dist/adapters/ChannelAdapter.js.map +1 -1
- package/dist/adapters/slack/SlackAdapter.d.ts +2 -2
- package/dist/adapters/slack/SlackAdapter.d.ts.map +1 -1
- package/dist/adapters/slack/SlackAdapter.js +17 -17
- package/dist/adapters/slack/SlackAdapter.js.map +1 -1
- package/dist/adapters/slack/SlackMessageDelivery.d.ts +1 -1
- package/dist/adapters/slack/SlackMessageDelivery.d.ts.map +1 -1
- package/dist/adapters/slack/SlackMessageDelivery.js +6 -6
- package/dist/adapters/slack/SlackMessageDelivery.js.map +1 -1
- package/dist/adapters/slack/slackMarkdown.d.ts.map +1 -1
- package/dist/adapters/slack/slackMarkdown.js +36 -36
- package/dist/adapters/slack/slackMarkdown.js.map +1 -1
- package/dist/adapters/slack/slackQuestion.d.ts +7 -7
- package/dist/adapters/slack/slackQuestion.d.ts.map +1 -1
- package/dist/adapters/slack/slackQuestion.js +13 -13
- package/dist/adapters/slack/slackQuestion.js.map +1 -1
- package/dist/adapters/telegram/TelegramAdapter.d.ts +2 -2
- package/dist/adapters/telegram/TelegramAdapter.d.ts.map +1 -1
- package/dist/adapters/telegram/TelegramAdapter.js +30 -27
- package/dist/adapters/telegram/TelegramAdapter.js.map +1 -1
- package/dist/adapters/telegram/TelegramMessageDelivery.d.ts.map +1 -1
- package/dist/adapters/telegram/TelegramMessageDelivery.js +2 -2
- package/dist/adapters/telegram/TelegramMessageDelivery.js.map +1 -1
- package/dist/adapters/telegram/telegramHtml.d.ts.map +1 -1
- package/dist/adapters/telegram/telegramHtml.js +16 -16
- package/dist/adapters/telegram/telegramHtml.js.map +1 -1
- package/dist/adapters/telegram/telegramKeyboard.d.ts +1 -1
- package/dist/adapters/telegram/telegramKeyboard.d.ts.map +1 -1
- package/dist/adapters/telegram/telegramKeyboard.js.map +1 -1
- package/dist/adapters/telegram/telegramMarkdown.d.ts.map +1 -1
- package/dist/adapters/telegram/telegramMarkdown.js +28 -28
- package/dist/adapters/telegram/telegramMarkdown.js.map +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +6 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +21 -22
- package/vitest.config.ts +16 -16
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import * as telegrafModule from
|
|
2
|
-
import * as telegramHtml from
|
|
3
|
-
import { TelegramAdapter } from
|
|
1
|
+
import * as telegrafModule from "telegraf";
|
|
2
|
+
import * as telegramHtml from "../../../adapters/telegram/telegramHtml.js";
|
|
3
|
+
import { TelegramAdapter } from "../../../adapters/telegram/TelegramAdapter.js";
|
|
4
4
|
// Mock telegraf
|
|
5
|
-
vi.mock(
|
|
5
|
+
vi.mock("telegraf", ()=>{
|
|
6
6
|
const handlers = {};
|
|
7
7
|
const mockBot = {
|
|
8
8
|
launch: vi.fn().mockResolvedValue(undefined),
|
|
@@ -15,7 +15,7 @@ vi.mock('telegraf', ()=>{
|
|
|
15
15
|
editMessageReplyMarkup: vi.fn().mockResolvedValue(undefined),
|
|
16
16
|
answerCbQuery: vi.fn().mockResolvedValue(undefined),
|
|
17
17
|
getMe: vi.fn().mockResolvedValue({
|
|
18
|
-
username:
|
|
18
|
+
username: "test_bot"
|
|
19
19
|
})
|
|
20
20
|
},
|
|
21
21
|
_handlers: handlers,
|
|
@@ -33,15 +33,15 @@ vi.mock('telegraf', ()=>{
|
|
|
33
33
|
},
|
|
34
34
|
reply: vi.fn().mockResolvedValue(undefined)
|
|
35
35
|
};
|
|
36
|
-
if (handlers[
|
|
37
|
-
await handlers[
|
|
36
|
+
if (handlers["text"]) {
|
|
37
|
+
await handlers["text"](ctx);
|
|
38
38
|
}
|
|
39
39
|
return ctx;
|
|
40
40
|
},
|
|
41
41
|
_triggerCallback: async (chatId, userId, data, messageId = 555)=>{
|
|
42
42
|
const ctx = {
|
|
43
43
|
callbackQuery: {
|
|
44
|
-
id:
|
|
44
|
+
id: "cbq-test",
|
|
45
45
|
data,
|
|
46
46
|
message: {
|
|
47
47
|
message_id: messageId,
|
|
@@ -55,8 +55,8 @@ vi.mock('telegraf', ()=>{
|
|
|
55
55
|
},
|
|
56
56
|
answerCbQuery: vi.fn().mockResolvedValue(undefined)
|
|
57
57
|
};
|
|
58
|
-
if (handlers[
|
|
59
|
-
await handlers[
|
|
58
|
+
if (handlers["callback_query"]) {
|
|
59
|
+
await handlers["callback_query"](ctx);
|
|
60
60
|
}
|
|
61
61
|
return ctx;
|
|
62
62
|
}
|
|
@@ -77,213 +77,213 @@ function expectHtmlChunkWithinLimit(chunk) {
|
|
|
77
77
|
expect((chunk.match(/<pre>/g) ?? []).length).toBe((chunk.match(/<\/pre>/g) ?? []).length);
|
|
78
78
|
expect((chunk.match(/<code(?: class="language-[^"]+")?>/g) ?? []).length).toBe((chunk.match(/<\/code>/g) ?? []).length);
|
|
79
79
|
}
|
|
80
|
-
describe(
|
|
80
|
+
describe("TelegramAdapter", ()=>{
|
|
81
81
|
let adapter;
|
|
82
82
|
beforeEach(()=>{
|
|
83
83
|
vi.clearAllMocks();
|
|
84
84
|
adapter = new TelegramAdapter({
|
|
85
|
-
botToken:
|
|
85
|
+
botToken: "test-token-123"
|
|
86
86
|
});
|
|
87
87
|
});
|
|
88
|
-
describe(
|
|
88
|
+
describe("type", ()=>{
|
|
89
89
|
it('should return "telegram"', ()=>{
|
|
90
|
-
expect(adapter.type).toBe(
|
|
90
|
+
expect(adapter.type).toBe("telegram");
|
|
91
91
|
});
|
|
92
92
|
});
|
|
93
|
-
describe(
|
|
94
|
-
it(
|
|
93
|
+
describe("start", ()=>{
|
|
94
|
+
it("should launch the telegraf bot", async ()=>{
|
|
95
95
|
const bot = getMockBot();
|
|
96
96
|
await adapter.start();
|
|
97
97
|
expect(bot.launch).toHaveBeenCalled();
|
|
98
98
|
});
|
|
99
99
|
});
|
|
100
|
-
describe(
|
|
101
|
-
it(
|
|
100
|
+
describe("stop", ()=>{
|
|
101
|
+
it("should stop the telegraf bot", async ()=>{
|
|
102
102
|
const bot = getMockBot();
|
|
103
103
|
await adapter.start();
|
|
104
104
|
await adapter.stop();
|
|
105
105
|
expect(bot.stop).toHaveBeenCalled();
|
|
106
106
|
});
|
|
107
107
|
});
|
|
108
|
-
describe(
|
|
109
|
-
it(
|
|
108
|
+
describe("onMessage", ()=>{
|
|
109
|
+
it("should silently ignore messages when no handler is registered", async ()=>{
|
|
110
110
|
// Don't register a handler
|
|
111
111
|
await adapter.start();
|
|
112
112
|
const bot = getMockBot();
|
|
113
|
-
const ctx = await bot._triggerText(12345, 67890,
|
|
113
|
+
const ctx = await bot._triggerText(12345, 67890, "hello");
|
|
114
114
|
// Should not throw or reply
|
|
115
115
|
expect(ctx.reply).not.toHaveBeenCalled();
|
|
116
116
|
});
|
|
117
|
-
it(
|
|
118
|
-
const handler = vi.fn().mockRejectedValue(
|
|
117
|
+
it("should handle non-Error thrown by handler", async ()=>{
|
|
118
|
+
const handler = vi.fn().mockRejectedValue("string error");
|
|
119
119
|
adapter.onMessage(handler);
|
|
120
120
|
await adapter.start();
|
|
121
121
|
const bot = getMockBot();
|
|
122
|
-
const ctx = await bot._triggerText(12345, 67890,
|
|
123
|
-
expect(ctx.reply).toHaveBeenCalledWith(
|
|
122
|
+
const ctx = await bot._triggerText(12345, 67890, "hello");
|
|
123
|
+
expect(ctx.reply).toHaveBeenCalledWith("Error processing message: Unknown error");
|
|
124
124
|
});
|
|
125
|
-
it(
|
|
125
|
+
it("should call handler with IncomingMessage on incoming text", async ()=>{
|
|
126
126
|
const handler = vi.fn().mockResolvedValue(undefined);
|
|
127
127
|
adapter.onMessage(handler);
|
|
128
128
|
await adapter.start();
|
|
129
129
|
const bot = getMockBot();
|
|
130
|
-
await bot._triggerText(12345, 67890,
|
|
130
|
+
await bot._triggerText(12345, 67890, "hello agent");
|
|
131
131
|
expect(handler).toHaveBeenCalledTimes(1);
|
|
132
132
|
const msg = handler.mock.calls[0][0];
|
|
133
|
-
expect(msg.channelType).toBe(
|
|
134
|
-
expect(msg.chatId).toBe(
|
|
135
|
-
expect(msg.userId).toBe(
|
|
136
|
-
expect(msg.text).toBe(
|
|
133
|
+
expect(msg.channelType).toBe("telegram");
|
|
134
|
+
expect(msg.chatId).toBe("12345");
|
|
135
|
+
expect(msg.userId).toBe("67890");
|
|
136
|
+
expect(msg.text).toBe("hello agent");
|
|
137
137
|
expect(msg.timestamp).toBeInstanceOf(Date);
|
|
138
138
|
});
|
|
139
|
-
it(
|
|
140
|
-
const handler = vi.fn().mockRejectedValue(new Error(
|
|
139
|
+
it("should handle handler errors gracefully", async ()=>{
|
|
140
|
+
const handler = vi.fn().mockRejectedValue(new Error("handler failed"));
|
|
141
141
|
adapter.onMessage(handler);
|
|
142
142
|
await adapter.start();
|
|
143
143
|
const bot = getMockBot();
|
|
144
|
-
const ctx = await bot._triggerText(12345, 67890,
|
|
144
|
+
const ctx = await bot._triggerText(12345, 67890, "hello");
|
|
145
145
|
// Should not throw, and should reply with error
|
|
146
|
-
expect(ctx.reply).toHaveBeenCalledWith(expect.stringContaining(
|
|
146
|
+
expect(ctx.reply).toHaveBeenCalledWith(expect.stringContaining("Error processing message"));
|
|
147
147
|
});
|
|
148
148
|
});
|
|
149
|
-
describe(
|
|
150
|
-
it(
|
|
149
|
+
describe("sendMessage", ()=>{
|
|
150
|
+
it("should send plain text with parse_mode HTML", async ()=>{
|
|
151
151
|
const bot = getMockBot();
|
|
152
|
-
await adapter.sendMessage(
|
|
153
|
-
expect(bot.telegram.sendMessage).toHaveBeenCalledWith(
|
|
154
|
-
parse_mode:
|
|
152
|
+
await adapter.sendMessage("12345", "hello from bot");
|
|
153
|
+
expect(bot.telegram.sendMessage).toHaveBeenCalledWith("12345", "hello from bot", {
|
|
154
|
+
parse_mode: "HTML"
|
|
155
155
|
});
|
|
156
156
|
});
|
|
157
|
-
it(
|
|
157
|
+
it("should render markdown as Telegram HTML", async ()=>{
|
|
158
158
|
const bot = getMockBot();
|
|
159
|
-
await adapter.sendMessage(
|
|
159
|
+
await adapter.sendMessage("12345", "**bold** and *italic* and `code`");
|
|
160
160
|
const sent = bot.telegram.sendMessage.mock.calls[0][1];
|
|
161
|
-
expect(sent).toContain(
|
|
162
|
-
expect(sent).toContain(
|
|
163
|
-
expect(sent).toContain(
|
|
161
|
+
expect(sent).toContain("<b>bold</b>");
|
|
162
|
+
expect(sent).toContain("<i>italic</i>");
|
|
163
|
+
expect(sent).toContain("<code>code</code>");
|
|
164
164
|
expect(bot.telegram.sendMessage.mock.calls[0][2]).toEqual({
|
|
165
|
-
parse_mode:
|
|
165
|
+
parse_mode: "HTML"
|
|
166
166
|
});
|
|
167
167
|
});
|
|
168
|
-
it(
|
|
168
|
+
it("should chunk messages exceeding 4096 chars", async ()=>{
|
|
169
169
|
const bot = getMockBot();
|
|
170
|
-
const line =
|
|
170
|
+
const line = "A".repeat(100) + "\n";
|
|
171
171
|
const longMessage = line.repeat(50); // 5050 chars
|
|
172
|
-
await adapter.sendMessage(
|
|
172
|
+
await adapter.sendMessage("12345", longMessage);
|
|
173
173
|
expect(bot.telegram.sendMessage.mock.calls.length).toBeGreaterThan(1);
|
|
174
174
|
for (const call of bot.telegram.sendMessage.mock.calls){
|
|
175
175
|
expect(call[1].length).toBeLessThanOrEqual(4096);
|
|
176
176
|
expect(call[2]).toEqual({
|
|
177
|
-
parse_mode:
|
|
177
|
+
parse_mode: "HTML"
|
|
178
178
|
});
|
|
179
179
|
}
|
|
180
180
|
});
|
|
181
|
-
it(
|
|
181
|
+
it("should split long fenced code into independently valid HTML code blocks", async ()=>{
|
|
182
182
|
const bot = getMockBot();
|
|
183
183
|
const codeLine = 'const sample = "<code>tag</code>" && value < limit;\n';
|
|
184
184
|
const longMessage = `Before\n\n\`\`\`ts\n${codeLine.repeat(100)}\`\`\`\n\nAfter`;
|
|
185
|
-
await adapter.sendMessage(
|
|
185
|
+
await adapter.sendMessage("12345", longMessage);
|
|
186
186
|
expect(bot.telegram.sendMessage.mock.calls.length).toBeGreaterThan(1);
|
|
187
187
|
for (const call of bot.telegram.sendMessage.mock.calls){
|
|
188
188
|
const chunk = call[1];
|
|
189
189
|
expect(call[2]).toEqual({
|
|
190
|
-
parse_mode:
|
|
190
|
+
parse_mode: "HTML"
|
|
191
191
|
});
|
|
192
192
|
expectHtmlChunkWithinLimit(chunk);
|
|
193
|
-
if (chunk.includes(
|
|
193
|
+
if (chunk.includes("<pre>")) {
|
|
194
194
|
expect(chunk).toContain('<code class="language-ts">');
|
|
195
|
-
expect(chunk).toContain(
|
|
195
|
+
expect(chunk).toContain("<code>tag</code>");
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
});
|
|
199
|
-
it(
|
|
199
|
+
it("should split nested list fenced code without sending partial HTML tags", async ()=>{
|
|
200
200
|
const bot = getMockBot();
|
|
201
201
|
const codeLine = ' return "<code>tag</code>" && input < output;\n';
|
|
202
202
|
const longMessage = [
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
203
|
+
"- parent item",
|
|
204
|
+
" - child item with code",
|
|
205
|
+
" ```js",
|
|
206
206
|
codeLine.repeat(100).trimEnd(),
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
].join(
|
|
210
|
-
await adapter.sendMessage(
|
|
207
|
+
" ```",
|
|
208
|
+
"- final item"
|
|
209
|
+
].join("\n");
|
|
210
|
+
await adapter.sendMessage("12345", longMessage);
|
|
211
211
|
expect(bot.telegram.sendMessage.mock.calls.length).toBeGreaterThan(1);
|
|
212
212
|
for (const call of bot.telegram.sendMessage.mock.calls){
|
|
213
213
|
const chunk = call[1];
|
|
214
214
|
expect(call[2]).toEqual({
|
|
215
|
-
parse_mode:
|
|
215
|
+
parse_mode: "HTML"
|
|
216
216
|
});
|
|
217
217
|
expectHtmlChunkWithinLimit(chunk);
|
|
218
218
|
}
|
|
219
219
|
});
|
|
220
|
-
it(
|
|
220
|
+
it("should split long paragraphs at readable markdown boundaries", async ()=>{
|
|
221
221
|
const bot = getMockBot();
|
|
222
|
-
const sentence =
|
|
222
|
+
const sentence = "This is a long sentence with enough words to make chunking choose sentence boundaries. ";
|
|
223
223
|
const longMessage = sentence.repeat(80);
|
|
224
|
-
await adapter.sendMessage(
|
|
224
|
+
await adapter.sendMessage("12345", longMessage);
|
|
225
225
|
expect(bot.telegram.sendMessage.mock.calls.length).toBeGreaterThan(1);
|
|
226
226
|
for (const call of bot.telegram.sendMessage.mock.calls){
|
|
227
227
|
const chunk = call[1];
|
|
228
228
|
expect(call[2]).toEqual({
|
|
229
|
-
parse_mode:
|
|
229
|
+
parse_mode: "HTML"
|
|
230
230
|
});
|
|
231
231
|
expectHtmlChunkWithinLimit(chunk);
|
|
232
|
-
expect(chunk.endsWith(
|
|
232
|
+
expect(chunk.endsWith(".") || chunk.endsWith(".\n\n") || chunk.endsWith(" ")).toBe(true);
|
|
233
233
|
}
|
|
234
234
|
});
|
|
235
|
-
it(
|
|
235
|
+
it("should keep emoji chunks within the configured JavaScript string length limit", async ()=>{
|
|
236
236
|
const bot = getMockBot();
|
|
237
|
-
const longMessage = `Status ${
|
|
238
|
-
await adapter.sendMessage(
|
|
237
|
+
const longMessage = `Status ${"🧪".repeat(2500)}`;
|
|
238
|
+
await adapter.sendMessage("12345", longMessage);
|
|
239
239
|
expect(bot.telegram.sendMessage.mock.calls.length).toBeGreaterThan(1);
|
|
240
240
|
for (const call of bot.telegram.sendMessage.mock.calls){
|
|
241
241
|
expect(call[1].length).toBeLessThanOrEqual(4096);
|
|
242
242
|
expect(call[2]).toEqual({
|
|
243
|
-
parse_mode:
|
|
243
|
+
parse_mode: "HTML"
|
|
244
244
|
});
|
|
245
245
|
}
|
|
246
246
|
});
|
|
247
|
-
it(
|
|
247
|
+
it("should hard split at 4096 when no newlines available", async ()=>{
|
|
248
248
|
const bot = getMockBot();
|
|
249
|
-
const longMessage =
|
|
250
|
-
await adapter.sendMessage(
|
|
249
|
+
const longMessage = "A".repeat(5000);
|
|
250
|
+
await adapter.sendMessage("12345", longMessage);
|
|
251
251
|
expect(bot.telegram.sendMessage.mock.calls.length).toBe(2);
|
|
252
252
|
expect(bot.telegram.sendMessage.mock.calls[0][1].length).toBe(4096);
|
|
253
253
|
expect(bot.telegram.sendMessage.mock.calls[1][1].length).toBe(904);
|
|
254
254
|
});
|
|
255
|
-
it(
|
|
255
|
+
it("should prefer paragraph (\\n\\n) over single \\n when chunking", async ()=>{
|
|
256
256
|
const bot = getMockBot();
|
|
257
257
|
// 4 paragraphs of ~1500 chars each, total > 4096
|
|
258
|
-
const paragraph =
|
|
258
|
+
const paragraph = "A".repeat(1500);
|
|
259
259
|
const message = `${paragraph}\n\n${paragraph}\n\n${paragraph}\n\n${paragraph}`;
|
|
260
|
-
await adapter.sendMessage(
|
|
260
|
+
await adapter.sendMessage("12345", message);
|
|
261
261
|
// First chunk should end at a \n\n boundary, not mid-paragraph
|
|
262
262
|
const firstChunk = bot.telegram.sendMessage.mock.calls[0][1];
|
|
263
263
|
expect(firstChunk).toBe(`${paragraph}\n\n${paragraph}`);
|
|
264
264
|
});
|
|
265
|
-
it(
|
|
265
|
+
it("should send short messages in a single call", async ()=>{
|
|
266
266
|
const bot = getMockBot();
|
|
267
|
-
await adapter.sendMessage(
|
|
267
|
+
await adapter.sendMessage("12345", "short message");
|
|
268
268
|
expect(bot.telegram.sendMessage).toHaveBeenCalledTimes(1);
|
|
269
269
|
});
|
|
270
|
-
it(
|
|
270
|
+
it("should retry as plain text when Telegram rejects HTML with parse-entities error", async ()=>{
|
|
271
271
|
const bot = getMockBot();
|
|
272
|
-
const parseError = Object.assign(new Error(
|
|
273
|
-
description:
|
|
272
|
+
const parseError = Object.assign(new Error("400: Bad Request"), {
|
|
273
|
+
description: 'Bad Request: can\'t parse entities: Unsupported start tag "foo"'
|
|
274
274
|
});
|
|
275
275
|
bot.telegram.sendMessage.mockRejectedValueOnce(parseError).mockResolvedValueOnce(undefined);
|
|
276
|
-
await adapter.sendMessage(
|
|
276
|
+
await adapter.sendMessage("12345", "**hello**");
|
|
277
277
|
expect(bot.telegram.sendMessage).toHaveBeenCalledTimes(2);
|
|
278
278
|
// First call: rendered HTML with parse_mode
|
|
279
279
|
const [, htmlChunk, htmlOpts] = bot.telegram.sendMessage.mock.calls[0];
|
|
280
|
-
expect(htmlChunk).toContain(
|
|
280
|
+
expect(htmlChunk).toContain("<b>hello</b>");
|
|
281
281
|
expect(htmlOpts).toEqual({
|
|
282
|
-
parse_mode:
|
|
282
|
+
parse_mode: "HTML"
|
|
283
283
|
});
|
|
284
284
|
// Second call: same content, plain text (tags stripped, no parse_mode)
|
|
285
285
|
const [, plainChunk, plainOpts] = bot.telegram.sendMessage.mock.calls[1];
|
|
286
|
-
expect(plainChunk).toBe(
|
|
286
|
+
expect(plainChunk).toBe("hello");
|
|
287
287
|
expect(plainOpts).toBeUndefined();
|
|
288
288
|
});
|
|
289
289
|
it('should detect parse-entities error from "message" field too', async ()=>{
|
|
@@ -291,86 +291,86 @@ describe('TelegramAdapter', ()=>{
|
|
|
291
291
|
// Some error shapes carry the marker on `message` rather than `description`
|
|
292
292
|
const parseError = new Error("can't parse entities");
|
|
293
293
|
bot.telegram.sendMessage.mockRejectedValueOnce(parseError).mockResolvedValueOnce(undefined);
|
|
294
|
-
await adapter.sendMessage(
|
|
294
|
+
await adapter.sendMessage("12345", "**hi**");
|
|
295
295
|
expect(bot.telegram.sendMessage).toHaveBeenCalledTimes(2);
|
|
296
296
|
});
|
|
297
|
-
it(
|
|
297
|
+
it("should decode HTML entities when falling back to plain text", async ()=>{
|
|
298
298
|
const bot = getMockBot();
|
|
299
|
-
const parseError = Object.assign(new Error(
|
|
299
|
+
const parseError = Object.assign(new Error("400"), {
|
|
300
300
|
description: "Bad Request: can't parse entities"
|
|
301
301
|
});
|
|
302
302
|
bot.telegram.sendMessage.mockRejectedValueOnce(parseError).mockResolvedValueOnce(undefined);
|
|
303
303
|
// Source has chars that escapeHtml encodes; fallback should decode them
|
|
304
|
-
await adapter.sendMessage(
|
|
304
|
+
await adapter.sendMessage("12345", "a < b && c > d");
|
|
305
305
|
const [, plainChunk] = bot.telegram.sendMessage.mock.calls[1];
|
|
306
|
-
expect(plainChunk).toContain(
|
|
307
|
-
expect(plainChunk).not.toContain(
|
|
308
|
-
expect(plainChunk).not.toContain(
|
|
306
|
+
expect(plainChunk).toContain("a < b && c > d");
|
|
307
|
+
expect(plainChunk).not.toContain("<");
|
|
308
|
+
expect(plainChunk).not.toContain("&");
|
|
309
309
|
});
|
|
310
|
-
it(
|
|
310
|
+
it("should deliver nested lists without throwing", async ()=>{
|
|
311
311
|
const bot = getMockBot();
|
|
312
|
-
await adapter.sendMessage(
|
|
312
|
+
await adapter.sendMessage("12345", "- agent\n - Status");
|
|
313
313
|
expect(bot.telegram.sendMessage).toHaveBeenCalledTimes(1);
|
|
314
|
-
expect(bot.telegram.sendMessage.mock.calls[0][1]).toContain(
|
|
314
|
+
expect(bot.telegram.sendMessage.mock.calls[0][1]).toContain("Status");
|
|
315
315
|
});
|
|
316
|
-
it(
|
|
316
|
+
it("should send plain text when markdown formatting throws", async ()=>{
|
|
317
317
|
const bot = getMockBot();
|
|
318
|
-
const formatter = vi.spyOn(telegramHtml,
|
|
319
|
-
throw new Error(
|
|
318
|
+
const formatter = vi.spyOn(telegramHtml, "markdownToTelegramHtml").mockImplementationOnce(()=>{
|
|
319
|
+
throw new Error("formatter failed");
|
|
320
320
|
});
|
|
321
|
-
await adapter.sendMessage(
|
|
321
|
+
await adapter.sendMessage("12345", "- agent\n - Status");
|
|
322
322
|
expect(bot.telegram.sendMessage).toHaveBeenCalledTimes(1);
|
|
323
|
-
expect(bot.telegram.sendMessage.mock.calls[0][0]).toBe(
|
|
324
|
-
expect(bot.telegram.sendMessage.mock.calls[0][1]).toBe(
|
|
323
|
+
expect(bot.telegram.sendMessage.mock.calls[0][0]).toBe("12345");
|
|
324
|
+
expect(bot.telegram.sendMessage.mock.calls[0][1]).toBe("- agent\n - Status");
|
|
325
325
|
expect(bot.telegram.sendMessage.mock.calls[0][2]).toBeUndefined();
|
|
326
326
|
formatter.mockRestore();
|
|
327
327
|
});
|
|
328
|
-
it(
|
|
328
|
+
it("should propagate non-parse-entities errors without falling back", async ()=>{
|
|
329
329
|
const bot = getMockBot();
|
|
330
|
-
const otherError = Object.assign(new Error(
|
|
331
|
-
description:
|
|
330
|
+
const otherError = Object.assign(new Error("403"), {
|
|
331
|
+
description: "Forbidden: bot was blocked by the user"
|
|
332
332
|
});
|
|
333
333
|
bot.telegram.sendMessage.mockRejectedValueOnce(otherError);
|
|
334
|
-
await expect(adapter.sendMessage(
|
|
334
|
+
await expect(adapter.sendMessage("12345", "hi")).rejects.toBe(otherError);
|
|
335
335
|
// Only the HTML attempt should have happened — no fallback retry
|
|
336
336
|
expect(bot.telegram.sendMessage).toHaveBeenCalledTimes(1);
|
|
337
337
|
});
|
|
338
338
|
});
|
|
339
|
-
describe(
|
|
340
|
-
it(
|
|
339
|
+
describe("sendInlineKeyboard", ()=>{
|
|
340
|
+
it("sends with parse_mode HTML and inline_keyboard payload, returns message_id", async ()=>{
|
|
341
341
|
const bot = getMockBot();
|
|
342
342
|
bot.telegram.sendMessage.mockResolvedValueOnce({
|
|
343
343
|
message_id: 42
|
|
344
344
|
});
|
|
345
|
-
const id = await adapter.sendInlineKeyboard(
|
|
345
|
+
const id = await adapter.sendInlineKeyboard("12345", "<b>hi</b>", [
|
|
346
346
|
[
|
|
347
347
|
{
|
|
348
|
-
text:
|
|
349
|
-
callbackData:
|
|
348
|
+
text: "A",
|
|
349
|
+
callbackData: "q:1:o:0"
|
|
350
350
|
}
|
|
351
351
|
],
|
|
352
352
|
[
|
|
353
353
|
{
|
|
354
|
-
text:
|
|
355
|
-
callbackData:
|
|
354
|
+
text: "B",
|
|
355
|
+
callbackData: "q:1:o:1"
|
|
356
356
|
}
|
|
357
357
|
]
|
|
358
358
|
]);
|
|
359
359
|
expect(id).toBe(42);
|
|
360
|
-
expect(bot.telegram.sendMessage).toHaveBeenCalledWith(
|
|
361
|
-
parse_mode:
|
|
360
|
+
expect(bot.telegram.sendMessage).toHaveBeenCalledWith("12345", "<b>hi</b>", {
|
|
361
|
+
parse_mode: "HTML",
|
|
362
362
|
reply_markup: {
|
|
363
363
|
inline_keyboard: [
|
|
364
364
|
[
|
|
365
365
|
{
|
|
366
|
-
text:
|
|
367
|
-
callback_data:
|
|
366
|
+
text: "A",
|
|
367
|
+
callback_data: "q:1:o:0"
|
|
368
368
|
}
|
|
369
369
|
],
|
|
370
370
|
[
|
|
371
371
|
{
|
|
372
|
-
text:
|
|
373
|
-
callback_data:
|
|
372
|
+
text: "B",
|
|
373
|
+
callback_data: "q:1:o:1"
|
|
374
374
|
}
|
|
375
375
|
]
|
|
376
376
|
]
|
|
@@ -378,80 +378,80 @@ describe('TelegramAdapter', ()=>{
|
|
|
378
378
|
});
|
|
379
379
|
});
|
|
380
380
|
});
|
|
381
|
-
describe(
|
|
382
|
-
it(
|
|
381
|
+
describe("editInlineKeyboard", ()=>{
|
|
382
|
+
it("replaces the keyboard on an existing message", async ()=>{
|
|
383
383
|
const bot = getMockBot();
|
|
384
|
-
await adapter.editInlineKeyboard(
|
|
384
|
+
await adapter.editInlineKeyboard("12345", 99, [
|
|
385
385
|
[
|
|
386
386
|
{
|
|
387
|
-
text:
|
|
388
|
-
callbackData:
|
|
387
|
+
text: "✓ A",
|
|
388
|
+
callbackData: "q:1:o:0"
|
|
389
389
|
}
|
|
390
390
|
]
|
|
391
391
|
]);
|
|
392
|
-
expect(bot.telegram.editMessageReplyMarkup).toHaveBeenCalledWith(
|
|
392
|
+
expect(bot.telegram.editMessageReplyMarkup).toHaveBeenCalledWith("12345", 99, undefined, {
|
|
393
393
|
inline_keyboard: [
|
|
394
394
|
[
|
|
395
395
|
{
|
|
396
|
-
text:
|
|
397
|
-
callback_data:
|
|
396
|
+
text: "✓ A",
|
|
397
|
+
callback_data: "q:1:o:0"
|
|
398
398
|
}
|
|
399
399
|
]
|
|
400
400
|
]
|
|
401
401
|
});
|
|
402
402
|
});
|
|
403
|
-
it(
|
|
403
|
+
it("passes undefined reply_markup to remove the keyboard", async ()=>{
|
|
404
404
|
const bot = getMockBot();
|
|
405
|
-
await adapter.editInlineKeyboard(
|
|
406
|
-
expect(bot.telegram.editMessageReplyMarkup).toHaveBeenCalledWith(
|
|
405
|
+
await adapter.editInlineKeyboard("12345", 99, null);
|
|
406
|
+
expect(bot.telegram.editMessageReplyMarkup).toHaveBeenCalledWith("12345", 99, undefined, undefined);
|
|
407
407
|
});
|
|
408
408
|
});
|
|
409
|
-
describe(
|
|
410
|
-
it(
|
|
409
|
+
describe("answerCallback", ()=>{
|
|
410
|
+
it("forwards id and optional text to telegraf", async ()=>{
|
|
411
411
|
const bot = getMockBot();
|
|
412
|
-
await adapter.answerCallback(
|
|
413
|
-
expect(bot.telegram.answerCbQuery).toHaveBeenCalledWith(
|
|
412
|
+
await adapter.answerCallback("cbq-1", "thanks");
|
|
413
|
+
expect(bot.telegram.answerCbQuery).toHaveBeenCalledWith("cbq-1", "thanks");
|
|
414
414
|
});
|
|
415
415
|
});
|
|
416
|
-
describe(
|
|
417
|
-
it(
|
|
416
|
+
describe("onCallback", ()=>{
|
|
417
|
+
it("invokes the registered handler with a normalized IncomingCallback", async ()=>{
|
|
418
418
|
const handler = vi.fn().mockResolvedValue(undefined);
|
|
419
419
|
adapter.onCallback(handler);
|
|
420
420
|
await adapter.start();
|
|
421
421
|
const bot = getMockBot();
|
|
422
|
-
await bot._triggerCallback(12345, 67890,
|
|
422
|
+
await bot._triggerCallback(12345, 67890, "q:abc:o:1", 777);
|
|
423
423
|
expect(handler).toHaveBeenCalledOnce();
|
|
424
424
|
const cb = handler.mock.calls[0][0];
|
|
425
|
-
expect(cb.channelType).toBe(
|
|
426
|
-
expect(cb.chatId).toBe(
|
|
427
|
-
expect(cb.userId).toBe(
|
|
425
|
+
expect(cb.channelType).toBe("telegram");
|
|
426
|
+
expect(cb.chatId).toBe("12345");
|
|
427
|
+
expect(cb.userId).toBe("67890");
|
|
428
428
|
expect(cb.messageId).toBe(777);
|
|
429
|
-
expect(cb.callbackData).toBe(
|
|
430
|
-
expect(cb.callbackQueryId).toBe(
|
|
429
|
+
expect(cb.callbackData).toBe("q:abc:o:1");
|
|
430
|
+
expect(cb.callbackQueryId).toBe("cbq-test");
|
|
431
431
|
});
|
|
432
|
-
it(
|
|
432
|
+
it("answers callbacks when no handler is registered", async ()=>{
|
|
433
433
|
await adapter.start();
|
|
434
434
|
const bot = getMockBot();
|
|
435
|
-
const ctx = await bot._triggerCallback(12345, 67890,
|
|
435
|
+
const ctx = await bot._triggerCallback(12345, 67890, "q:x:o:0");
|
|
436
436
|
expect(ctx.answerCbQuery).toHaveBeenCalled();
|
|
437
437
|
});
|
|
438
|
-
it(
|
|
439
|
-
adapter.onCallback(vi.fn().mockRejectedValue(new Error(
|
|
438
|
+
it("answers with error when handler rejects", async ()=>{
|
|
439
|
+
adapter.onCallback(vi.fn().mockRejectedValue(new Error("boom")));
|
|
440
440
|
await adapter.start();
|
|
441
441
|
const bot = getMockBot();
|
|
442
|
-
const ctx = await bot._triggerCallback(12345, 67890,
|
|
443
|
-
expect(ctx.answerCbQuery).toHaveBeenCalledWith(
|
|
442
|
+
const ctx = await bot._triggerCallback(12345, 67890, "q:x:o:0");
|
|
443
|
+
expect(ctx.answerCbQuery).toHaveBeenCalledWith("Error");
|
|
444
444
|
});
|
|
445
445
|
});
|
|
446
|
-
describe(
|
|
447
|
-
it(
|
|
446
|
+
describe("isHealthy", ()=>{
|
|
447
|
+
it("should return true after start", async ()=>{
|
|
448
448
|
await adapter.start();
|
|
449
449
|
expect(await adapter.isHealthy()).toBe(true);
|
|
450
450
|
});
|
|
451
|
-
it(
|
|
451
|
+
it("should return false before start", async ()=>{
|
|
452
452
|
expect(await adapter.isHealthy()).toBe(false);
|
|
453
453
|
});
|
|
454
|
-
it(
|
|
454
|
+
it("should return false after stop", async ()=>{
|
|
455
455
|
await adapter.start();
|
|
456
456
|
await adapter.stop();
|
|
457
457
|
expect(await adapter.isHealthy()).toBe(false);
|