signal-sdk 0.1.1 → 0.1.3
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 +23 -11
- package/dist/MultiAccountManager.js +11 -19
- package/dist/SignalBot.js +40 -36
- package/dist/SignalCli.d.ts +25 -301
- package/dist/SignalCli.js +226 -971
- package/dist/__tests__/DeviceManager.test.d.ts +1 -0
- package/dist/__tests__/DeviceManager.test.js +135 -0
- package/dist/__tests__/MultiAccountManager.coverage.test.d.ts +1 -0
- package/dist/__tests__/MultiAccountManager.coverage.test.js +33 -0
- package/dist/__tests__/MultiAccountManager.test.js +3 -3
- package/dist/__tests__/SignalBot.additional.test.js +40 -37
- package/dist/__tests__/SignalBot.coverage.test.d.ts +1 -0
- package/dist/__tests__/SignalBot.coverage.test.js +385 -0
- package/dist/__tests__/SignalBot.test.js +8 -8
- package/dist/__tests__/SignalCli.advanced.test.js +47 -58
- package/dist/__tests__/SignalCli.connections.test.d.ts +1 -0
- package/dist/__tests__/SignalCli.connections.test.js +110 -0
- package/dist/__tests__/SignalCli.e2e.test.js +28 -32
- package/dist/__tests__/SignalCli.events.test.d.ts +1 -0
- package/dist/__tests__/SignalCli.events.test.js +113 -0
- package/dist/__tests__/SignalCli.integration.test.js +6 -5
- package/dist/__tests__/SignalCli.methods.test.js +150 -66
- package/dist/__tests__/SignalCli.parsing.test.js +4 -13
- package/dist/__tests__/SignalCli.simple.test.d.ts +1 -0
- package/dist/__tests__/SignalCli.simple.test.js +77 -0
- package/dist/__tests__/SignalCli.test.js +133 -74
- package/dist/__tests__/config.test.js +19 -29
- package/dist/__tests__/errors.test.js +2 -2
- package/dist/__tests__/retry.test.js +10 -8
- package/dist/__tests__/robustness.test.d.ts +1 -0
- package/dist/__tests__/robustness.test.js +59 -0
- package/dist/__tests__/security.test.d.ts +1 -0
- package/dist/__tests__/security.test.js +50 -0
- package/dist/config.js +3 -3
- package/dist/interfaces.d.ts +27 -0
- package/dist/managers/AccountManager.d.ts +27 -0
- package/dist/managers/AccountManager.js +147 -0
- package/dist/managers/BaseManager.d.ts +9 -0
- package/dist/managers/BaseManager.js +17 -0
- package/dist/managers/ContactManager.d.ts +15 -0
- package/dist/managers/ContactManager.js +123 -0
- package/dist/managers/DeviceManager.d.ts +11 -0
- package/dist/managers/DeviceManager.js +139 -0
- package/dist/managers/GroupManager.d.ts +12 -0
- package/dist/managers/GroupManager.js +78 -0
- package/dist/managers/MessageManager.d.ts +18 -0
- package/dist/managers/MessageManager.js +301 -0
- package/dist/managers/StickerManager.d.ts +8 -0
- package/dist/managers/StickerManager.js +39 -0
- package/dist/retry.js +3 -3
- package/dist/validators.d.ts +9 -0
- package/dist/validators.js +20 -0
- package/package.json +11 -4
- package/scripts/install.js +1 -1
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const SignalBot_1 = require("../SignalBot");
|
|
37
|
+
const SignalCli_1 = require("../SignalCli");
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const https = __importStar(require("https"));
|
|
40
|
+
const events_1 = require("events");
|
|
41
|
+
jest.mock('../SignalCli');
|
|
42
|
+
jest.mock('fs');
|
|
43
|
+
jest.mock('https');
|
|
44
|
+
jest.mock('http');
|
|
45
|
+
describe('SignalBot Coverage', () => {
|
|
46
|
+
let bot;
|
|
47
|
+
let mockSignalCli;
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
jest.clearAllMocks();
|
|
50
|
+
// Create a real EventEmitter and mix in mocked SignalCli methods
|
|
51
|
+
const emitter = new events_1.EventEmitter();
|
|
52
|
+
mockSignalCli = Object.assign(emitter, {
|
|
53
|
+
connect: jest.fn(),
|
|
54
|
+
disconnect: jest.fn(),
|
|
55
|
+
gracefulShutdown: jest.fn(),
|
|
56
|
+
listDevices: jest.fn(),
|
|
57
|
+
sendMessage: jest.fn(),
|
|
58
|
+
sendReaction: jest.fn(),
|
|
59
|
+
sendReceipt: jest.fn(),
|
|
60
|
+
updateGroup: jest.fn(),
|
|
61
|
+
createGroup: jest.fn(),
|
|
62
|
+
listGroups: jest.fn(),
|
|
63
|
+
});
|
|
64
|
+
SignalCli_1.SignalCli.mockImplementation(() => mockSignalCli);
|
|
65
|
+
bot = new SignalBot_1.SignalBot({
|
|
66
|
+
phoneNumber: '+1234567890',
|
|
67
|
+
admins: ['+admin'],
|
|
68
|
+
settings: {
|
|
69
|
+
cooldownSeconds: 1
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
describe('Image Download and Management', () => {
|
|
74
|
+
it('should download image successfully from URL', async () => {
|
|
75
|
+
const mockResponse = new events_1.EventEmitter();
|
|
76
|
+
mockResponse.statusCode = 200;
|
|
77
|
+
mockResponse.pipe = jest.fn();
|
|
78
|
+
mockResponse.headers = {};
|
|
79
|
+
const mockFile = new events_1.EventEmitter();
|
|
80
|
+
mockFile.close = jest.fn();
|
|
81
|
+
fs.createWriteStream.mockReturnValue(mockFile);
|
|
82
|
+
const mockRequest = new events_1.EventEmitter();
|
|
83
|
+
const getMock = jest.spyOn(https, 'get').mockImplementation((url, cb) => {
|
|
84
|
+
cb(mockResponse);
|
|
85
|
+
return mockRequest;
|
|
86
|
+
});
|
|
87
|
+
const downloadPromise = bot.downloadImageFromUrl('https://example.com/image.jpg');
|
|
88
|
+
mockResponse.emit('end');
|
|
89
|
+
mockFile.emit('finish');
|
|
90
|
+
const path = await downloadPromise;
|
|
91
|
+
expect(path).toBeDefined();
|
|
92
|
+
expect(getMock).toHaveBeenCalled();
|
|
93
|
+
});
|
|
94
|
+
it('should handle redirects during image download', async () => {
|
|
95
|
+
const mockRedirectResponse = new events_1.EventEmitter();
|
|
96
|
+
mockRedirectResponse.statusCode = 301;
|
|
97
|
+
mockRedirectResponse.headers = { location: 'https://new-url.com/image.jpg' };
|
|
98
|
+
const mockSuccessResponse = new events_1.EventEmitter();
|
|
99
|
+
mockSuccessResponse.statusCode = 200;
|
|
100
|
+
mockSuccessResponse.pipe = jest.fn();
|
|
101
|
+
mockSuccessResponse.headers = {};
|
|
102
|
+
const mockFile = new events_1.EventEmitter();
|
|
103
|
+
mockFile.close = jest.fn();
|
|
104
|
+
fs.createWriteStream.mockReturnValue(mockFile);
|
|
105
|
+
const mockRequest = new events_1.EventEmitter();
|
|
106
|
+
let callCount = 0;
|
|
107
|
+
jest.spyOn(https, 'get').mockImplementation((url, cb) => {
|
|
108
|
+
if (callCount === 0) {
|
|
109
|
+
callCount++;
|
|
110
|
+
cb(mockRedirectResponse);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
cb(mockSuccessResponse);
|
|
114
|
+
}
|
|
115
|
+
return mockRequest;
|
|
116
|
+
});
|
|
117
|
+
const downloadPromise = bot.downloadImageFromUrl('https://example.com/redirect');
|
|
118
|
+
mockFile.emit('finish');
|
|
119
|
+
const path = await downloadPromise;
|
|
120
|
+
expect(path).toBeDefined();
|
|
121
|
+
});
|
|
122
|
+
it('should process base64 avatars', async () => {
|
|
123
|
+
const base64Avatar = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==';
|
|
124
|
+
fs.writeFileSync.mockReturnValue(undefined);
|
|
125
|
+
const result = await bot.processGroupAvatar(base64Avatar);
|
|
126
|
+
expect(result).toContain('bot_avatar_');
|
|
127
|
+
expect(fs.writeFileSync).toHaveBeenCalled();
|
|
128
|
+
});
|
|
129
|
+
it('should process local file avatars', async () => {
|
|
130
|
+
fs.existsSync.mockReturnValue(true);
|
|
131
|
+
const result = await bot.processGroupAvatar('/path/to/avatar.jpg');
|
|
132
|
+
expect(result).toBe('/path/to/avatar.jpg');
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
describe('Message and Command Handling', () => {
|
|
136
|
+
it('should ignore own messages', async () => {
|
|
137
|
+
const ownMessage = {
|
|
138
|
+
envelope: {
|
|
139
|
+
sourceNumber: '+1234567890',
|
|
140
|
+
dataMessage: { message: 'hello' },
|
|
141
|
+
timestamp: Date.now()
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
await bot.handleMessage(ownMessage);
|
|
145
|
+
expect(bot.getStats().messagesReceived).toBe(0);
|
|
146
|
+
});
|
|
147
|
+
it('should process authorized group messages', async () => {
|
|
148
|
+
bot.botGroupId = 'authorized-group';
|
|
149
|
+
const groupMessage = {
|
|
150
|
+
envelope: {
|
|
151
|
+
sourceNumber: '+someone',
|
|
152
|
+
dataMessage: {
|
|
153
|
+
message: 'hello',
|
|
154
|
+
groupInfo: { groupId: 'authorized-group' }
|
|
155
|
+
},
|
|
156
|
+
timestamp: Date.now()
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
await bot.handleMessage(groupMessage);
|
|
160
|
+
expect(bot.getStats().messagesReceived).toBe(1);
|
|
161
|
+
});
|
|
162
|
+
it('should ignore unauthorized group messages', async () => {
|
|
163
|
+
bot.botGroupId = 'authorized-group';
|
|
164
|
+
const groupMessage = {
|
|
165
|
+
envelope: {
|
|
166
|
+
sourceNumber: '+someone',
|
|
167
|
+
dataMessage: {
|
|
168
|
+
message: 'hello',
|
|
169
|
+
groupInfo: { groupId: 'unauthorized-group' }
|
|
170
|
+
},
|
|
171
|
+
timestamp: Date.now()
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
await bot.handleMessage(groupMessage);
|
|
175
|
+
expect(bot.getStats().messagesReceived).toBe(0);
|
|
176
|
+
});
|
|
177
|
+
it('should handle commands with cooldown', async () => {
|
|
178
|
+
const handler = jest.fn().mockResolvedValue('Response');
|
|
179
|
+
bot.addCommand({
|
|
180
|
+
name: 'test',
|
|
181
|
+
description: 'test',
|
|
182
|
+
handler
|
|
183
|
+
});
|
|
184
|
+
const message = {
|
|
185
|
+
id: '1',
|
|
186
|
+
source: '+user',
|
|
187
|
+
text: '/test',
|
|
188
|
+
timestamp: Date.now(),
|
|
189
|
+
isFromAdmin: false
|
|
190
|
+
};
|
|
191
|
+
await bot.handleCommand(message);
|
|
192
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
193
|
+
// Immediate second call should hit cooldown
|
|
194
|
+
await bot.handleCommand(message);
|
|
195
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
196
|
+
});
|
|
197
|
+
it('should enforce adminOnly commands', async () => {
|
|
198
|
+
const handler = jest.fn().mockResolvedValue('Secret');
|
|
199
|
+
bot.addCommand({
|
|
200
|
+
name: 'admincmd',
|
|
201
|
+
description: 'admin only',
|
|
202
|
+
adminOnly: true,
|
|
203
|
+
handler
|
|
204
|
+
});
|
|
205
|
+
const message = {
|
|
206
|
+
id: '1',
|
|
207
|
+
source: '+user',
|
|
208
|
+
text: '/admincmd',
|
|
209
|
+
timestamp: Date.now(),
|
|
210
|
+
isFromAdmin: false
|
|
211
|
+
};
|
|
212
|
+
await bot.handleCommand(message);
|
|
213
|
+
expect(handler).not.toHaveBeenCalled();
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
describe('Action Queue and Process Management', () => {
|
|
217
|
+
it('should process action queue sequentially', async () => {
|
|
218
|
+
mockSignalCli.sendMessage.mockResolvedValue({ results: [], timestamp: Date.now() });
|
|
219
|
+
await bot.sendMessage('+user1', 'msg1');
|
|
220
|
+
await bot.sendMessage('+user2', 'msg2');
|
|
221
|
+
// Wait for queue to process
|
|
222
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
223
|
+
expect(mockSignalCli.sendMessage).toHaveBeenCalledTimes(2);
|
|
224
|
+
});
|
|
225
|
+
it('should handle sendMessageWithAttachment and cleanup', async () => {
|
|
226
|
+
jest.useFakeTimers();
|
|
227
|
+
mockSignalCli.sendMessage.mockResolvedValue({ results: [], timestamp: Date.now() });
|
|
228
|
+
fs.existsSync.mockReturnValue(true);
|
|
229
|
+
await bot.sendMessageWithAttachment('+user', 'msg', ['/tmp/file'], ['/tmp/file']);
|
|
230
|
+
// Allow queue to start processing
|
|
231
|
+
jest.advanceTimersByTime(100);
|
|
232
|
+
expect(mockSignalCli.sendMessage).toHaveBeenCalled();
|
|
233
|
+
// Advance timers to trigger cleanup
|
|
234
|
+
jest.advanceTimersByTime(2500);
|
|
235
|
+
expect(fs.unlinkSync).toHaveBeenCalledWith('/tmp/file');
|
|
236
|
+
jest.useRealTimers();
|
|
237
|
+
});
|
|
238
|
+
it('should handle daemon events', () => {
|
|
239
|
+
const logSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
240
|
+
bot.setupEventHandlers();
|
|
241
|
+
mockSignalCli.emit('log', { level: 'info', message: 'test log' });
|
|
242
|
+
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('test log'));
|
|
243
|
+
mockSignalCli.emit('close', 1);
|
|
244
|
+
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('error code 1'));
|
|
245
|
+
logSpy.mockRestore();
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
describe('Startup and Shutdown', () => {
|
|
249
|
+
it('should start gracefully', async () => {
|
|
250
|
+
mockSignalCli.connect.mockResolvedValue(undefined);
|
|
251
|
+
mockSignalCli.listDevices.mockResolvedValue([{ id: 1 }]);
|
|
252
|
+
await bot.start();
|
|
253
|
+
expect(mockSignalCli.connect).toHaveBeenCalled();
|
|
254
|
+
expect(bot.getStats()).toBeDefined();
|
|
255
|
+
});
|
|
256
|
+
it('should throw error if no devices linked on start', async () => {
|
|
257
|
+
mockSignalCli.connect.mockResolvedValue(undefined);
|
|
258
|
+
mockSignalCli.listDevices.mockResolvedValue([]);
|
|
259
|
+
await expect(bot.start()).rejects.toThrow('link the bot first');
|
|
260
|
+
});
|
|
261
|
+
it('should shutdown gracefully', async () => {
|
|
262
|
+
await bot.gracefulShutdown();
|
|
263
|
+
expect(mockSignalCli.gracefulShutdown).toHaveBeenCalled();
|
|
264
|
+
});
|
|
265
|
+
it('should stop bot and clear timers', async () => {
|
|
266
|
+
const timer = setTimeout(() => { }, 10000);
|
|
267
|
+
bot.activeTimers.push(timer);
|
|
268
|
+
await bot.stop();
|
|
269
|
+
expect(bot.isRunning).toBe(false);
|
|
270
|
+
expect(bot.activeTimers.length).toBe(0);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
describe('Group Management', () => {
|
|
274
|
+
beforeEach(() => {
|
|
275
|
+
bot = new SignalBot_1.SignalBot({
|
|
276
|
+
phoneNumber: '+1234567890',
|
|
277
|
+
admins: ['+admin1'],
|
|
278
|
+
group: {
|
|
279
|
+
name: 'Test Group',
|
|
280
|
+
createIfNotExists: true,
|
|
281
|
+
initialMembers: ['+member1']
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
it('should setup group and add missing admins if it already exists', async () => {
|
|
286
|
+
mockSignalCli.listGroups.mockResolvedValue([{
|
|
287
|
+
name: 'Test Group',
|
|
288
|
+
groupId: 'group-id',
|
|
289
|
+
isMember: true,
|
|
290
|
+
members: ['+1234567890'] // +admin1 is missing
|
|
291
|
+
}]);
|
|
292
|
+
await bot.setupBotGroup();
|
|
293
|
+
expect(bot.botGroupId).toBe('group-id');
|
|
294
|
+
expect(mockSignalCli.updateGroup).toHaveBeenCalledWith('group-id', expect.objectContaining({
|
|
295
|
+
addMembers: ['+admin1']
|
|
296
|
+
}));
|
|
297
|
+
});
|
|
298
|
+
it('should create group if it does not exist', async () => {
|
|
299
|
+
mockSignalCli.listGroups.mockResolvedValue([]);
|
|
300
|
+
mockSignalCli.createGroup.mockResolvedValue({
|
|
301
|
+
groupId: 'new-group-id'
|
|
302
|
+
});
|
|
303
|
+
await bot.setupBotGroup();
|
|
304
|
+
expect(bot.botGroupId).toBe('new-group-id');
|
|
305
|
+
expect(mockSignalCli.createGroup).toHaveBeenCalled();
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
describe('Utility and Helpers', () => {
|
|
309
|
+
it('should format uptime correctly', () => {
|
|
310
|
+
const botAny = bot;
|
|
311
|
+
expect(botAny.formatUptime(1000)).toBe('1s');
|
|
312
|
+
expect(botAny.formatUptime(61000)).toBe('1m 1s');
|
|
313
|
+
expect(botAny.formatUptime(3661000)).toBe('1h 1m');
|
|
314
|
+
expect(botAny.formatUptime(90000000)).toBe('1d 1h 0m');
|
|
315
|
+
});
|
|
316
|
+
it('should handle errors in action queue', async () => {
|
|
317
|
+
const logSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
318
|
+
mockSignalCli.sendMessage.mockRejectedValue(new Error('Send failed'));
|
|
319
|
+
await bot.sendMessage('+user', 'test');
|
|
320
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
321
|
+
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to execute action'));
|
|
322
|
+
logSpy.mockRestore();
|
|
323
|
+
});
|
|
324
|
+
it('should send welcome message to admins', async () => {
|
|
325
|
+
const sendMessageSpy = jest.spyOn(bot, 'sendMessage').mockResolvedValue();
|
|
326
|
+
await bot.sendWelcomeMessage();
|
|
327
|
+
expect(sendMessageSpy).toHaveBeenCalled();
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
describe('handleMessage edge cases', () => {
|
|
331
|
+
it('should handle messages with groupInfo variants', async () => {
|
|
332
|
+
bot.botGroupId = 'target-id';
|
|
333
|
+
const msg = {
|
|
334
|
+
envelope: {
|
|
335
|
+
sourceNumber: '+someone',
|
|
336
|
+
dataMessage: {
|
|
337
|
+
message: 'hello',
|
|
338
|
+
groupInfo: { id: 'target-id' } // uses 'id' instead of 'groupId'
|
|
339
|
+
},
|
|
340
|
+
timestamp: Date.now()
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
await bot.handleMessage(msg);
|
|
344
|
+
expect(bot.getStats().messagesReceived).toBe(1);
|
|
345
|
+
});
|
|
346
|
+
it('should ignore messages with no text and no attachments', async () => {
|
|
347
|
+
const msg = {
|
|
348
|
+
envelope: {
|
|
349
|
+
sourceNumber: '+someone',
|
|
350
|
+
dataMessage: {
|
|
351
|
+
message: '',
|
|
352
|
+
attachments: undefined // Use undefined to hit the check
|
|
353
|
+
},
|
|
354
|
+
timestamp: Date.now()
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
await bot.handleMessage(msg);
|
|
358
|
+
expect(bot.getStats().messagesReceived).toBe(0);
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
describe('sendMessageWithImage', () => {
|
|
362
|
+
it('should download and send image', async () => {
|
|
363
|
+
const downloadSpy = jest.spyOn(bot, 'downloadImageFromUrl').mockResolvedValue('/tmp/downloaded.jpg');
|
|
364
|
+
const processQueueSpy = jest.spyOn(bot, 'processActionQueue').mockImplementation();
|
|
365
|
+
await bot.sendMessageWithImage('+recipient', 'caption', 'https://example.com/img.png');
|
|
366
|
+
expect(downloadSpy).toHaveBeenCalledWith('https://example.com/img.png', 'bot_image');
|
|
367
|
+
expect(bot.actionQueue).toContainEqual(expect.objectContaining({
|
|
368
|
+
type: 'sendMessageWithAttachment',
|
|
369
|
+
attachments: ['/tmp/downloaded.jpg']
|
|
370
|
+
}));
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
describe('Action Queue edge cases', () => {
|
|
374
|
+
it('should handle sendReaction action', async () => {
|
|
375
|
+
await bot.sendReaction('+rec', '+author', 123, '😀');
|
|
376
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
377
|
+
expect(mockSignalCli.sendReaction).toHaveBeenCalled();
|
|
378
|
+
});
|
|
379
|
+
it('should handle non-data messages', async () => {
|
|
380
|
+
const msg = { envelope: { receiptMessage: {} } };
|
|
381
|
+
await bot.handleMessage(msg);
|
|
382
|
+
expect(bot.getStats().messagesReceived).toBe(0);
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
});
|
|
@@ -10,8 +10,8 @@ describe('SignalBot', () => {
|
|
|
10
10
|
admins: ['+0987654321'],
|
|
11
11
|
group: {
|
|
12
12
|
name: 'Test Bot Group',
|
|
13
|
-
createIfNotExists: false
|
|
14
|
-
}
|
|
13
|
+
createIfNotExists: false,
|
|
14
|
+
},
|
|
15
15
|
};
|
|
16
16
|
});
|
|
17
17
|
afterEach(async () => {
|
|
@@ -26,7 +26,7 @@ describe('SignalBot', () => {
|
|
|
26
26
|
});
|
|
27
27
|
test('should create bot with minimal config', () => {
|
|
28
28
|
bot = new SignalBot_1.SignalBot({
|
|
29
|
-
phoneNumber: '+1234567890'
|
|
29
|
+
phoneNumber: '+1234567890',
|
|
30
30
|
});
|
|
31
31
|
expect(bot).toBeDefined();
|
|
32
32
|
expect(bot.isAdmin('+1234567890')).toBe(false);
|
|
@@ -42,7 +42,7 @@ describe('SignalBot', () => {
|
|
|
42
42
|
const testCommand = {
|
|
43
43
|
name: 'test',
|
|
44
44
|
description: 'Test command',
|
|
45
|
-
handler: jest.fn()
|
|
45
|
+
handler: jest.fn(),
|
|
46
46
|
};
|
|
47
47
|
bot.addCommand(testCommand);
|
|
48
48
|
const commands = bot.getCommands();
|
|
@@ -66,13 +66,13 @@ describe('SignalBot', () => {
|
|
|
66
66
|
phoneNumber: '+1234567890',
|
|
67
67
|
group: {
|
|
68
68
|
name: 'Test Group',
|
|
69
|
-
createIfNotExists: false
|
|
70
|
-
}
|
|
69
|
+
createIfNotExists: false,
|
|
70
|
+
},
|
|
71
71
|
});
|
|
72
72
|
// Mock SignalCli methods to avoid actual Signal operations
|
|
73
73
|
const mockSignalCli = bot.getSignalCli();
|
|
74
74
|
jest.spyOn(mockSignalCli, 'listDevices').mockResolvedValue([
|
|
75
|
-
{ id: 1, name: 'Test Device', created: Date.now(), lastSeen: Date.now() }
|
|
75
|
+
{ id: 1, name: 'Test Device', created: Date.now(), lastSeen: Date.now() },
|
|
76
76
|
]);
|
|
77
77
|
jest.spyOn(mockSignalCli, 'startDaemon').mockImplementation(() => { });
|
|
78
78
|
jest.spyOn(mockSignalCli, 'connect').mockResolvedValue(undefined);
|
|
@@ -98,7 +98,7 @@ describe('SignalBot', () => {
|
|
|
98
98
|
test('should handle admin permissions', () => {
|
|
99
99
|
bot = new SignalBot_1.SignalBot({
|
|
100
100
|
phoneNumber: '+1234567890',
|
|
101
|
-
admins: ['+0987654321', '+1111111111']
|
|
101
|
+
admins: ['+0987654321', '+1111111111'],
|
|
102
102
|
});
|
|
103
103
|
expect(bot.isAdmin('+0987654321')).toBe(true);
|
|
104
104
|
expect(bot.isAdmin('+1111111111')).toBe(true);
|