signal-sdk 0.1.0 → 0.1.2
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 +26 -17
- package/dist/MultiAccountManager.d.ts +149 -0
- package/dist/MultiAccountManager.js +320 -0
- package/dist/SignalBot.d.ts +1 -0
- package/dist/SignalBot.js +20 -2
- package/dist/SignalCli.d.ts +264 -15
- package/dist/SignalCli.js +652 -25
- package/dist/__tests__/MultiAccountManager.test.d.ts +4 -0
- package/dist/__tests__/MultiAccountManager.test.js +209 -0
- package/dist/__tests__/SignalBot.additional.test.js +31 -11
- package/dist/__tests__/SignalBot.test.js +5 -0
- package/dist/__tests__/SignalCli.advanced.test.d.ts +5 -0
- package/dist/__tests__/SignalCli.advanced.test.js +295 -0
- package/dist/__tests__/SignalCli.e2e.test.d.ts +5 -0
- package/dist/__tests__/SignalCli.e2e.test.js +240 -0
- package/dist/__tests__/SignalCli.integration.test.js +9 -2
- package/dist/__tests__/SignalCli.methods.test.js +171 -1
- package/dist/__tests__/SignalCli.parsing.test.d.ts +5 -0
- package/dist/__tests__/SignalCli.parsing.test.js +258 -0
- package/dist/__tests__/SignalCli.test.js +50 -13
- package/dist/config.d.ts +16 -1
- package/dist/config.js +6 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/interfaces.d.ts +73 -9
- package/dist/retry.js +25 -8
- package/package.json +1 -1
- package/scripts/install.js +1 -1
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tests for MultiAccountManager
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const MultiAccountManager_1 = require("../MultiAccountManager");
|
|
7
|
+
const SignalCli_1 = require("../SignalCli");
|
|
8
|
+
// Mock SignalCli
|
|
9
|
+
jest.mock('../SignalCli');
|
|
10
|
+
describe('MultiAccountManager', () => {
|
|
11
|
+
let manager;
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
jest.clearAllMocks();
|
|
14
|
+
manager = new MultiAccountManager_1.MultiAccountManager({
|
|
15
|
+
dataPath: '/tmp/test-data',
|
|
16
|
+
verbose: false
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
afterEach(async () => {
|
|
20
|
+
if (manager) {
|
|
21
|
+
await manager.shutdown();
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
describe('Account Management', () => {
|
|
25
|
+
it('should add an account', async () => {
|
|
26
|
+
const instance = await manager.addAccount('+33123456789');
|
|
27
|
+
expect(instance).toBeInstanceOf(SignalCli_1.SignalCli);
|
|
28
|
+
expect(manager.hasAccount('+33123456789')).toBe(true);
|
|
29
|
+
expect(manager.getAccounts()).toContain('+33123456789');
|
|
30
|
+
});
|
|
31
|
+
it('should emit accountAdded event', async () => {
|
|
32
|
+
const spy = jest.fn();
|
|
33
|
+
manager.on('accountAdded', spy);
|
|
34
|
+
await manager.addAccount('+33123456789');
|
|
35
|
+
expect(spy).toHaveBeenCalledWith('+33123456789');
|
|
36
|
+
});
|
|
37
|
+
it('should throw error when adding duplicate account', async () => {
|
|
38
|
+
await manager.addAccount('+33123456789');
|
|
39
|
+
await expect(manager.addAccount('+33123456789')).rejects.toThrow('Account +33123456789 already exists');
|
|
40
|
+
});
|
|
41
|
+
it('should remove an account', async () => {
|
|
42
|
+
await manager.addAccount('+33123456789');
|
|
43
|
+
await manager.removeAccount('+33123456789');
|
|
44
|
+
expect(manager.hasAccount('+33123456789')).toBe(false);
|
|
45
|
+
expect(manager.getAccounts()).not.toContain('+33123456789');
|
|
46
|
+
});
|
|
47
|
+
it('should emit accountRemoved event', async () => {
|
|
48
|
+
const spy = jest.fn();
|
|
49
|
+
await manager.addAccount('+33123456789');
|
|
50
|
+
manager.on('accountRemoved', spy);
|
|
51
|
+
await manager.removeAccount('+33123456789');
|
|
52
|
+
expect(spy).toHaveBeenCalledWith('+33123456789');
|
|
53
|
+
});
|
|
54
|
+
it('should throw error when removing non-existent account', async () => {
|
|
55
|
+
await expect(manager.removeAccount('+33123456789')).rejects.toThrow('Account +33123456789 not found');
|
|
56
|
+
});
|
|
57
|
+
it('should get specific account instance', async () => {
|
|
58
|
+
await manager.addAccount('+33123456789');
|
|
59
|
+
const instance = manager.getAccount('+33123456789');
|
|
60
|
+
expect(instance).toBeInstanceOf(SignalCli_1.SignalCli);
|
|
61
|
+
});
|
|
62
|
+
it('should return undefined for non-existent account', () => {
|
|
63
|
+
const instance = manager.getAccount('+33999999999');
|
|
64
|
+
expect(instance).toBeUndefined();
|
|
65
|
+
});
|
|
66
|
+
it('should get all account numbers', async () => {
|
|
67
|
+
await manager.addAccount('+33123456789');
|
|
68
|
+
await manager.addAccount('+33987654321');
|
|
69
|
+
const accounts = manager.getAccounts();
|
|
70
|
+
expect(accounts).toHaveLength(2);
|
|
71
|
+
expect(accounts).toContain('+33123456789');
|
|
72
|
+
expect(accounts).toContain('+33987654321');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe('Connection Management', () => {
|
|
76
|
+
it('should connect a specific account', async () => {
|
|
77
|
+
const instance = await manager.addAccount('+33123456789');
|
|
78
|
+
instance.connect = jest.fn().mockResolvedValue(undefined);
|
|
79
|
+
await manager.connect('+33123456789');
|
|
80
|
+
expect(instance.connect).toHaveBeenCalled();
|
|
81
|
+
});
|
|
82
|
+
it('should emit accountConnected event', async () => {
|
|
83
|
+
const instance = await manager.addAccount('+33123456789');
|
|
84
|
+
instance.connect = jest.fn().mockResolvedValue(undefined);
|
|
85
|
+
const spy = jest.fn();
|
|
86
|
+
manager.on('accountConnected', spy);
|
|
87
|
+
await manager.connect('+33123456789');
|
|
88
|
+
expect(spy).toHaveBeenCalledWith('+33123456789');
|
|
89
|
+
});
|
|
90
|
+
it('should disconnect a specific account', async () => {
|
|
91
|
+
const instance = await manager.addAccount('+33123456789');
|
|
92
|
+
instance.connect = jest.fn().mockResolvedValue(undefined);
|
|
93
|
+
instance.disconnect = jest.fn().mockResolvedValue(undefined);
|
|
94
|
+
await manager.connect('+33123456789');
|
|
95
|
+
await manager.disconnect('+33123456789');
|
|
96
|
+
expect(instance.disconnect).toHaveBeenCalled();
|
|
97
|
+
});
|
|
98
|
+
it('should emit accountDisconnected event', async () => {
|
|
99
|
+
const instance = await manager.addAccount('+33123456789');
|
|
100
|
+
instance.connect = jest.fn().mockResolvedValue(undefined);
|
|
101
|
+
instance.disconnect = jest.fn().mockResolvedValue(undefined);
|
|
102
|
+
const spy = jest.fn();
|
|
103
|
+
await manager.connect('+33123456789');
|
|
104
|
+
manager.on('accountDisconnected', spy);
|
|
105
|
+
await manager.disconnect('+33123456789');
|
|
106
|
+
expect(spy).toHaveBeenCalledWith('+33123456789');
|
|
107
|
+
});
|
|
108
|
+
it('should connect all accounts', async () => {
|
|
109
|
+
const instance1 = await manager.addAccount('+33123456789');
|
|
110
|
+
const instance2 = await manager.addAccount('+33987654321');
|
|
111
|
+
instance1.connect = jest.fn().mockResolvedValue(undefined);
|
|
112
|
+
instance2.connect = jest.fn().mockResolvedValue(undefined);
|
|
113
|
+
await manager.connectAll();
|
|
114
|
+
expect(instance1.connect).toHaveBeenCalled();
|
|
115
|
+
expect(instance2.connect).toHaveBeenCalled();
|
|
116
|
+
});
|
|
117
|
+
it('should disconnect all accounts', async () => {
|
|
118
|
+
const instance1 = await manager.addAccount('+33123456789');
|
|
119
|
+
const instance2 = await manager.addAccount('+33987654321');
|
|
120
|
+
instance1.connect = jest.fn().mockResolvedValue(undefined);
|
|
121
|
+
instance2.connect = jest.fn().mockResolvedValue(undefined);
|
|
122
|
+
instance1.disconnect = jest.fn().mockResolvedValue(undefined);
|
|
123
|
+
instance2.disconnect = jest.fn().mockResolvedValue(undefined);
|
|
124
|
+
await manager.connectAll();
|
|
125
|
+
await manager.disconnectAll();
|
|
126
|
+
expect(instance1.disconnect).toHaveBeenCalled();
|
|
127
|
+
expect(instance2.disconnect).toHaveBeenCalled();
|
|
128
|
+
});
|
|
129
|
+
it('should handle connection errors gracefully', async () => {
|
|
130
|
+
const instance = await manager.addAccount('+33123456789');
|
|
131
|
+
instance.connect = jest.fn().mockRejectedValue(new Error('Connection failed'));
|
|
132
|
+
await expect(manager.connect('+33123456789')).rejects.toThrow('Connection failed');
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
describe('Message Sending', () => {
|
|
136
|
+
it('should send message from specific account', async () => {
|
|
137
|
+
const instance = await manager.addAccount('+33123456789');
|
|
138
|
+
instance.sendMessage = jest.fn().mockResolvedValue({
|
|
139
|
+
timestamp: Date.now(),
|
|
140
|
+
results: []
|
|
141
|
+
});
|
|
142
|
+
await manager.sendMessage('+33123456789', '+33111111111', 'Hello!');
|
|
143
|
+
expect(instance.sendMessage).toHaveBeenCalledWith('+33111111111', 'Hello!', {});
|
|
144
|
+
});
|
|
145
|
+
it('should throw error when sending from non-existent account', async () => {
|
|
146
|
+
await expect(manager.sendMessage('+33999999999', '+33111111111', 'Hello!')).rejects.toThrow('Account +33999999999 not found');
|
|
147
|
+
});
|
|
148
|
+
it('should send message with options', async () => {
|
|
149
|
+
const instance = await manager.addAccount('+33123456789');
|
|
150
|
+
instance.sendMessage = jest.fn().mockResolvedValue({
|
|
151
|
+
timestamp: Date.now(),
|
|
152
|
+
results: []
|
|
153
|
+
});
|
|
154
|
+
const options = { attachments: ['file.jpg'] };
|
|
155
|
+
await manager.sendMessage('+33123456789', '+33111111111', 'Hello!', options);
|
|
156
|
+
expect(instance.sendMessage).toHaveBeenCalledWith('+33111111111', 'Hello!', options);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
describe('Event Forwarding', () => {
|
|
160
|
+
it('should setup event forwarding on account addition', async () => {
|
|
161
|
+
const instance = await manager.addAccount('+33123456789');
|
|
162
|
+
// Verify instance was created
|
|
163
|
+
expect(instance).toBeDefined();
|
|
164
|
+
expect(manager.hasAccount('+33123456789')).toBe(true);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
describe('Status Information', () => {
|
|
168
|
+
it('should get status for specific account', async () => {
|
|
169
|
+
await manager.addAccount('+33123456789');
|
|
170
|
+
const status = manager.getStatus('+33123456789');
|
|
171
|
+
expect(status).toHaveProperty('account', '+33123456789');
|
|
172
|
+
expect(status).toHaveProperty('connected', false);
|
|
173
|
+
expect(status).toHaveProperty('lastActivity');
|
|
174
|
+
expect(status).toHaveProperty('uptime');
|
|
175
|
+
});
|
|
176
|
+
it('should return null for non-existent account status', () => {
|
|
177
|
+
const status = manager.getStatus('+33999999999');
|
|
178
|
+
expect(status).toBeNull();
|
|
179
|
+
});
|
|
180
|
+
it('should get status for all accounts', async () => {
|
|
181
|
+
await manager.addAccount('+33123456789');
|
|
182
|
+
await manager.addAccount('+33987654321');
|
|
183
|
+
const status = manager.getStatus();
|
|
184
|
+
expect(status.totalAccounts).toBe(2);
|
|
185
|
+
expect(status.connectedAccounts).toBe(0);
|
|
186
|
+
expect(status.accounts).toHaveLength(2);
|
|
187
|
+
});
|
|
188
|
+
it('should update connected count in status', async () => {
|
|
189
|
+
const instance = await manager.addAccount('+33123456789');
|
|
190
|
+
jest.spyOn(instance, 'connect').mockResolvedValue();
|
|
191
|
+
await manager.connect('+33123456789');
|
|
192
|
+
const status = manager.getStatus();
|
|
193
|
+
expect(status.connectedAccounts).toBe(1);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
describe('Shutdown', () => {
|
|
197
|
+
it('should shutdown and cleanup all accounts', async () => {
|
|
198
|
+
const instance1 = await manager.addAccount('+33123456789');
|
|
199
|
+
const instance2 = await manager.addAccount('+33987654321');
|
|
200
|
+
instance1.connect = jest.fn().mockResolvedValue(undefined);
|
|
201
|
+
instance2.connect = jest.fn().mockResolvedValue(undefined);
|
|
202
|
+
instance1.disconnect = jest.fn().mockResolvedValue(undefined);
|
|
203
|
+
instance2.disconnect = jest.fn().mockResolvedValue(undefined);
|
|
204
|
+
await manager.connectAll();
|
|
205
|
+
await manager.shutdown();
|
|
206
|
+
expect(manager.getAccounts()).toHaveLength(0);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
});
|
|
@@ -9,6 +9,8 @@ describe('SignalBot Additional Tests', () => {
|
|
|
9
9
|
let bot;
|
|
10
10
|
let mockConfig;
|
|
11
11
|
beforeEach(() => {
|
|
12
|
+
// Use real timers for these tests
|
|
13
|
+
jest.useRealTimers();
|
|
12
14
|
mockConfig = {
|
|
13
15
|
phoneNumber: '+1234567890',
|
|
14
16
|
admins: ['+0987654321'],
|
|
@@ -25,8 +27,11 @@ describe('SignalBot Additional Tests', () => {
|
|
|
25
27
|
afterEach(async () => {
|
|
26
28
|
if (bot) {
|
|
27
29
|
await bot.stop();
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
+
// Remove all listeners to prevent memory leaks
|
|
31
|
+
bot.removeAllListeners();
|
|
32
|
+
bot.getSignalCli().removeAllListeners();
|
|
33
|
+
// Wait a bit for any async operations to complete
|
|
34
|
+
await new Promise(resolve => process.nextTick(resolve));
|
|
30
35
|
}
|
|
31
36
|
});
|
|
32
37
|
describe('Command Management', () => {
|
|
@@ -115,42 +120,57 @@ describe('SignalBot Additional Tests', () => {
|
|
|
115
120
|
});
|
|
116
121
|
describe('Message Sending', () => {
|
|
117
122
|
test('should queue message for sending', async () => {
|
|
123
|
+
jest.useFakeTimers();
|
|
118
124
|
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
119
125
|
const mockSignalCli = bot.getSignalCli();
|
|
120
126
|
const sendMessageSpy = jest.spyOn(mockSignalCli, 'sendMessage').mockResolvedValue({
|
|
121
127
|
results: [{ type: 'SUCCESS' }],
|
|
122
128
|
timestamp: Date.now()
|
|
123
129
|
});
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
130
|
+
// Send message (queued)
|
|
131
|
+
const sendPromise = bot.sendMessage('+1111111111', 'Test message');
|
|
132
|
+
// Advance timers to process queue
|
|
133
|
+
jest.advanceTimersByTime(250);
|
|
134
|
+
await Promise.resolve(); // Let promises resolve
|
|
135
|
+
await sendPromise;
|
|
127
136
|
expect(sendMessageSpy).toHaveBeenCalledWith('+1111111111', 'Test message');
|
|
137
|
+
jest.useRealTimers();
|
|
128
138
|
});
|
|
129
139
|
test('should queue reaction for sending', async () => {
|
|
140
|
+
jest.useFakeTimers();
|
|
130
141
|
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
131
142
|
const mockSignalCli = bot.getSignalCli();
|
|
132
143
|
const sendReactionSpy = jest.spyOn(mockSignalCli, 'sendReaction').mockResolvedValue({
|
|
133
144
|
results: [{ type: 'SUCCESS' }],
|
|
134
145
|
timestamp: Date.now()
|
|
135
146
|
});
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
147
|
+
// Send reaction (queued)
|
|
148
|
+
const sendPromise = bot.sendReaction('+1111111111', '+2222222222', 123456, '👍');
|
|
149
|
+
// Advance timers to process queue
|
|
150
|
+
jest.advanceTimersByTime(250);
|
|
151
|
+
await Promise.resolve();
|
|
152
|
+
await sendPromise;
|
|
139
153
|
expect(sendReactionSpy).toHaveBeenCalledWith('+1111111111', '+2222222222', 123456, '👍');
|
|
154
|
+
jest.useRealTimers();
|
|
140
155
|
});
|
|
141
156
|
test('should handle message with attachments', async () => {
|
|
157
|
+
jest.useFakeTimers();
|
|
142
158
|
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
143
159
|
const mockSignalCli = bot.getSignalCli();
|
|
144
160
|
const sendMessageSpy = jest.spyOn(mockSignalCli, 'sendMessage').mockResolvedValue({
|
|
145
161
|
results: [{ type: 'SUCCESS' }],
|
|
146
162
|
timestamp: Date.now()
|
|
147
163
|
});
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
164
|
+
// Send message with attachment (queued)
|
|
165
|
+
const sendPromise = bot.sendMessageWithAttachment('+1111111111', 'Message with files', ['file1.txt', 'file2.jpg']);
|
|
166
|
+
// Advance timers to process queue and cleanup timer
|
|
167
|
+
jest.advanceTimersByTime(2500); // 250ms for queue + 2000ms for cleanup
|
|
168
|
+
await Promise.resolve();
|
|
169
|
+
await sendPromise;
|
|
151
170
|
expect(sendMessageSpy).toHaveBeenCalledWith('+1111111111', 'Message with files', {
|
|
152
171
|
attachments: ['file1.txt', 'file2.jpg']
|
|
153
172
|
});
|
|
173
|
+
jest.useRealTimers();
|
|
154
174
|
});
|
|
155
175
|
});
|
|
156
176
|
describe('Configuration', () => {
|
|
@@ -17,7 +17,12 @@ describe('SignalBot', () => {
|
|
|
17
17
|
afterEach(async () => {
|
|
18
18
|
if (bot) {
|
|
19
19
|
await bot.stop();
|
|
20
|
+
// Clean up all listeners
|
|
21
|
+
bot.removeAllListeners();
|
|
22
|
+
bot.getSignalCli().removeAllListeners();
|
|
20
23
|
}
|
|
24
|
+
// Clear all timers
|
|
25
|
+
jest.clearAllTimers();
|
|
21
26
|
});
|
|
22
27
|
test('should create bot with minimal config', () => {
|
|
23
28
|
bot = new SignalBot_1.SignalBot({
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Advanced Features Tests for SignalCli
|
|
4
|
+
* Tests for new features added for 100% signal-cli coverage
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const SignalCli_1 = require("../SignalCli");
|
|
8
|
+
describe('SignalCli Advanced Features', () => {
|
|
9
|
+
let signalCli;
|
|
10
|
+
let sendJsonRpcRequestSpy;
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
signalCli = new SignalCli_1.SignalCli('+1234567890');
|
|
13
|
+
sendJsonRpcRequestSpy = jest.spyOn(signalCli, 'sendJsonRpcRequest')
|
|
14
|
+
.mockResolvedValue({ timestamp: Date.now(), results: [] });
|
|
15
|
+
});
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
jest.restoreAllMocks();
|
|
18
|
+
});
|
|
19
|
+
describe('Advanced sendMessage Options', () => {
|
|
20
|
+
it('should send message with text styles', async () => {
|
|
21
|
+
await signalCli.sendMessage('+33123456789', 'Hello *bold* text', {
|
|
22
|
+
textStyles: [
|
|
23
|
+
{ start: 6, length: 6, style: 'BOLD' }
|
|
24
|
+
]
|
|
25
|
+
});
|
|
26
|
+
expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('send', expect.objectContaining({
|
|
27
|
+
message: 'Hello *bold* text',
|
|
28
|
+
textStyles: [
|
|
29
|
+
{ start: 6, length: 6, style: 'BOLD' }
|
|
30
|
+
]
|
|
31
|
+
}));
|
|
32
|
+
});
|
|
33
|
+
it('should send message with mentions', async () => {
|
|
34
|
+
await signalCli.sendMessage('+33123456789', 'Hello @John', {
|
|
35
|
+
mentions: [
|
|
36
|
+
{ start: 6, length: 5, number: '+33111111111' }
|
|
37
|
+
]
|
|
38
|
+
});
|
|
39
|
+
expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('send', expect.objectContaining({
|
|
40
|
+
message: 'Hello @John',
|
|
41
|
+
mentions: [
|
|
42
|
+
{ start: 6, length: 5, number: '+33111111111' }
|
|
43
|
+
]
|
|
44
|
+
}));
|
|
45
|
+
});
|
|
46
|
+
it('should send message with preview URL', async () => {
|
|
47
|
+
await signalCli.sendMessage('+33123456789', 'Check this out', {
|
|
48
|
+
previewUrl: 'https://example.com'
|
|
49
|
+
});
|
|
50
|
+
expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('send', expect.objectContaining({
|
|
51
|
+
message: 'Check this out',
|
|
52
|
+
previewUrl: 'https://example.com'
|
|
53
|
+
}));
|
|
54
|
+
});
|
|
55
|
+
it('should send message with quote and advanced fields', async () => {
|
|
56
|
+
await signalCli.sendMessage('+33123456789', 'I agree!', {
|
|
57
|
+
quote: {
|
|
58
|
+
timestamp: 123456789,
|
|
59
|
+
author: '+33111111111',
|
|
60
|
+
text: 'Original message',
|
|
61
|
+
mentions: [{ start: 0, length: 5, number: '+33222222222' }],
|
|
62
|
+
textStyles: [{ start: 0, length: 8, style: 'BOLD' }]
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('send', expect.objectContaining({
|
|
66
|
+
message: 'I agree!',
|
|
67
|
+
quoteTimestamp: 123456789,
|
|
68
|
+
quoteAuthor: '+33111111111',
|
|
69
|
+
quoteMessage: 'Original message',
|
|
70
|
+
quoteMentions: [{ start: 0, length: 5, number: '+33222222222' }],
|
|
71
|
+
quoteTextStyles: [{ start: 0, length: 8, style: 'BOLD' }]
|
|
72
|
+
}));
|
|
73
|
+
});
|
|
74
|
+
it('should send message edit', async () => {
|
|
75
|
+
await signalCli.sendMessage('+33123456789', 'Corrected text', {
|
|
76
|
+
editTimestamp: 123456789
|
|
77
|
+
});
|
|
78
|
+
expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('send', expect.objectContaining({
|
|
79
|
+
message: 'Corrected text',
|
|
80
|
+
editTimestamp: 123456789
|
|
81
|
+
}));
|
|
82
|
+
});
|
|
83
|
+
it('should send reply to story', async () => {
|
|
84
|
+
await signalCli.sendMessage('+33123456789', 'Nice story!', {
|
|
85
|
+
storyTimestamp: 123456789,
|
|
86
|
+
storyAuthor: '+33111111111'
|
|
87
|
+
});
|
|
88
|
+
expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('send', expect.objectContaining({
|
|
89
|
+
message: 'Nice story!',
|
|
90
|
+
storyTimestamp: 123456789,
|
|
91
|
+
storyAuthor: '+33111111111'
|
|
92
|
+
}));
|
|
93
|
+
});
|
|
94
|
+
it('should send message with noteToSelf flag', async () => {
|
|
95
|
+
await signalCli.sendMessage('+1234567890', 'Note to myself', {
|
|
96
|
+
noteToSelf: true
|
|
97
|
+
});
|
|
98
|
+
expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('send', expect.objectContaining({
|
|
99
|
+
message: 'Note to myself',
|
|
100
|
+
noteToSelf: true
|
|
101
|
+
}));
|
|
102
|
+
});
|
|
103
|
+
it('should send message with endSession flag', async () => {
|
|
104
|
+
await signalCli.sendMessage('+33123456789', 'Goodbye', {
|
|
105
|
+
endSession: true
|
|
106
|
+
});
|
|
107
|
+
expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('send', expect.objectContaining({
|
|
108
|
+
message: 'Goodbye',
|
|
109
|
+
endSession: true
|
|
110
|
+
}));
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
describe('receive() Method', () => {
|
|
114
|
+
it('should receive messages with default options', async () => {
|
|
115
|
+
sendJsonRpcRequestSpy.mockResolvedValue([
|
|
116
|
+
{
|
|
117
|
+
timestamp: 123456789,
|
|
118
|
+
source: '+33123456789',
|
|
119
|
+
dataMessage: { message: 'Hello!' }
|
|
120
|
+
}
|
|
121
|
+
]);
|
|
122
|
+
const messages = await signalCli.receive();
|
|
123
|
+
expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('receive', expect.objectContaining({
|
|
124
|
+
account: '+1234567890'
|
|
125
|
+
}));
|
|
126
|
+
expect(messages).toHaveLength(1);
|
|
127
|
+
expect(messages[0].text).toBe('Hello!');
|
|
128
|
+
});
|
|
129
|
+
it('should receive messages with custom timeout', async () => {
|
|
130
|
+
sendJsonRpcRequestSpy.mockResolvedValue([]);
|
|
131
|
+
await signalCli.receive({ timeout: 10 });
|
|
132
|
+
expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('receive', expect.objectContaining({
|
|
133
|
+
timeout: 10
|
|
134
|
+
}));
|
|
135
|
+
});
|
|
136
|
+
it('should receive messages with maxMessages limit', async () => {
|
|
137
|
+
sendJsonRpcRequestSpy.mockResolvedValue([]);
|
|
138
|
+
await signalCli.receive({ maxMessages: 5 });
|
|
139
|
+
expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('receive', expect.objectContaining({
|
|
140
|
+
maxMessages: 5
|
|
141
|
+
}));
|
|
142
|
+
});
|
|
143
|
+
it('should receive messages with ignoreAttachments', async () => {
|
|
144
|
+
await signalCli.receive({ ignoreAttachments: true });
|
|
145
|
+
expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('receive', expect.objectContaining({
|
|
146
|
+
ignoreAttachments: true
|
|
147
|
+
}));
|
|
148
|
+
});
|
|
149
|
+
it('should receive messages with ignoreStories', async () => {
|
|
150
|
+
await signalCli.receive({ ignoreStories: true });
|
|
151
|
+
expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('receive', expect.objectContaining({
|
|
152
|
+
ignoreStories: true
|
|
153
|
+
}));
|
|
154
|
+
});
|
|
155
|
+
it('should receive messages with sendReadReceipts', async () => {
|
|
156
|
+
await signalCli.receive({ sendReadReceipts: true });
|
|
157
|
+
expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('receive', expect.objectContaining({
|
|
158
|
+
sendReadReceipts: true
|
|
159
|
+
}));
|
|
160
|
+
});
|
|
161
|
+
it('should handle empty message array', async () => {
|
|
162
|
+
sendJsonRpcRequestSpy.mockResolvedValue([]);
|
|
163
|
+
const messages = await signalCli.receive();
|
|
164
|
+
expect(messages).toEqual([]);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
describe('Username Management Helpers', () => {
|
|
168
|
+
it('should set username', async () => {
|
|
169
|
+
sendJsonRpcRequestSpy.mockResolvedValue({
|
|
170
|
+
username: 'myuser.123',
|
|
171
|
+
usernameLink: 'https://signal.me/#myuser.123'
|
|
172
|
+
});
|
|
173
|
+
const result = await signalCli.setUsername('myuser');
|
|
174
|
+
expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('updateAccount', expect.objectContaining({
|
|
175
|
+
username: 'myuser'
|
|
176
|
+
}));
|
|
177
|
+
expect(result.success).toBe(true);
|
|
178
|
+
expect(result.username).toBe('myuser.123');
|
|
179
|
+
});
|
|
180
|
+
it('should delete username', async () => {
|
|
181
|
+
sendJsonRpcRequestSpy.mockResolvedValue({});
|
|
182
|
+
const result = await signalCli.deleteUsername();
|
|
183
|
+
expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('updateAccount', expect.objectContaining({
|
|
184
|
+
deleteUsername: true
|
|
185
|
+
}));
|
|
186
|
+
expect(result.success).toBe(true);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
describe('Advanced Identity Management', () => {
|
|
190
|
+
it('should get safety number', async () => {
|
|
191
|
+
sendJsonRpcRequestSpy.mockResolvedValue([
|
|
192
|
+
{
|
|
193
|
+
number: '+33123456789',
|
|
194
|
+
safetyNumber: '12345 67890 12345 67890 12345 67890',
|
|
195
|
+
trustLevel: 'TRUSTED'
|
|
196
|
+
}
|
|
197
|
+
]);
|
|
198
|
+
const safetyNumber = await signalCli.getSafetyNumber('+33123456789');
|
|
199
|
+
expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('listIdentities', expect.objectContaining({
|
|
200
|
+
number: '+33123456789'
|
|
201
|
+
}));
|
|
202
|
+
expect(safetyNumber).toBe('12345 67890 12345 67890 12345 67890');
|
|
203
|
+
});
|
|
204
|
+
it('should return null when safety number not found', async () => {
|
|
205
|
+
sendJsonRpcRequestSpy.mockResolvedValue([]);
|
|
206
|
+
const safetyNumber = await signalCli.getSafetyNumber('+33123456789');
|
|
207
|
+
expect(safetyNumber).toBeNull();
|
|
208
|
+
});
|
|
209
|
+
it('should verify safety number successfully', async () => {
|
|
210
|
+
// Mock listIdentities response
|
|
211
|
+
sendJsonRpcRequestSpy.mockResolvedValueOnce([
|
|
212
|
+
{
|
|
213
|
+
number: '+33123456789',
|
|
214
|
+
safetyNumber: '12345 67890 12345 67890 12345 67890',
|
|
215
|
+
trustLevel: 'TRUSTED'
|
|
216
|
+
}
|
|
217
|
+
]);
|
|
218
|
+
// Mock trustIdentity response
|
|
219
|
+
sendJsonRpcRequestSpy.mockResolvedValueOnce({});
|
|
220
|
+
const verified = await signalCli.verifySafetyNumber('+33123456789', '12345 67890 12345 67890 12345 67890');
|
|
221
|
+
expect(verified).toBe(true);
|
|
222
|
+
expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('trust', expect.objectContaining({
|
|
223
|
+
recipient: '+33123456789'
|
|
224
|
+
}));
|
|
225
|
+
});
|
|
226
|
+
it('should fail to verify incorrect safety number', async () => {
|
|
227
|
+
sendJsonRpcRequestSpy.mockResolvedValue([
|
|
228
|
+
{
|
|
229
|
+
number: '+33123456789',
|
|
230
|
+
safetyNumber: '12345 67890 12345 67890 12345 67890',
|
|
231
|
+
trustLevel: 'TRUSTED'
|
|
232
|
+
}
|
|
233
|
+
]);
|
|
234
|
+
const verified = await signalCli.verifySafetyNumber('+33123456789', '99999 99999 99999 99999 99999 99999');
|
|
235
|
+
expect(verified).toBe(false);
|
|
236
|
+
});
|
|
237
|
+
it('should list untrusted identities', async () => {
|
|
238
|
+
sendJsonRpcRequestSpy.mockResolvedValue([
|
|
239
|
+
{ number: '+33111111111', trustLevel: 'TRUSTED' },
|
|
240
|
+
{ number: '+33222222222', trustLevel: 'UNTRUSTED' },
|
|
241
|
+
{ number: '+33333333333', trustLevel: 'TRUST_ON_FIRST_USE' },
|
|
242
|
+
{ number: '+33444444444' }
|
|
243
|
+
]);
|
|
244
|
+
const untrusted = await signalCli.listUntrustedIdentities();
|
|
245
|
+
expect(untrusted).toHaveLength(3);
|
|
246
|
+
expect(untrusted.map(i => i.number)).toEqual([
|
|
247
|
+
'+33222222222',
|
|
248
|
+
'+33333333333',
|
|
249
|
+
'+33444444444'
|
|
250
|
+
]);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
describe('Advanced Group Management', () => {
|
|
254
|
+
it('should send group invite link', async () => {
|
|
255
|
+
// Mock listGroups response
|
|
256
|
+
sendJsonRpcRequestSpy.mockResolvedValueOnce([
|
|
257
|
+
{
|
|
258
|
+
groupId: 'group123==',
|
|
259
|
+
name: 'Test Group',
|
|
260
|
+
groupInviteLink: 'https://signal.group/...'
|
|
261
|
+
}
|
|
262
|
+
]);
|
|
263
|
+
// Mock sendMessage response
|
|
264
|
+
sendJsonRpcRequestSpy.mockResolvedValueOnce({ timestamp: Date.now() });
|
|
265
|
+
await signalCli.sendGroupInviteLink('group123==', '+33123456789');
|
|
266
|
+
expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('send', expect.objectContaining({
|
|
267
|
+
message: expect.stringContaining('https://signal.group/'),
|
|
268
|
+
recipients: ['+33123456789']
|
|
269
|
+
}));
|
|
270
|
+
});
|
|
271
|
+
it('should throw error when group has no invite link', async () => {
|
|
272
|
+
sendJsonRpcRequestSpy.mockResolvedValue([
|
|
273
|
+
{
|
|
274
|
+
groupId: 'group123==',
|
|
275
|
+
name: 'Test Group'
|
|
276
|
+
}
|
|
277
|
+
]);
|
|
278
|
+
await expect(signalCli.sendGroupInviteLink('group123==', '+33123456789')).rejects.toThrow('Group not found or does not have an invite link');
|
|
279
|
+
});
|
|
280
|
+
it('should set banned members', async () => {
|
|
281
|
+
await signalCli.setBannedMembers('group123==', ['+33111111111', '+33222222222']);
|
|
282
|
+
expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('updateGroup', expect.objectContaining({
|
|
283
|
+
groupId: 'group123==',
|
|
284
|
+
banMembers: ['+33111111111', '+33222222222']
|
|
285
|
+
}));
|
|
286
|
+
});
|
|
287
|
+
it('should reset group link', async () => {
|
|
288
|
+
await signalCli.resetGroupLink('group123==');
|
|
289
|
+
expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('updateGroup', expect.objectContaining({
|
|
290
|
+
groupId: 'group123==',
|
|
291
|
+
resetLink: true
|
|
292
|
+
}));
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
});
|