signal-sdk 0.1.0 → 0.1.1

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,240 @@
1
+ "use strict";
2
+ /**
3
+ * End-to-end integration tests for complete workflows (Phase 6)
4
+ * Tests realistic scenarios combining multiple operations
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const SignalCli_1 = require("../SignalCli");
8
+ describe('SignalCli - E2E Workflow Tests (Phase 6)', () => {
9
+ let signal;
10
+ const mockSendResponse = {
11
+ timestamp: Date.now(),
12
+ results: [{ type: 'SUCCESS' }],
13
+ };
14
+ beforeEach(() => {
15
+ signal = new SignalCli_1.SignalCli('+33123456789');
16
+ });
17
+ describe('Complete Messaging Workflow', () => {
18
+ it('should send message with full options and handle response', async () => {
19
+ signal.sendJsonRpcRequest = jest.fn()
20
+ .mockResolvedValue(mockSendResponse);
21
+ const result = await signal.sendMessage('+33987654321', 'Hello!', {
22
+ attachments: ['/path/to/image.jpg'],
23
+ textStyles: [
24
+ { start: 0, length: 5, style: 'BOLD' },
25
+ ],
26
+ mentions: [
27
+ { start: 0, length: 5, number: '+33987654321' },
28
+ ],
29
+ });
30
+ expect(result.timestamp).toBeDefined();
31
+ expect(result.results).toBeDefined();
32
+ });
33
+ it('should send quoted message with attachments', async () => {
34
+ signal.sendJsonRpcRequest = jest.fn()
35
+ .mockResolvedValue(mockSendResponse);
36
+ const result = await signal.sendMessage('+33987654321', 'Reply', {
37
+ quote: {
38
+ timestamp: 1234567890,
39
+ author: '+33111111111',
40
+ text: 'Original message',
41
+ },
42
+ attachments: ['/path/to/doc.pdf'],
43
+ });
44
+ expect(result.timestamp).toBeDefined();
45
+ });
46
+ });
47
+ describe('Contact Management Workflow', () => {
48
+ it('should manage contacts through complete lifecycle', async () => {
49
+ const mockContacts = [
50
+ {
51
+ number: '+33111111111',
52
+ name: 'Alice',
53
+ blocked: false,
54
+ givenName: 'Alice',
55
+ familyName: 'Smith',
56
+ },
57
+ {
58
+ number: '+33222222222',
59
+ name: 'Bob',
60
+ blocked: false,
61
+ },
62
+ ];
63
+ signal.sendJsonRpcRequest = jest.fn()
64
+ .mockResolvedValueOnce(mockContacts)
65
+ .mockResolvedValueOnce(undefined)
66
+ .mockResolvedValueOnce([mockContacts[0]]);
67
+ const contacts = await signal.getContactsWithProfiles();
68
+ expect(contacts).toHaveLength(2);
69
+ expect(contacts[0].profileName).toBe('Alice Smith');
70
+ await signal.updateContact('+33222222222', 'Bob Johnson');
71
+ const updated = await signal.listContacts();
72
+ expect(signal.sendJsonRpcRequest).toHaveBeenCalledTimes(3);
73
+ });
74
+ });
75
+ describe('Group Management Workflow', () => {
76
+ it('should manage group through complete lifecycle', async () => {
77
+ const mockGroup = {
78
+ groupId: 'group123==',
79
+ name: 'Test Group',
80
+ isMember: true,
81
+ isBlocked: false,
82
+ messageExpirationTime: 0,
83
+ members: [
84
+ { number: '+33111111111' },
85
+ { number: '+33222222222' },
86
+ ],
87
+ pendingMembers: [],
88
+ requestingMembers: [],
89
+ admins: [{ number: '+33111111111' }],
90
+ banned: [],
91
+ permissionAddMember: 'EVERY_MEMBER',
92
+ permissionEditDetails: 'EVERY_MEMBER',
93
+ permissionSendMessage: 'EVERY_MEMBER',
94
+ groupInviteLink: 'https://signal.group/test',
95
+ };
96
+ signal.sendJsonRpcRequest = jest.fn()
97
+ .mockResolvedValueOnce([mockGroup])
98
+ .mockResolvedValueOnce(undefined)
99
+ .mockResolvedValueOnce(mockSendResponse)
100
+ .mockResolvedValueOnce([
101
+ {
102
+ ...mockGroup,
103
+ members: [
104
+ ...mockGroup.members,
105
+ { number: '+33333333333' },
106
+ ],
107
+ },
108
+ ]);
109
+ const groups = await signal.getGroupsWithDetails();
110
+ expect(groups).toHaveLength(1);
111
+ expect(groups[0].members).toHaveLength(2);
112
+ await signal.updateGroup('group123==', {
113
+ addMembers: ['+33333333333'],
114
+ });
115
+ await signal.sendMessage('group123==', 'Welcome new member!');
116
+ const updatedGroups = await signal.listGroups();
117
+ expect(updatedGroups[0].members).toHaveLength(3);
118
+ });
119
+ });
120
+ describe('Identity Management Workflow', () => {
121
+ it('should verify safety number before sending', async () => {
122
+ const mockSafetyNumber = '12345 67890 12345 67890 12345 67890';
123
+ const mockIdentities = [{
124
+ safetyNumber: mockSafetyNumber,
125
+ number: '+33987654321',
126
+ }];
127
+ signal.sendJsonRpcRequest = jest.fn()
128
+ .mockResolvedValueOnce(mockIdentities) // listIdentities
129
+ .mockResolvedValueOnce(mockIdentities) // listIdentities again for verify
130
+ .mockResolvedValueOnce(undefined) // trust
131
+ .mockResolvedValueOnce(mockSendResponse); // send
132
+ const safetyNumberResult = await signal.getSafetyNumber('+33987654321');
133
+ expect(safetyNumberResult).toBe(mockSafetyNumber);
134
+ await signal.verifySafetyNumber('+33987654321', mockSafetyNumber);
135
+ await signal.sendMessage('+33987654321', 'Secure message');
136
+ expect(signal.sendJsonRpcRequest).toHaveBeenCalledTimes(4);
137
+ });
138
+ });
139
+ describe('Username Management Workflow', () => {
140
+ it('should manage username lifecycle', async () => {
141
+ signal.sendJsonRpcRequest = jest.fn()
142
+ .mockResolvedValueOnce({ username: 'alice.01' })
143
+ .mockResolvedValueOnce(mockSendResponse)
144
+ .mockResolvedValueOnce(undefined);
145
+ const result = await signal.setUsername('alice.01');
146
+ expect(result.username).toBe('alice.01');
147
+ await signal.sendMessage('+33987654321', 'You can find me at @alice.01');
148
+ await signal.deleteUsername();
149
+ expect(signal.sendJsonRpcRequest).toHaveBeenCalledTimes(3);
150
+ });
151
+ });
152
+ describe('Receive and Process Messages Workflow', () => {
153
+ it('should receive and process incoming messages', async () => {
154
+ const mockMessages = [
155
+ {
156
+ envelope: {
157
+ source: '+33987654321',
158
+ timestamp: Date.now(),
159
+ dataMessage: {
160
+ message: 'Hello!',
161
+ timestamp: Date.now(),
162
+ },
163
+ },
164
+ },
165
+ {
166
+ envelope: {
167
+ source: '+33111111111',
168
+ timestamp: Date.now(),
169
+ dataMessage: {
170
+ message: 'Hi there!',
171
+ timestamp: Date.now(),
172
+ groupInfo: {
173
+ groupId: 'group123==',
174
+ },
175
+ },
176
+ },
177
+ },
178
+ ];
179
+ signal.sendJsonRpcRequest = jest.fn()
180
+ .mockResolvedValueOnce(mockMessages)
181
+ .mockResolvedValueOnce(mockSendResponse)
182
+ .mockResolvedValueOnce(mockSendResponse);
183
+ const messages = await signal.receive();
184
+ expect(messages).toHaveLength(2);
185
+ // Only send messages if the source/groupId fields exist
186
+ const msg0 = messages[0];
187
+ const msg1 = messages[1];
188
+ if (msg0.envelope && msg0.envelope.source) {
189
+ await signal.sendMessage(msg0.envelope.source, 'Hello back!');
190
+ }
191
+ if (msg1.envelope && msg1.envelope.dataMessage &&
192
+ msg1.envelope.dataMessage.groupInfo &&
193
+ msg1.envelope.dataMessage.groupInfo.groupId) {
194
+ await signal.sendMessage(msg1.envelope.dataMessage.groupInfo.groupId, 'Hi everyone!');
195
+ }
196
+ // Verify at least receive was called (message sending depends on data structure)
197
+ expect(signal.sendJsonRpcRequest).toHaveBeenCalled();
198
+ });
199
+ });
200
+ describe('Multi-Step Group Operations', () => {
201
+ it('should create group, add members, and send announcement', async () => {
202
+ const mockGroupId = 'newgroup123==';
203
+ signal.sendJsonRpcRequest = jest.fn()
204
+ .mockResolvedValueOnce({ groupId: mockGroupId })
205
+ .mockResolvedValueOnce(undefined)
206
+ .mockResolvedValueOnce(undefined)
207
+ .mockResolvedValueOnce(mockSendResponse);
208
+ const createResult = await signal.updateGroup(mockGroupId, {
209
+ name: 'New Team',
210
+ });
211
+ await signal.updateGroup(mockGroupId, {
212
+ addMembers: ['+33222222222', '+33333333333'],
213
+ });
214
+ await signal.updateGroup(mockGroupId, {
215
+ description: 'Our awesome team workspace',
216
+ });
217
+ await signal.sendMessage(mockGroupId, 'Welcome to the team! 🎉');
218
+ expect(signal.sendJsonRpcRequest).toHaveBeenCalledTimes(4);
219
+ });
220
+ });
221
+ describe('Error Handling in Workflows', () => {
222
+ it('should handle errors gracefully in message sending workflow', async () => {
223
+ signal.sendJsonRpcRequest = jest.fn()
224
+ .mockRejectedValueOnce(new Error('Network error'))
225
+ .mockResolvedValueOnce(mockSendResponse);
226
+ await expect(signal.sendMessage('+33987654321', 'Test')).rejects.toThrow('Network error');
227
+ const result = await signal.sendMessage('+33987654321', 'Test');
228
+ expect(result.timestamp).toBeDefined();
229
+ });
230
+ it('should handle non-existent group in workflow', async () => {
231
+ signal.sendJsonRpcRequest = jest.fn()
232
+ .mockResolvedValue([]);
233
+ const groups = await signal.getGroupsWithDetails();
234
+ expect(groups).toHaveLength(0);
235
+ signal.sendJsonRpcRequest = jest.fn()
236
+ .mockRejectedValue(new Error('Group not found'));
237
+ await expect(signal.sendMessage('nonexistent==', 'Test')).rejects.toThrow('Group not found');
238
+ });
239
+ });
240
+ });
@@ -34,7 +34,12 @@ describe('SignalCli Integration Tests', () => {
34
34
  afterEach(() => {
35
35
  if (signalCli) {
36
36
  signalCli.disconnect();
37
+ // Clean up all listeners
38
+ signalCli.removeAllListeners();
37
39
  }
40
+ // Clear all timers and mocks
41
+ jest.clearAllTimers();
42
+ jest.clearAllMocks();
38
43
  });
39
44
  describe('Connection Lifecycle', () => {
40
45
  it('should handle multiple connect/disconnect cycles', async () => {
@@ -62,11 +67,13 @@ describe('SignalCli Integration Tests', () => {
62
67
  }
63
68
  });
64
69
  const shutdownPromise = signalCli.gracefulShutdown();
65
- // Simulate timeout and force kill
66
- setTimeout(() => {
70
+ // Simulate timeout and force kill with unref to prevent hanging
71
+ const timer = setTimeout(() => {
67
72
  if (closeCallback)
68
73
  closeCallback(0);
69
74
  }, 100);
75
+ if (timer.unref)
76
+ timer.unref();
70
77
  await expect(shutdownPromise).resolves.toBeUndefined();
71
78
  }, 10000); // Increase timeout to 10 seconds
72
79
  it('should handle stderr data with different log levels', async () => {
@@ -71,7 +71,7 @@ describe('SignalCli Methods Tests', () => {
71
71
  it('should send view-once message', async () => {
72
72
  await signalCli.sendMessage('+1234567890', 'View once', { isViewOnce: true });
73
73
  expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('send', expect.objectContaining({
74
- isViewOnce: true
74
+ viewOnce: true
75
75
  }));
76
76
  });
77
77
  it('should send reaction', async () => {
@@ -467,4 +467,90 @@ describe('SignalCli Methods Tests', () => {
467
467
  }));
468
468
  });
469
469
  });
470
+ describe('Phone Number Change Methods', () => {
471
+ it('should start change number with SMS', async () => {
472
+ await signalCli.startChangeNumber('+33612345678');
473
+ expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('startChangeNumber', {
474
+ account: '+1234567890',
475
+ number: '+33612345678',
476
+ voice: false
477
+ });
478
+ });
479
+ it('should start change number with voice verification', async () => {
480
+ await signalCli.startChangeNumber('+33612345678', true);
481
+ expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('startChangeNumber', {
482
+ account: '+1234567890',
483
+ number: '+33612345678',
484
+ voice: true
485
+ });
486
+ });
487
+ it('should start change number with captcha', async () => {
488
+ const captcha = 'captcha_token_12345';
489
+ await signalCli.startChangeNumber('+33612345678', false, captcha);
490
+ expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('startChangeNumber', {
491
+ account: '+1234567890',
492
+ number: '+33612345678',
493
+ voice: false,
494
+ captcha
495
+ });
496
+ });
497
+ it('should validate phone number in startChangeNumber', async () => {
498
+ await expect(signalCli.startChangeNumber('invalid')).rejects.toThrow();
499
+ });
500
+ it('should finish change number without PIN', async () => {
501
+ await signalCli.finishChangeNumber('+33612345678', '123456');
502
+ expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('finishChangeNumber', {
503
+ account: '+1234567890',
504
+ number: '+33612345678',
505
+ verificationCode: '123456'
506
+ });
507
+ });
508
+ it('should finish change number with PIN', async () => {
509
+ await signalCli.finishChangeNumber('+33612345678', '123456', '1234');
510
+ expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('finishChangeNumber', {
511
+ account: '+1234567890',
512
+ number: '+33612345678',
513
+ verificationCode: '123456',
514
+ pin: '1234'
515
+ });
516
+ });
517
+ it('should validate phone number in finishChangeNumber', async () => {
518
+ await expect(signalCli.finishChangeNumber('invalid', '123456')).rejects.toThrow();
519
+ });
520
+ it('should require verification code in finishChangeNumber', async () => {
521
+ await expect(signalCli.finishChangeNumber('+33612345678', '')).rejects.toThrow('Verification code is required');
522
+ });
523
+ });
524
+ describe('Payment Notification Methods', () => {
525
+ it('should send payment notification', async () => {
526
+ const receipt = 'base64EncodedReceipt';
527
+ const note = 'Thanks for dinner!';
528
+ const recipient = '+1111111111';
529
+ sendJsonRpcRequestSpy.mockResolvedValue({ timestamp: 1234567890 });
530
+ const result = await signalCli.sendPaymentNotification(recipient, { receipt, note });
531
+ expect(result.timestamp).toBe(1234567890);
532
+ expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('sendPaymentNotification', {
533
+ account: '+1234567890',
534
+ recipient,
535
+ receipt,
536
+ note
537
+ });
538
+ });
539
+ it('should send payment notification without note', async () => {
540
+ const receipt = 'base64EncodedReceipt';
541
+ const recipient = '+1111111111';
542
+ await signalCli.sendPaymentNotification(recipient, { receipt });
543
+ expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('sendPaymentNotification', {
544
+ account: '+1234567890',
545
+ recipient,
546
+ receipt
547
+ });
548
+ });
549
+ it('should validate recipient in sendPaymentNotification', async () => {
550
+ await expect(signalCli.sendPaymentNotification('invalid', { receipt: 'test' })).rejects.toThrow();
551
+ });
552
+ it('should require receipt in sendPaymentNotification', async () => {
553
+ await expect(signalCli.sendPaymentNotification('+1111111111', { receipt: '' })).rejects.toThrow('Payment receipt is required');
554
+ });
555
+ });
470
556
  });
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Tests for enhanced parsing functionality (Phase 5)
3
+ * Tests profile and group data parsing methods
4
+ */
5
+ export {};
@@ -0,0 +1,258 @@
1
+ "use strict";
2
+ /**
3
+ * Tests for enhanced parsing functionality (Phase 5)
4
+ * Tests profile and group data parsing methods
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const SignalCli_1 = require("../SignalCli");
8
+ describe('SignalCli - Enhanced Parsing (Phase 5)', () => {
9
+ let signal;
10
+ beforeEach(() => {
11
+ signal = new SignalCli_1.SignalCli('+33123456789');
12
+ // Mock sendJsonRpcRequest to avoid actual signal-cli calls
13
+ signal.sendJsonRpcRequest = jest.fn();
14
+ });
15
+ describe('parseContactProfile', () => {
16
+ it('should parse contact with full profile data', () => {
17
+ const contact = {
18
+ number: '+33123456789',
19
+ name: 'John Doe',
20
+ uuid: 'test-uuid',
21
+ blocked: false,
22
+ givenName: 'John',
23
+ familyName: 'Doe',
24
+ mobileCoinAddress: 'mc1_test_address_123',
25
+ profileName: 'John Doe',
26
+ };
27
+ const parsed = signal.parseContactProfile(contact);
28
+ expect(parsed.givenName).toBe('John');
29
+ expect(parsed.familyName).toBe('Doe');
30
+ expect(parsed.mobileCoinAddress).toBe('mc1_test_address_123');
31
+ expect(parsed.profileName).toBe('John Doe');
32
+ });
33
+ it('should build profileName from givenName and familyName if missing', () => {
34
+ const contact = {
35
+ number: '+33123456789',
36
+ name: 'John Doe',
37
+ uuid: 'test-uuid',
38
+ blocked: false,
39
+ givenName: 'John',
40
+ familyName: 'Doe',
41
+ };
42
+ const parsed = signal.parseContactProfile(contact);
43
+ expect(parsed.profileName).toBe('John Doe');
44
+ });
45
+ it('should use givenName as profileName if no familyName', () => {
46
+ const contact = {
47
+ number: '+33123456789',
48
+ name: 'John',
49
+ uuid: 'test-uuid',
50
+ blocked: false,
51
+ givenName: 'John',
52
+ };
53
+ const parsed = signal.parseContactProfile(contact);
54
+ expect(parsed.profileName).toBe('John');
55
+ });
56
+ it('should handle contact with minimal data', () => {
57
+ const contact = {
58
+ number: '+33123456789',
59
+ name: 'Unknown',
60
+ blocked: false,
61
+ };
62
+ const parsed = signal.parseContactProfile(contact);
63
+ expect(parsed.givenName).toBeUndefined();
64
+ expect(parsed.familyName).toBeUndefined();
65
+ expect(parsed.mobileCoinAddress).toBeUndefined();
66
+ });
67
+ it('should preserve additional contact fields', () => {
68
+ const contact = {
69
+ number: '+33123456789',
70
+ name: 'John Doe',
71
+ uuid: 'test-uuid',
72
+ blocked: true,
73
+ profileAvatar: 'avatar.jpg',
74
+ color: 'blue',
75
+ archived: true,
76
+ mutedUntil: 123456789,
77
+ hideStory: true,
78
+ givenName: 'John',
79
+ };
80
+ const parsed = signal.parseContactProfile(contact);
81
+ expect(parsed.blocked).toBe(true);
82
+ expect(parsed.profileAvatar).toBe('avatar.jpg');
83
+ expect(parsed.color).toBe('blue');
84
+ expect(parsed.archived).toBe(true);
85
+ expect(parsed.mutedUntil).toBe(123456789);
86
+ expect(parsed.hideStory).toBe(true);
87
+ });
88
+ });
89
+ describe('parseGroupDetails', () => {
90
+ it('should parse group with full membership data', () => {
91
+ const group = {
92
+ groupId: 'group123==',
93
+ name: 'Test Group',
94
+ description: 'A test group',
95
+ isMember: true,
96
+ isBlocked: false,
97
+ messageExpirationTime: 0,
98
+ members: [
99
+ { number: '+33111111111' },
100
+ { number: '+33222222222' },
101
+ ],
102
+ pendingMembers: [
103
+ { number: '+33333333333' },
104
+ ],
105
+ requestingMembers: [],
106
+ admins: [
107
+ { number: '+33111111111' },
108
+ ],
109
+ banned: [
110
+ { number: '+33444444444' },
111
+ ],
112
+ permissionAddMember: 'ONLY_ADMINS',
113
+ permissionEditDetails: 'ONLY_ADMINS',
114
+ permissionSendMessage: 'EVERY_MEMBER',
115
+ groupInviteLink: 'https://signal.group/test',
116
+ };
117
+ const parsed = signal.parseGroupDetails(group);
118
+ expect(parsed.pendingMembers).toHaveLength(1);
119
+ expect(parsed.pendingMembers[0].number).toBe('+33333333333');
120
+ expect(parsed.banned).toHaveLength(1);
121
+ expect(parsed.banned[0].number).toBe('+33444444444');
122
+ expect(parsed.inviteLink).toBe('https://signal.group/test');
123
+ expect(parsed.groupInviteLink).toBe('https://signal.group/test');
124
+ });
125
+ it('should normalize inviteLink field', () => {
126
+ const group1 = {
127
+ groupId: 'group123==',
128
+ name: 'Test Group',
129
+ isMember: true,
130
+ isBlocked: false,
131
+ messageExpirationTime: 0,
132
+ members: [],
133
+ pendingMembers: [],
134
+ requestingMembers: [],
135
+ admins: [],
136
+ banned: [],
137
+ permissionAddMember: 'EVERY_MEMBER',
138
+ permissionEditDetails: 'EVERY_MEMBER',
139
+ permissionSendMessage: 'EVERY_MEMBER',
140
+ groupInviteLink: 'https://signal.group/test1',
141
+ };
142
+ const parsed1 = signal.parseGroupDetails(group1);
143
+ expect(parsed1.inviteLink).toBe('https://signal.group/test1');
144
+ expect(parsed1.groupInviteLink).toBe('https://signal.group/test1');
145
+ const group2 = {
146
+ ...group1,
147
+ groupInviteLink: undefined,
148
+ inviteLink: 'https://signal.group/test2',
149
+ };
150
+ const parsed2 = signal.parseGroupDetails(group2);
151
+ expect(parsed2.inviteLink).toBe('https://signal.group/test2');
152
+ expect(parsed2.groupInviteLink).toBe('https://signal.group/test2');
153
+ });
154
+ it('should ensure membership arrays exist', () => {
155
+ const group = {
156
+ groupId: 'group123==',
157
+ name: 'Minimal Group',
158
+ isMember: true,
159
+ isBlocked: false,
160
+ messageExpirationTime: 0,
161
+ members: [],
162
+ pendingMembers: [],
163
+ requestingMembers: [],
164
+ admins: [],
165
+ banned: [],
166
+ permissionAddMember: 'EVERY_MEMBER',
167
+ permissionEditDetails: 'EVERY_MEMBER',
168
+ permissionSendMessage: 'EVERY_MEMBER',
169
+ };
170
+ const parsed = signal.parseGroupDetails(group);
171
+ expect(Array.isArray(parsed.members)).toBe(true);
172
+ expect(Array.isArray(parsed.pendingMembers)).toBe(true);
173
+ expect(Array.isArray(parsed.requestingMembers)).toBe(true);
174
+ expect(Array.isArray(parsed.admins)).toBe(true);
175
+ expect(Array.isArray(parsed.banned)).toBe(true);
176
+ });
177
+ it('should preserve group permissions', () => {
178
+ const group = {
179
+ groupId: 'group123==',
180
+ name: 'Restricted Group',
181
+ isMember: true,
182
+ isBlocked: false,
183
+ messageExpirationTime: 3600,
184
+ members: [],
185
+ pendingMembers: [],
186
+ requestingMembers: [],
187
+ admins: [],
188
+ banned: [],
189
+ permissionAddMember: 'ONLY_ADMINS',
190
+ permissionEditDetails: 'ONLY_ADMINS',
191
+ permissionSendMessage: 'ONLY_ADMINS',
192
+ };
193
+ const parsed = signal.parseGroupDetails(group);
194
+ expect(parsed.permissionAddMember).toBe('ONLY_ADMINS');
195
+ expect(parsed.permissionEditDetails).toBe('ONLY_ADMINS');
196
+ expect(parsed.permissionSendMessage).toBe('ONLY_ADMINS');
197
+ expect(parsed.messageExpirationTime).toBe(3600);
198
+ });
199
+ });
200
+ describe('getContactsWithProfiles', () => {
201
+ it('should return enriched contacts list', async () => {
202
+ const mockContacts = [
203
+ {
204
+ number: '+33111111111',
205
+ name: 'Alice',
206
+ blocked: false,
207
+ givenName: 'Alice',
208
+ familyName: 'Smith',
209
+ },
210
+ {
211
+ number: '+33222222222',
212
+ name: 'Bob',
213
+ blocked: false,
214
+ givenName: 'Bob',
215
+ mobileCoinAddress: 'mc1_bob',
216
+ },
217
+ ];
218
+ signal.sendJsonRpcRequest = jest.fn().mockResolvedValue(mockContacts);
219
+ const enriched = await signal.getContactsWithProfiles();
220
+ expect(enriched).toHaveLength(2);
221
+ expect(enriched[0].profileName).toBe('Alice Smith');
222
+ expect(enriched[1].profileName).toBe('Bob');
223
+ expect(enriched[1].mobileCoinAddress).toBe('mc1_bob');
224
+ });
225
+ });
226
+ describe('getGroupsWithDetails', () => {
227
+ it('should return enriched groups list', async () => {
228
+ const mockGroups = [
229
+ {
230
+ groupId: 'group1==',
231
+ name: 'Group 1',
232
+ isMember: true,
233
+ isBlocked: false,
234
+ messageExpirationTime: 0,
235
+ members: [{ number: '+33111111111' }],
236
+ pendingMembers: [{ number: '+33222222222' }],
237
+ requestingMembers: [],
238
+ admins: [{ number: '+33111111111' }],
239
+ banned: [],
240
+ permissionAddMember: 'EVERY_MEMBER',
241
+ permissionEditDetails: 'EVERY_MEMBER',
242
+ permissionSendMessage: 'EVERY_MEMBER',
243
+ groupInviteLink: 'https://signal.group/group1',
244
+ },
245
+ ];
246
+ signal.sendJsonRpcRequest = jest.fn().mockResolvedValue(mockGroups);
247
+ const enriched = await signal.getGroupsWithDetails();
248
+ expect(enriched).toHaveLength(1);
249
+ expect(enriched[0].inviteLink).toBe('https://signal.group/group1');
250
+ expect(enriched[0].pendingMembers).toHaveLength(1);
251
+ });
252
+ it('should support list options', async () => {
253
+ signal.sendJsonRpcRequest = jest.fn().mockResolvedValue([]);
254
+ await signal.getGroupsWithDetails({ detailed: true });
255
+ expect(signal.sendJsonRpcRequest).toHaveBeenCalledWith('listGroups', expect.objectContaining({ detailed: true }));
256
+ });
257
+ });
258
+ });
@@ -155,31 +155,23 @@ describe('SignalCli', () => {
155
155
  expect(result).toEqual(mockResponse);
156
156
  });
157
157
  it('should start change number', async () => {
158
- const mockResponse = {
159
- session: 'change-session-id',
160
- challenge: 'challenge-token'
161
- };
162
158
  const sendJsonRpcRequestSpy = jest.spyOn(signalCli, 'sendJsonRpcRequest')
163
- .mockResolvedValue(mockResponse);
164
- const result = await signalCli.startChangeNumber('+1987654321');
159
+ .mockResolvedValue({});
160
+ await signalCli.startChangeNumber('+1987654321');
165
161
  expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('startChangeNumber', {
166
162
  account: '+1234567890',
167
163
  number: '+1987654321',
168
164
  voice: false
169
165
  });
170
- expect(result).toEqual({
171
- session: 'change-session-id',
172
- newNumber: '+1987654321',
173
- challenge: 'challenge-token'
174
- });
175
166
  });
176
167
  it('should finish change number', async () => {
177
168
  const sendJsonRpcRequestSpy = jest.spyOn(signalCli, 'sendJsonRpcRequest')
178
169
  .mockResolvedValue({});
179
- await signalCli.finishChangeNumber('123456', 'pin-code');
170
+ await signalCli.finishChangeNumber('+1987654321', '123456', 'pin-code');
180
171
  expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('finishChangeNumber', {
181
172
  account: '+1234567890',
182
- code: '123456',
173
+ number: '+1987654321',
174
+ verificationCode: '123456',
183
175
  pin: 'pin-code'
184
176
  });
185
177
  });