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.
Files changed (36) hide show
  1. package/README.md +175 -59
  2. package/dist/SignalBot.d.ts +108 -0
  3. package/dist/SignalBot.js +811 -0
  4. package/dist/SignalCli.d.ts +205 -0
  5. package/dist/SignalCli.js +967 -0
  6. package/dist/__tests__/SignalBot.additional.test.d.ts +5 -0
  7. package/dist/__tests__/SignalBot.additional.test.js +333 -0
  8. package/dist/__tests__/SignalBot.test.d.ts +1 -0
  9. package/dist/__tests__/SignalBot.test.js +102 -0
  10. package/dist/__tests__/SignalCli.integration.test.d.ts +5 -0
  11. package/dist/__tests__/SignalCli.integration.test.js +218 -0
  12. package/dist/__tests__/SignalCli.methods.test.d.ts +5 -0
  13. package/dist/__tests__/SignalCli.methods.test.js +470 -0
  14. package/dist/__tests__/SignalCli.test.d.ts +1 -0
  15. package/dist/__tests__/SignalCli.test.js +479 -0
  16. package/dist/__tests__/config.test.d.ts +5 -0
  17. package/dist/__tests__/config.test.js +252 -0
  18. package/dist/__tests__/errors.test.d.ts +5 -0
  19. package/dist/__tests__/errors.test.js +276 -0
  20. package/dist/__tests__/retry.test.d.ts +4 -0
  21. package/dist/__tests__/retry.test.js +123 -0
  22. package/dist/__tests__/validators.test.d.ts +4 -0
  23. package/dist/__tests__/validators.test.js +147 -0
  24. package/dist/config.d.ts +67 -0
  25. package/dist/config.js +111 -0
  26. package/dist/errors.d.ts +32 -0
  27. package/dist/errors.js +75 -0
  28. package/dist/index.d.ts +7 -0
  29. package/dist/index.js +26 -0
  30. package/dist/interfaces.d.ts +1073 -0
  31. package/dist/interfaces.js +11 -0
  32. package/dist/retry.d.ts +56 -0
  33. package/dist/retry.js +135 -0
  34. package/dist/validators.d.ts +59 -0
  35. package/dist/validators.js +170 -0
  36. package/package.json +5 -6
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Additional tests for SignalBot
3
+ * Covers message handling, commands, and edge cases
4
+ */
5
+ export {};
@@ -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,5 @@
1
+ /**
2
+ * Integration tests for SignalCli
3
+ * Tests complex scenarios and edge cases
4
+ */
5
+ export {};
@@ -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
+ });
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Tests for SignalCli methods
3
+ * Covers additional methods not tested in SignalCli.test.ts
4
+ */
5
+ export {};