signal-sdk 0.0.8 → 0.1.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.
- package/README.md +175 -59
- package/dist/SignalBot.d.ts +108 -0
- package/dist/SignalBot.js +811 -0
- package/dist/SignalCli.d.ts +205 -0
- package/dist/SignalCli.js +967 -0
- package/dist/__tests__/SignalBot.additional.test.d.ts +5 -0
- package/dist/__tests__/SignalBot.additional.test.js +333 -0
- package/dist/__tests__/SignalBot.test.d.ts +1 -0
- package/dist/__tests__/SignalBot.test.js +102 -0
- package/dist/__tests__/SignalCli.integration.test.d.ts +5 -0
- package/dist/__tests__/SignalCli.integration.test.js +218 -0
- package/dist/__tests__/SignalCli.methods.test.d.ts +5 -0
- package/dist/__tests__/SignalCli.methods.test.js +470 -0
- package/dist/__tests__/SignalCli.test.d.ts +1 -0
- package/dist/__tests__/SignalCli.test.js +479 -0
- package/dist/__tests__/config.test.d.ts +5 -0
- package/dist/__tests__/config.test.js +252 -0
- package/dist/__tests__/errors.test.d.ts +5 -0
- package/dist/__tests__/errors.test.js +276 -0
- package/dist/__tests__/retry.test.d.ts +4 -0
- package/dist/__tests__/retry.test.js +123 -0
- package/dist/__tests__/validators.test.d.ts +4 -0
- package/dist/__tests__/validators.test.js +147 -0
- package/dist/config.d.ts +67 -0
- package/dist/config.js +111 -0
- package/dist/errors.d.ts +32 -0
- package/dist/errors.js +75 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +26 -0
- package/dist/interfaces.d.ts +1073 -0
- package/dist/interfaces.js +11 -0
- package/dist/retry.d.ts +56 -0
- package/dist/retry.js +135 -0
- package/dist/validators.d.ts +59 -0
- package/dist/validators.js +170 -0
- package/package.json +5 -6
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Additional tests for SignalBot
|
|
4
|
+
* Covers message handling, commands, and edge cases
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const SignalBot_1 = require("../SignalBot");
|
|
8
|
+
describe('SignalBot Additional Tests', () => {
|
|
9
|
+
let bot;
|
|
10
|
+
let mockConfig;
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
mockConfig = {
|
|
13
|
+
phoneNumber: '+1234567890',
|
|
14
|
+
admins: ['+0987654321'],
|
|
15
|
+
settings: {
|
|
16
|
+
commandPrefix: '/',
|
|
17
|
+
autoReact: true,
|
|
18
|
+
logMessages: true,
|
|
19
|
+
welcomeNewMembers: true,
|
|
20
|
+
cooldownSeconds: 2,
|
|
21
|
+
maxMessageLength: 1000
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
afterEach(async () => {
|
|
26
|
+
if (bot) {
|
|
27
|
+
await bot.stop();
|
|
28
|
+
// Wait for any pending async operations to complete
|
|
29
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
describe('Command Management', () => {
|
|
33
|
+
test('should add custom command and execute it', () => {
|
|
34
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
35
|
+
const customHandler = jest.fn().mockResolvedValue('Custom response');
|
|
36
|
+
bot.addCommand({
|
|
37
|
+
name: 'custom',
|
|
38
|
+
description: 'Custom command',
|
|
39
|
+
handler: customHandler
|
|
40
|
+
});
|
|
41
|
+
const commands = bot.getCommands();
|
|
42
|
+
expect(commands.find(c => c.name === 'custom')).toBeDefined();
|
|
43
|
+
});
|
|
44
|
+
test('should remove command successfully', () => {
|
|
45
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
46
|
+
bot.addCommand({
|
|
47
|
+
name: 'temp',
|
|
48
|
+
description: 'Temporary command',
|
|
49
|
+
handler: async () => 'temp'
|
|
50
|
+
});
|
|
51
|
+
expect(bot.removeCommand('temp')).toBe(true);
|
|
52
|
+
expect(bot.removeCommand('nonexistent')).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
test('should prevent duplicate commands', () => {
|
|
55
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
56
|
+
bot.addCommand({
|
|
57
|
+
name: 'test',
|
|
58
|
+
description: 'Test command',
|
|
59
|
+
handler: async () => 'test1'
|
|
60
|
+
});
|
|
61
|
+
bot.addCommand({
|
|
62
|
+
name: 'test',
|
|
63
|
+
description: 'Test command 2',
|
|
64
|
+
handler: async () => 'test2'
|
|
65
|
+
});
|
|
66
|
+
const commands = bot.getCommands();
|
|
67
|
+
const testCommands = commands.filter(c => c.name === 'test');
|
|
68
|
+
expect(testCommands.length).toBe(1);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe('Admin Management', () => {
|
|
72
|
+
test('should identify admin correctly', () => {
|
|
73
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
74
|
+
expect(bot.isAdmin('+0987654321')).toBe(true);
|
|
75
|
+
expect(bot.isAdmin('+1111111111')).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
describe('Statistics', () => {
|
|
79
|
+
test('should track bot statistics', () => {
|
|
80
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
81
|
+
const stats = bot.getStats();
|
|
82
|
+
expect(stats).toHaveProperty('messagesReceived');
|
|
83
|
+
expect(stats).toHaveProperty('commandsExecuted');
|
|
84
|
+
expect(stats).toHaveProperty('startTime');
|
|
85
|
+
expect(stats).toHaveProperty('lastActivity');
|
|
86
|
+
expect(stats.messagesReceived).toBe(0);
|
|
87
|
+
expect(stats.commandsExecuted).toBe(0);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
describe('Group Management', () => {
|
|
91
|
+
test('should get bot group ID', () => {
|
|
92
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
93
|
+
const groupId = bot.getBotGroupId();
|
|
94
|
+
expect(groupId).toBeNull(); // Not set until bot starts
|
|
95
|
+
});
|
|
96
|
+
test('should create bot without group config', () => {
|
|
97
|
+
bot = new SignalBot_1.SignalBot({
|
|
98
|
+
phoneNumber: '+1234567890'
|
|
99
|
+
});
|
|
100
|
+
expect(bot).toBeDefined();
|
|
101
|
+
expect(bot.getBotGroupId()).toBeNull();
|
|
102
|
+
});
|
|
103
|
+
test('should create bot with group config', () => {
|
|
104
|
+
bot = new SignalBot_1.SignalBot({
|
|
105
|
+
phoneNumber: '+1234567890',
|
|
106
|
+
group: {
|
|
107
|
+
name: 'Test Group',
|
|
108
|
+
description: 'Test Description',
|
|
109
|
+
createIfNotExists: true,
|
|
110
|
+
initialMembers: ['+1111111111', '+2222222222']
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
expect(bot).toBeDefined();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
describe('Message Sending', () => {
|
|
117
|
+
test('should queue message for sending', async () => {
|
|
118
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
119
|
+
const mockSignalCli = bot.getSignalCli();
|
|
120
|
+
const sendMessageSpy = jest.spyOn(mockSignalCli, 'sendMessage').mockResolvedValue({
|
|
121
|
+
results: [{ type: 'SUCCESS' }],
|
|
122
|
+
timestamp: Date.now()
|
|
123
|
+
});
|
|
124
|
+
await bot.sendMessage('+1111111111', 'Test message');
|
|
125
|
+
// Wait for queue to process
|
|
126
|
+
await new Promise(resolve => setTimeout(resolve, 150));
|
|
127
|
+
expect(sendMessageSpy).toHaveBeenCalledWith('+1111111111', 'Test message');
|
|
128
|
+
});
|
|
129
|
+
test('should queue reaction for sending', async () => {
|
|
130
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
131
|
+
const mockSignalCli = bot.getSignalCli();
|
|
132
|
+
const sendReactionSpy = jest.spyOn(mockSignalCli, 'sendReaction').mockResolvedValue({
|
|
133
|
+
results: [{ type: 'SUCCESS' }],
|
|
134
|
+
timestamp: Date.now()
|
|
135
|
+
});
|
|
136
|
+
await bot.sendReaction('+1111111111', '+2222222222', 123456, '👍');
|
|
137
|
+
// Wait for queue to process
|
|
138
|
+
await new Promise(resolve => setTimeout(resolve, 150));
|
|
139
|
+
expect(sendReactionSpy).toHaveBeenCalledWith('+1111111111', '+2222222222', 123456, '👍');
|
|
140
|
+
});
|
|
141
|
+
test('should handle message with attachments', async () => {
|
|
142
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
143
|
+
const mockSignalCli = bot.getSignalCli();
|
|
144
|
+
const sendMessageSpy = jest.spyOn(mockSignalCli, 'sendMessage').mockResolvedValue({
|
|
145
|
+
results: [{ type: 'SUCCESS' }],
|
|
146
|
+
timestamp: Date.now()
|
|
147
|
+
});
|
|
148
|
+
await bot.sendMessageWithAttachment('+1111111111', 'Message with files', ['file1.txt', 'file2.jpg']);
|
|
149
|
+
// Wait for queue to process
|
|
150
|
+
await new Promise(resolve => setTimeout(resolve, 150));
|
|
151
|
+
expect(sendMessageSpy).toHaveBeenCalledWith('+1111111111', 'Message with files', {
|
|
152
|
+
attachments: ['file1.txt', 'file2.jpg']
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
describe('Configuration', () => {
|
|
157
|
+
test('should use default settings when not provided', () => {
|
|
158
|
+
bot = new SignalBot_1.SignalBot({
|
|
159
|
+
phoneNumber: '+1234567890'
|
|
160
|
+
});
|
|
161
|
+
const commands = bot.getCommands();
|
|
162
|
+
expect(commands.length).toBeGreaterThan(0); // Should have default commands
|
|
163
|
+
});
|
|
164
|
+
test('should use custom command prefix', () => {
|
|
165
|
+
bot = new SignalBot_1.SignalBot({
|
|
166
|
+
phoneNumber: '+1234567890',
|
|
167
|
+
settings: {
|
|
168
|
+
commandPrefix: '!'
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
expect(bot).toBeDefined();
|
|
172
|
+
});
|
|
173
|
+
test('should handle all settings', () => {
|
|
174
|
+
bot = new SignalBot_1.SignalBot({
|
|
175
|
+
phoneNumber: '+1234567890',
|
|
176
|
+
admins: ['+1111111111'],
|
|
177
|
+
settings: {
|
|
178
|
+
commandPrefix: '$',
|
|
179
|
+
autoReact: false,
|
|
180
|
+
logMessages: false,
|
|
181
|
+
welcomeNewMembers: false,
|
|
182
|
+
cooldownSeconds: 5,
|
|
183
|
+
maxMessageLength: 2000
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
expect(bot).toBeDefined();
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
describe('Signal CLI Access', () => {
|
|
190
|
+
test('should provide access to underlying SignalCli', () => {
|
|
191
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
192
|
+
const signalCli = bot.getSignalCli();
|
|
193
|
+
expect(signalCli).toBeDefined();
|
|
194
|
+
expect(signalCli.connect).toBeDefined();
|
|
195
|
+
expect(signalCli.disconnect).toBeDefined();
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
describe('Bot State', () => {
|
|
199
|
+
test('should track running state', () => {
|
|
200
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
201
|
+
// Bot is not running initially
|
|
202
|
+
expect(bot.getStats().startTime).toBeDefined();
|
|
203
|
+
});
|
|
204
|
+
test('should handle stop when not running', async () => {
|
|
205
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
206
|
+
await expect(bot.stop()).resolves.toBeUndefined();
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
describe('Image Download Feature', () => {
|
|
210
|
+
test('should have downloadImageFromUrl method', () => {
|
|
211
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
212
|
+
expect(bot.downloadImageFromUrl).toBeDefined();
|
|
213
|
+
expect(typeof bot.downloadImageFromUrl).toBe('function');
|
|
214
|
+
});
|
|
215
|
+
test('should have sendMessageWithImage method', () => {
|
|
216
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
217
|
+
expect(bot.sendMessageWithImage).toBeDefined();
|
|
218
|
+
expect(typeof bot.sendMessageWithImage).toBe('function');
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
describe('Event Emitters', () => {
|
|
222
|
+
test('should emit ready event structure', (done) => {
|
|
223
|
+
bot = new SignalBot_1.SignalBot({
|
|
224
|
+
phoneNumber: '+1234567890',
|
|
225
|
+
group: {
|
|
226
|
+
name: 'Test Group',
|
|
227
|
+
createIfNotExists: false
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
const mockSignalCli = bot.getSignalCli();
|
|
231
|
+
jest.spyOn(mockSignalCli, 'listDevices').mockResolvedValue([
|
|
232
|
+
{ id: 1, name: 'Test Device', created: Date.now(), lastSeen: Date.now() }
|
|
233
|
+
]);
|
|
234
|
+
jest.spyOn(mockSignalCli, 'connect').mockResolvedValue(undefined);
|
|
235
|
+
jest.spyOn(mockSignalCli, 'on').mockReturnValue(mockSignalCli);
|
|
236
|
+
bot.on('ready', () => {
|
|
237
|
+
done();
|
|
238
|
+
});
|
|
239
|
+
bot.start().catch(done);
|
|
240
|
+
});
|
|
241
|
+
test('should have stopped event', async () => {
|
|
242
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
243
|
+
const stoppedHandler = jest.fn();
|
|
244
|
+
bot.on('stopped', stoppedHandler);
|
|
245
|
+
await bot.stop();
|
|
246
|
+
expect(stoppedHandler).toHaveBeenCalled();
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
describe('Default Commands', () => {
|
|
250
|
+
test('should include help command', () => {
|
|
251
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
252
|
+
const commands = bot.getCommands();
|
|
253
|
+
const helpCmd = commands.find(c => c.name === 'help');
|
|
254
|
+
expect(helpCmd).toBeDefined();
|
|
255
|
+
expect(helpCmd?.description).toBeDefined();
|
|
256
|
+
});
|
|
257
|
+
test('should include stats command', () => {
|
|
258
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
259
|
+
const commands = bot.getCommands();
|
|
260
|
+
const statsCmd = commands.find(c => c.name === 'stats');
|
|
261
|
+
expect(statsCmd).toBeDefined();
|
|
262
|
+
expect(statsCmd?.description).toBeDefined();
|
|
263
|
+
});
|
|
264
|
+
test('should include ping command', () => {
|
|
265
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
266
|
+
const commands = bot.getCommands();
|
|
267
|
+
const pingCmd = commands.find(c => c.name === 'ping');
|
|
268
|
+
expect(pingCmd).toBeDefined();
|
|
269
|
+
expect(pingCmd?.description).toBeDefined();
|
|
270
|
+
});
|
|
271
|
+
test('should include info command', () => {
|
|
272
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
273
|
+
const commands = bot.getCommands();
|
|
274
|
+
const infoCmd = commands.find(c => c.name === 'info');
|
|
275
|
+
expect(infoCmd).toBeDefined();
|
|
276
|
+
expect(infoCmd?.description).toBeDefined();
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
describe('Group Configuration with Avatar', () => {
|
|
280
|
+
test('should handle group config with avatar URL', () => {
|
|
281
|
+
bot = new SignalBot_1.SignalBot({
|
|
282
|
+
phoneNumber: '+1234567890',
|
|
283
|
+
group: {
|
|
284
|
+
name: 'Test Group',
|
|
285
|
+
description: 'Test Description',
|
|
286
|
+
createIfNotExists: true,
|
|
287
|
+
initialMembers: ['+1111111111'],
|
|
288
|
+
avatar: 'https://example.com/avatar.jpg'
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
expect(bot).toBeDefined();
|
|
292
|
+
});
|
|
293
|
+
test('should handle group config with local avatar path', () => {
|
|
294
|
+
bot = new SignalBot_1.SignalBot({
|
|
295
|
+
phoneNumber: '+1234567890',
|
|
296
|
+
group: {
|
|
297
|
+
name: 'Test Group',
|
|
298
|
+
description: 'Test Description',
|
|
299
|
+
createIfNotExists: true,
|
|
300
|
+
initialMembers: ['+1111111111'],
|
|
301
|
+
avatar: '/path/to/local/avatar.jpg'
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
expect(bot).toBeDefined();
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
describe('Edge Cases', () => {
|
|
308
|
+
test('should handle empty admins list', () => {
|
|
309
|
+
bot = new SignalBot_1.SignalBot({
|
|
310
|
+
phoneNumber: '+1234567890',
|
|
311
|
+
admins: []
|
|
312
|
+
});
|
|
313
|
+
expect(bot.isAdmin('+1234567890')).toBe(false);
|
|
314
|
+
});
|
|
315
|
+
test('should handle undefined settings', () => {
|
|
316
|
+
bot = new SignalBot_1.SignalBot({
|
|
317
|
+
phoneNumber: '+1234567890',
|
|
318
|
+
settings: undefined
|
|
319
|
+
});
|
|
320
|
+
expect(bot).toBeDefined();
|
|
321
|
+
});
|
|
322
|
+
test('should handle partial settings', () => {
|
|
323
|
+
bot = new SignalBot_1.SignalBot({
|
|
324
|
+
phoneNumber: '+1234567890',
|
|
325
|
+
settings: {
|
|
326
|
+
commandPrefix: '!'
|
|
327
|
+
// Other settings will use defaults
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
expect(bot).toBeDefined();
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const SignalBot_1 = require("../SignalBot");
|
|
4
|
+
describe('SignalBot', () => {
|
|
5
|
+
let bot;
|
|
6
|
+
let mockConfig;
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
mockConfig = {
|
|
9
|
+
phoneNumber: '+1234567890',
|
|
10
|
+
admins: ['+0987654321'],
|
|
11
|
+
group: {
|
|
12
|
+
name: 'Test Bot Group',
|
|
13
|
+
createIfNotExists: false
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
afterEach(async () => {
|
|
18
|
+
if (bot) {
|
|
19
|
+
await bot.stop();
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
test('should create bot with minimal config', () => {
|
|
23
|
+
bot = new SignalBot_1.SignalBot({
|
|
24
|
+
phoneNumber: '+1234567890'
|
|
25
|
+
});
|
|
26
|
+
expect(bot).toBeDefined();
|
|
27
|
+
expect(bot.isAdmin('+1234567890')).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
test('should create bot with full config', () => {
|
|
30
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
31
|
+
expect(bot).toBeDefined();
|
|
32
|
+
expect(bot.isAdmin('+0987654321')).toBe(true);
|
|
33
|
+
expect(bot.getBotGroupId()).toBeNull();
|
|
34
|
+
});
|
|
35
|
+
test('should add and remove commands', () => {
|
|
36
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
37
|
+
const testCommand = {
|
|
38
|
+
name: 'test',
|
|
39
|
+
description: 'Test command',
|
|
40
|
+
handler: jest.fn()
|
|
41
|
+
};
|
|
42
|
+
bot.addCommand(testCommand);
|
|
43
|
+
const commands = bot.getCommands();
|
|
44
|
+
expect(commands.some((cmd) => cmd.name === 'test')).toBe(true);
|
|
45
|
+
const removed = bot.removeCommand('test');
|
|
46
|
+
expect(removed).toBe(true);
|
|
47
|
+
const commandsAfter = bot.getCommands();
|
|
48
|
+
expect(commandsAfter.some((cmd) => cmd.name === 'test')).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
test('should have default commands', () => {
|
|
51
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
52
|
+
const commands = bot.getCommands();
|
|
53
|
+
const commandNames = commands.map((cmd) => cmd.name);
|
|
54
|
+
expect(commandNames).toContain('help');
|
|
55
|
+
expect(commandNames).toContain('stats');
|
|
56
|
+
expect(commandNames).toContain('ping');
|
|
57
|
+
expect(commandNames).toContain('info');
|
|
58
|
+
});
|
|
59
|
+
test('should emit ready event when started', (done) => {
|
|
60
|
+
bot = new SignalBot_1.SignalBot({
|
|
61
|
+
phoneNumber: '+1234567890',
|
|
62
|
+
group: {
|
|
63
|
+
name: 'Test Group',
|
|
64
|
+
createIfNotExists: false
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
// Mock SignalCli methods to avoid actual Signal operations
|
|
68
|
+
const mockSignalCli = bot.getSignalCli();
|
|
69
|
+
jest.spyOn(mockSignalCli, 'listDevices').mockResolvedValue([
|
|
70
|
+
{ id: 1, name: 'Test Device', created: Date.now(), lastSeen: Date.now() }
|
|
71
|
+
]);
|
|
72
|
+
jest.spyOn(mockSignalCli, 'startDaemon').mockImplementation(() => { });
|
|
73
|
+
jest.spyOn(mockSignalCli, 'connect').mockResolvedValue(undefined);
|
|
74
|
+
jest.spyOn(mockSignalCli, 'on').mockReturnValue(mockSignalCli);
|
|
75
|
+
bot.on('ready', () => {
|
|
76
|
+
expect(true).toBe(true);
|
|
77
|
+
done();
|
|
78
|
+
});
|
|
79
|
+
bot.start().catch(done);
|
|
80
|
+
});
|
|
81
|
+
test('should get stats', () => {
|
|
82
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
83
|
+
const stats = bot.getStats();
|
|
84
|
+
expect(stats).toHaveProperty('messagesReceived');
|
|
85
|
+
expect(stats).toHaveProperty('commandsExecuted');
|
|
86
|
+
expect(stats).toHaveProperty('startTime');
|
|
87
|
+
expect(stats).toHaveProperty('lastActivity');
|
|
88
|
+
expect(stats).toHaveProperty('activeUsers');
|
|
89
|
+
expect(stats.messagesReceived).toBe(0);
|
|
90
|
+
expect(stats.commandsExecuted).toBe(0);
|
|
91
|
+
expect(stats.activeUsers).toBe(0);
|
|
92
|
+
});
|
|
93
|
+
test('should handle admin permissions', () => {
|
|
94
|
+
bot = new SignalBot_1.SignalBot({
|
|
95
|
+
phoneNumber: '+1234567890',
|
|
96
|
+
admins: ['+0987654321', '+1111111111']
|
|
97
|
+
});
|
|
98
|
+
expect(bot.isAdmin('+0987654321')).toBe(true);
|
|
99
|
+
expect(bot.isAdmin('+1111111111')).toBe(true);
|
|
100
|
+
expect(bot.isAdmin('+9999999999')).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Integration tests for SignalCli
|
|
4
|
+
* Tests complex scenarios and edge cases
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const SignalCli_1 = require("../SignalCli");
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
jest.mock('child_process');
|
|
10
|
+
describe('SignalCli Integration Tests', () => {
|
|
11
|
+
let signalCli;
|
|
12
|
+
let mockProcess;
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
mockProcess = {
|
|
15
|
+
stdout: {
|
|
16
|
+
on: jest.fn(),
|
|
17
|
+
once: jest.fn(),
|
|
18
|
+
},
|
|
19
|
+
stderr: {
|
|
20
|
+
on: jest.fn(),
|
|
21
|
+
},
|
|
22
|
+
stdin: {
|
|
23
|
+
write: jest.fn(),
|
|
24
|
+
},
|
|
25
|
+
on: jest.fn(),
|
|
26
|
+
once: jest.fn(),
|
|
27
|
+
kill: jest.fn(),
|
|
28
|
+
killed: false,
|
|
29
|
+
};
|
|
30
|
+
const spawnMock = child_process_1.spawn;
|
|
31
|
+
spawnMock.mockReturnValue(mockProcess);
|
|
32
|
+
signalCli = new SignalCli_1.SignalCli('signal-cli', '+1234567890');
|
|
33
|
+
});
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
if (signalCli) {
|
|
36
|
+
signalCli.disconnect();
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
describe('Connection Lifecycle', () => {
|
|
40
|
+
it('should handle multiple connect/disconnect cycles', async () => {
|
|
41
|
+
mockProcess.stdout.once.mockImplementation((event, callback) => {
|
|
42
|
+
if (event === 'data')
|
|
43
|
+
callback();
|
|
44
|
+
});
|
|
45
|
+
await signalCli.connect();
|
|
46
|
+
signalCli.disconnect();
|
|
47
|
+
await signalCli.connect();
|
|
48
|
+
signalCli.disconnect();
|
|
49
|
+
expect(mockProcess.kill).toHaveBeenCalledTimes(2);
|
|
50
|
+
});
|
|
51
|
+
it('should handle graceful shutdown timeout', async () => {
|
|
52
|
+
mockProcess.stdout.once.mockImplementation((event, callback) => {
|
|
53
|
+
if (event === 'data')
|
|
54
|
+
callback();
|
|
55
|
+
});
|
|
56
|
+
await signalCli.connect();
|
|
57
|
+
// Don't emit close event to simulate hanging process
|
|
58
|
+
let closeCallback;
|
|
59
|
+
mockProcess.once.mockImplementation((event, callback) => {
|
|
60
|
+
if (event === 'close') {
|
|
61
|
+
closeCallback = callback;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
const shutdownPromise = signalCli.gracefulShutdown();
|
|
65
|
+
// Simulate timeout and force kill
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
if (closeCallback)
|
|
68
|
+
closeCallback(0);
|
|
69
|
+
}, 100);
|
|
70
|
+
await expect(shutdownPromise).resolves.toBeUndefined();
|
|
71
|
+
}, 10000); // Increase timeout to 10 seconds
|
|
72
|
+
it('should handle stderr data with different log levels', async () => {
|
|
73
|
+
const handleStderrData = signalCli.handleStderrData.bind(signalCli);
|
|
74
|
+
const errorHandler = jest.fn();
|
|
75
|
+
const logHandler = jest.fn();
|
|
76
|
+
signalCli.on('error', errorHandler);
|
|
77
|
+
signalCli.on('log', logHandler);
|
|
78
|
+
handleStderrData('ERROR Component - Critical error');
|
|
79
|
+
expect(errorHandler).toHaveBeenCalled();
|
|
80
|
+
handleStderrData('WARN Component - Warning message');
|
|
81
|
+
expect(logHandler).toHaveBeenCalledWith(expect.objectContaining({ level: 'warn' }));
|
|
82
|
+
handleStderrData('INFO Component - Info message');
|
|
83
|
+
expect(logHandler).toHaveBeenCalledWith(expect.objectContaining({ level: 'info' }));
|
|
84
|
+
handleStderrData('DEBUG Component - Debug message');
|
|
85
|
+
expect(logHandler).toHaveBeenCalledWith(expect.objectContaining({ level: 'debug' }));
|
|
86
|
+
});
|
|
87
|
+
it('should filter out informational WARN messages', async () => {
|
|
88
|
+
const handleStderrData = signalCli.handleStderrData.bind(signalCli);
|
|
89
|
+
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
|
|
90
|
+
handleStderrData('WARN Component - Failed to get sender certificate');
|
|
91
|
+
handleStderrData('WARN Component - ignoring: java.lang.InterruptedException');
|
|
92
|
+
handleStderrData('WARN Component - Request was interrupted');
|
|
93
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
94
|
+
handleStderrData('WARN Component - Real warning message');
|
|
95
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
96
|
+
consoleSpy.mockRestore();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
describe('JSON-RPC Response Handling', () => {
|
|
100
|
+
it('should handle multi-line JSON-RPC responses', () => {
|
|
101
|
+
const handleRpcResponse = signalCli.handleRpcResponse.bind(signalCli);
|
|
102
|
+
const mockPromise = {
|
|
103
|
+
resolve: jest.fn(),
|
|
104
|
+
reject: jest.fn(),
|
|
105
|
+
};
|
|
106
|
+
signalCli.requestPromises.set('test-id', mockPromise);
|
|
107
|
+
const multiLineData = `{"jsonrpc":"2.0","id":"test-id","result":{"success":true}}\n{"jsonrpc":"2.0","method":"receive","params":{"data":"test"}}`;
|
|
108
|
+
handleRpcResponse(multiLineData);
|
|
109
|
+
expect(mockPromise.resolve).toHaveBeenCalledWith({ success: true });
|
|
110
|
+
});
|
|
111
|
+
it('should handle malformed JSON gracefully', () => {
|
|
112
|
+
const handleRpcResponse = signalCli.handleRpcResponse.bind(signalCli);
|
|
113
|
+
const errorHandler = jest.fn();
|
|
114
|
+
signalCli.on('error', errorHandler);
|
|
115
|
+
handleRpcResponse('not valid json');
|
|
116
|
+
expect(errorHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
117
|
+
message: expect.stringContaining('Failed to parse JSON-RPC response')
|
|
118
|
+
}));
|
|
119
|
+
});
|
|
120
|
+
it('should emit notifications correctly', () => {
|
|
121
|
+
const handleRpcResponse = signalCli.handleRpcResponse.bind(signalCli);
|
|
122
|
+
const notificationHandler = jest.fn();
|
|
123
|
+
const messageHandler = jest.fn();
|
|
124
|
+
signalCli.on('notification', notificationHandler);
|
|
125
|
+
signalCli.on('message', messageHandler);
|
|
126
|
+
const notification = '{"jsonrpc":"2.0","method":"receive","params":{"envelope":{"dataMessage":"test"}}}';
|
|
127
|
+
handleRpcResponse(notification);
|
|
128
|
+
expect(notificationHandler).toHaveBeenCalled();
|
|
129
|
+
expect(messageHandler).toHaveBeenCalled();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
describe('Validation Edge Cases', () => {
|
|
133
|
+
it('should validate recipients (phone, UUID, username)', async () => {
|
|
134
|
+
// Connect first
|
|
135
|
+
mockProcess.stdout.once.mockImplementation((event, callback) => {
|
|
136
|
+
if (event === 'data')
|
|
137
|
+
callback();
|
|
138
|
+
});
|
|
139
|
+
await signalCli.connect();
|
|
140
|
+
jest.spyOn(signalCli, 'sendJsonRpcRequest').mockResolvedValue({});
|
|
141
|
+
// Valid phone
|
|
142
|
+
await expect(signalCli.sendMessage('+1234567890', 'test')).resolves.toBeDefined();
|
|
143
|
+
// Valid UUID
|
|
144
|
+
await expect(signalCli.sendMessage('12345678-1234-1234-1234-123456789012', 'test')).resolves.toBeDefined();
|
|
145
|
+
// Valid username
|
|
146
|
+
await expect(signalCli.sendMessage('u:username.123', 'test')).resolves.toBeDefined();
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
describe('Group Detection', () => {
|
|
150
|
+
it('should correctly identify group IDs', () => {
|
|
151
|
+
const isGroupId = signalCli.isGroupId.bind(signalCli);
|
|
152
|
+
expect(isGroupId('base64groupid==')).toBe(true);
|
|
153
|
+
expect(isGroupId('group/with/slash')).toBe(true);
|
|
154
|
+
expect(isGroupId('multiple+signs+notphone')).toBe(true);
|
|
155
|
+
expect(isGroupId('+1234567890')).toBe(false);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
describe('Deprecated Methods', () => {
|
|
159
|
+
it('should warn about deprecated receiveMessages', async () => {
|
|
160
|
+
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
|
|
161
|
+
await signalCli.receiveMessages();
|
|
162
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('deprecated'));
|
|
163
|
+
consoleSpy.mockRestore();
|
|
164
|
+
});
|
|
165
|
+
it('should warn about deprecated startDaemon', async () => {
|
|
166
|
+
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
|
|
167
|
+
mockProcess.stdout.once.mockImplementation((event, callback) => {
|
|
168
|
+
if (event === 'data')
|
|
169
|
+
callback();
|
|
170
|
+
});
|
|
171
|
+
await signalCli.startDaemon();
|
|
172
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('deprecated'));
|
|
173
|
+
consoleSpy.mockRestore();
|
|
174
|
+
});
|
|
175
|
+
it('should warn about deprecated stopDaemon', () => {
|
|
176
|
+
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
|
|
177
|
+
signalCli.stopDaemon();
|
|
178
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('deprecated'));
|
|
179
|
+
consoleSpy.mockRestore();
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
describe('Complex Scenarios', () => {
|
|
183
|
+
beforeEach(() => {
|
|
184
|
+
jest.spyOn(signalCli, 'sendJsonRpcRequest').mockResolvedValue({});
|
|
185
|
+
});
|
|
186
|
+
it('should handle group message with options', async () => {
|
|
187
|
+
const sendJsonRpcRequestSpy = jest.spyOn(signalCli, 'sendJsonRpcRequest')
|
|
188
|
+
.mockResolvedValue({ results: [{ type: 'SUCCESS' }], timestamp: 123456 });
|
|
189
|
+
await signalCli.sendMessage('groupId==', 'Group message', {
|
|
190
|
+
mentions: [{ start: 0, length: 4, number: '+1111111111' }]
|
|
191
|
+
});
|
|
192
|
+
expect(sendJsonRpcRequestSpy).toHaveBeenCalled();
|
|
193
|
+
expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('send', expect.objectContaining({
|
|
194
|
+
message: 'Group message',
|
|
195
|
+
groupId: 'groupId=='
|
|
196
|
+
}));
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
describe('Error Handling', () => {
|
|
200
|
+
it('should reject when not connected', async () => {
|
|
201
|
+
const disconnectedSignal = new SignalCli_1.SignalCli('signal-cli', '+1234567890');
|
|
202
|
+
await expect(disconnectedSignal.sendMessage('+0987654321', 'test')).rejects.toThrow('Not connected');
|
|
203
|
+
});
|
|
204
|
+
it('should handle JSON-RPC errors correctly', () => {
|
|
205
|
+
const handleRpcResponse = signalCli.handleRpcResponse.bind(signalCli);
|
|
206
|
+
const mockPromise = {
|
|
207
|
+
resolve: jest.fn(),
|
|
208
|
+
reject: jest.fn(),
|
|
209
|
+
};
|
|
210
|
+
signalCli.requestPromises.set('error-id', mockPromise);
|
|
211
|
+
const errorResponse = '{"jsonrpc":"2.0","id":"error-id","error":{"code":-32600,"message":"Invalid Request"}}';
|
|
212
|
+
handleRpcResponse(errorResponse);
|
|
213
|
+
expect(mockPromise.reject).toHaveBeenCalledWith(expect.objectContaining({
|
|
214
|
+
message: expect.stringContaining('Invalid Request')
|
|
215
|
+
}));
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
});
|