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.
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Tests for MultiAccountManager
3
+ */
4
+ export {};
@@ -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
- // Wait for any pending async operations to complete
29
- await new Promise(resolve => setTimeout(resolve, 100));
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
- await bot.sendMessage('+1111111111', 'Test message');
125
- // Wait for queue to process
126
- await new Promise(resolve => setTimeout(resolve, 150));
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
- await bot.sendReaction('+1111111111', '+2222222222', 123456, '👍');
137
- // Wait for queue to process
138
- await new Promise(resolve => setTimeout(resolve, 150));
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
- 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));
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,5 @@
1
+ /**
2
+ * Advanced Features Tests for SignalCli
3
+ * Tests for new features added for 100% signal-cli coverage
4
+ */
5
+ export {};
@@ -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
+ });
@@ -0,0 +1,5 @@
1
+ /**
2
+ * End-to-end integration tests for complete workflows (Phase 6)
3
+ * Tests realistic scenarios combining multiple operations
4
+ */
5
+ export {};