Skip to content

Commit 0c2b7c9

Browse files
authored
Merge pull request #19 from elizaos-plugins/fix/telegram-button-handling-and-logging
fix: resolve button handling crash and logging errors
2 parents bbc5903 + 1b28012 commit 0c2b7c9

8 files changed

Lines changed: 719 additions & 316 deletions

File tree

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
import { afterEach, describe, expect, it, mock, spyOn } from 'bun:test';
2+
3+
// Mock telegraf before importing
4+
mock.module('telegraf', () => ({
5+
Markup: {
6+
button: {
7+
url: mock((text: string, url: string) => ({ text, url, type: 'url' })),
8+
login: mock((text: string, url: string) => ({ text, url, type: 'login' })),
9+
},
10+
},
11+
}));
12+
13+
// Mock logger
14+
const warnSpy = mock();
15+
mock.module('@elizaos/core', () => ({
16+
logger: {
17+
debug: mock(),
18+
info: mock(),
19+
warn: warnSpy,
20+
error: mock(),
21+
},
22+
}));
23+
24+
import {
25+
convertToTelegramButtons,
26+
convertMarkdownToTelegram,
27+
splitMessage,
28+
cleanText,
29+
} from '../src/utils';
30+
import type { Button } from '../src/types';
31+
32+
describe('Telegram Utils', () => {
33+
afterEach(() => {
34+
mock.restore();
35+
});
36+
37+
describe('splitMessage', () => {
38+
it('should not split message within limit', () => {
39+
const message = 'Hello World';
40+
const chunks = splitMessage(message, 4096);
41+
expect(chunks).toEqual(['Hello World']);
42+
});
43+
44+
it('should handle empty string', () => {
45+
const chunks = splitMessage('');
46+
expect(chunks).toEqual([]);
47+
});
48+
49+
it('should split at line boundaries', () => {
50+
const line1 = 'a'.repeat(3000);
51+
const line2 = 'b'.repeat(2000);
52+
const message = `${line1}\n${line2}`;
53+
const chunks = splitMessage(message, 4096);
54+
expect(chunks.length).toBe(2);
55+
expect(chunks[0]).toBe(line1);
56+
expect(chunks[1]).toBe(line2);
57+
});
58+
});
59+
60+
describe('convertMarkdownToTelegram', () => {
61+
it('should handle text without special characters', () => {
62+
const input = 'Hello World 123';
63+
expect(convertMarkdownToTelegram(input)).toBe(input);
64+
});
65+
66+
it('should handle empty string', () => {
67+
expect(convertMarkdownToTelegram('')).toBe('');
68+
});
69+
70+
it('should convert headers to bold text', () => {
71+
const result = convertMarkdownToTelegram('# Header 1');
72+
expect(result).toContain('*Header 1*');
73+
});
74+
75+
it('should convert bold text correctly', () => {
76+
const result = convertMarkdownToTelegram('This is **bold text**');
77+
expect(result).toBe('This is *bold text*');
78+
});
79+
80+
it('should convert italic text correctly', () => {
81+
const result = convertMarkdownToTelegram('This is *italic text*');
82+
expect(result).toBe('This is _italic text_');
83+
});
84+
85+
it('should convert strikethrough text correctly', () => {
86+
const result = convertMarkdownToTelegram('This is ~~strikethrough text~~');
87+
expect(result).toBe('This is ~strikethrough text~');
88+
});
89+
90+
it('should convert inline code correctly', () => {
91+
const result = convertMarkdownToTelegram('This is `inline code`');
92+
expect(result).toBe('This is `inline code`');
93+
});
94+
95+
it('should escape special characters correctly', () => {
96+
const result = convertMarkdownToTelegram(
97+
'These chars: _ * [ ] ( ) ~ ` > # + - = | { } . ! \\'
98+
);
99+
expect(result).toBe(
100+
'These chars: \\_ \\* \\[ \\] \\( \\) \\~ \\` \\> \\# \\+ \\- \\= \\| \\{ \\} \\. \\! \\\\'
101+
);
102+
});
103+
104+
it('should handle mixed formatting correctly', () => {
105+
const result = convertMarkdownToTelegram(
106+
'**Bold** and *italic* and `code` and ~~strikethrough~~'
107+
);
108+
expect(result).toBe('*Bold* and _italic_ and `code` and ~strikethrough~');
109+
});
110+
});
111+
112+
describe('cleanText', () => {
113+
it('should remove NULL characters', () => {
114+
const result = cleanText('Hello\u0000World');
115+
expect(result).toBe('HelloWorld');
116+
});
117+
118+
it('should handle empty strings', () => {
119+
expect(cleanText('')).toBe('');
120+
expect(cleanText(null)).toBe('');
121+
expect(cleanText(undefined)).toBe('');
122+
});
123+
});
124+
125+
describe('convertToTelegramButtons', () => {
126+
it('should convert valid URL buttons correctly', () => {
127+
const buttons: Button[] = [{ kind: 'url', text: 'Click me', url: 'https://example.com' }];
128+
129+
const result = convertToTelegramButtons(buttons);
130+
131+
expect(result).toHaveLength(1);
132+
expect(result[0]).toMatchObject({
133+
text: 'Click me',
134+
url: 'https://example.com',
135+
type: 'url',
136+
});
137+
});
138+
139+
it('should convert valid login buttons correctly', () => {
140+
const buttons: Button[] = [
141+
{ kind: 'login', text: 'Login', url: 'https://login.example.com' },
142+
];
143+
144+
const result = convertToTelegramButtons(buttons);
145+
146+
expect(result).toHaveLength(1);
147+
expect(result[0]).toMatchObject({
148+
text: 'Login',
149+
url: 'https://login.example.com',
150+
type: 'login',
151+
});
152+
});
153+
154+
it('should handle multiple buttons', () => {
155+
const buttons: Button[] = [
156+
{ kind: 'url', text: 'Button 1', url: 'https://example1.com' },
157+
{ kind: 'url', text: 'Button 2', url: 'https://example2.com' },
158+
{ kind: 'login', text: 'Login', url: 'https://login.com' },
159+
];
160+
161+
const result = convertToTelegramButtons(buttons);
162+
163+
expect(result).toHaveLength(3);
164+
});
165+
166+
// THE BUG WE FIXED: Unknown button kinds should not crash
167+
it('should handle unknown button kinds with default fallback', () => {
168+
const buttons: Button[] = [
169+
{ kind: 'unknown' as any, text: 'Unknown Type', url: 'https://example.com' },
170+
];
171+
172+
const result = convertToTelegramButtons(buttons);
173+
174+
expect(result).toHaveLength(1);
175+
expect(result[0]).toMatchObject({
176+
text: 'Unknown Type',
177+
url: 'https://example.com',
178+
type: 'url', // Should default to URL button
179+
});
180+
});
181+
182+
// THE BUG WE FIXED: Invalid buttons should be skipped, not crash
183+
it('should skip buttons with missing text', () => {
184+
const buttons: Button[] = [
185+
{ kind: 'url', text: '', url: 'https://example.com' },
186+
{ kind: 'url', text: 'Valid', url: 'https://example.com' },
187+
];
188+
189+
const result = convertToTelegramButtons(buttons);
190+
191+
expect(result).toHaveLength(1);
192+
expect(result[0].text).toBe('Valid');
193+
});
194+
195+
it('should skip buttons with missing url', () => {
196+
const buttons: Button[] = [
197+
{ kind: 'url', text: 'Click', url: '' },
198+
{ kind: 'url', text: 'Valid', url: 'https://example.com' },
199+
];
200+
201+
const result = convertToTelegramButtons(buttons);
202+
203+
expect(result).toHaveLength(1);
204+
expect(result[0].text).toBe('Valid');
205+
});
206+
207+
it('should skip null/undefined buttons in array', () => {
208+
const buttons: (Button | null | undefined)[] = [
209+
null,
210+
{ kind: 'url', text: 'Valid', url: 'https://example.com' },
211+
undefined,
212+
{ kind: 'url', text: 'Also Valid', url: 'https://example2.com' },
213+
];
214+
215+
const result = convertToTelegramButtons(buttons as Button[]);
216+
217+
expect(result).toHaveLength(2);
218+
});
219+
220+
it('should handle empty button array', () => {
221+
const result = convertToTelegramButtons([]);
222+
expect(result).toHaveLength(0);
223+
});
224+
225+
it('should handle null input', () => {
226+
const result = convertToTelegramButtons(null);
227+
expect(result).toHaveLength(0);
228+
});
229+
230+
it('should handle undefined input', () => {
231+
const result = convertToTelegramButtons(undefined);
232+
expect(result).toHaveLength(0);
233+
});
234+
235+
// Edge case: All buttons invalid
236+
it('should return empty array when all buttons are invalid', () => {
237+
const buttons: Button[] = [
238+
{ kind: 'url', text: '', url: 'https://example.com' },
239+
{ kind: 'url', text: 'Click', url: '' },
240+
null as any,
241+
];
242+
243+
const result = convertToTelegramButtons(buttons);
244+
245+
expect(result).toHaveLength(0);
246+
});
247+
});
248+
});

0 commit comments

Comments
 (0)