signal-sdk 0.0.9 → 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.
- package/README.md +175 -59
- package/dist/SignalCli.d.ts +72 -2
- package/dist/SignalCli.js +257 -1
- package/dist/__tests__/SignalBot.additional.test.d.ts +5 -0
- package/dist/__tests__/SignalBot.additional.test.js +333 -0
- package/dist/__tests__/SignalCli.integration.test.d.ts +5 -0
- package/dist/__tests__/SignalCli.integration.test.js +218 -0
- package/dist/__tests__/SignalCli.methods.test.d.ts +5 -0
- package/dist/__tests__/SignalCli.methods.test.js +470 -0
- package/dist/__tests__/SignalCli.test.js +244 -0
- package/dist/__tests__/config.test.d.ts +5 -0
- package/dist/__tests__/config.test.js +252 -0
- package/dist/__tests__/errors.test.d.ts +5 -0
- package/dist/__tests__/errors.test.js +276 -0
- package/dist/__tests__/retry.test.d.ts +4 -0
- package/dist/__tests__/retry.test.js +123 -0
- package/dist/__tests__/validators.test.d.ts +4 -0
- package/dist/__tests__/validators.test.js +147 -0
- package/dist/config.d.ts +67 -0
- package/dist/config.js +111 -0
- package/dist/errors.d.ts +32 -0
- package/dist/errors.js +75 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/interfaces.d.ts +136 -1
- package/dist/interfaces.js +1 -1
- package/dist/retry.d.ts +56 -0
- package/dist/retry.js +135 -0
- package/dist/validators.d.ts +59 -0
- package/dist/validators.js +170 -0
- package/package.json +1 -1
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Additional tests for errors.ts
|
|
4
|
+
* Tests all error classes and their properties
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const errors_1 = require("../errors");
|
|
8
|
+
describe('Error Classes Additional Tests', () => {
|
|
9
|
+
describe('SignalError', () => {
|
|
10
|
+
test('should create basic SignalError', () => {
|
|
11
|
+
const error = new errors_1.SignalError('Test error');
|
|
12
|
+
expect(error).toBeInstanceOf(Error);
|
|
13
|
+
expect(error).toBeInstanceOf(errors_1.SignalError);
|
|
14
|
+
expect(error.message).toBe('Test error');
|
|
15
|
+
expect(error.name).toBe('SignalError');
|
|
16
|
+
expect(error.code).toBeUndefined();
|
|
17
|
+
});
|
|
18
|
+
test('should create SignalError with code', () => {
|
|
19
|
+
const error = new errors_1.SignalError('Test error', 'TEST_CODE');
|
|
20
|
+
expect(error.message).toBe('Test error');
|
|
21
|
+
expect(error.code).toBe('TEST_CODE');
|
|
22
|
+
});
|
|
23
|
+
test('should have proper prototype chain', () => {
|
|
24
|
+
const error = new errors_1.SignalError('Test');
|
|
25
|
+
expect(error instanceof errors_1.SignalError).toBe(true);
|
|
26
|
+
expect(error instanceof Error).toBe(true);
|
|
27
|
+
expect(Object.getPrototypeOf(error)).toBe(errors_1.SignalError.prototype);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
describe('ConnectionError', () => {
|
|
31
|
+
test('should create ConnectionError', () => {
|
|
32
|
+
const error = new errors_1.ConnectionError('Connection failed');
|
|
33
|
+
expect(error).toBeInstanceOf(Error);
|
|
34
|
+
expect(error).toBeInstanceOf(errors_1.SignalError);
|
|
35
|
+
expect(error).toBeInstanceOf(errors_1.ConnectionError);
|
|
36
|
+
expect(error.message).toBe('Connection failed');
|
|
37
|
+
expect(error.name).toBe('ConnectionError');
|
|
38
|
+
expect(error.code).toBe('CONNECTION_ERROR');
|
|
39
|
+
});
|
|
40
|
+
test('should have proper prototype chain', () => {
|
|
41
|
+
const error = new errors_1.ConnectionError('Test');
|
|
42
|
+
expect(error instanceof errors_1.ConnectionError).toBe(true);
|
|
43
|
+
expect(error instanceof errors_1.SignalError).toBe(true);
|
|
44
|
+
expect(error instanceof Error).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe('AuthenticationError', () => {
|
|
48
|
+
test('should create AuthenticationError', () => {
|
|
49
|
+
const error = new errors_1.AuthenticationError('Authentication failed');
|
|
50
|
+
expect(error).toBeInstanceOf(Error);
|
|
51
|
+
expect(error).toBeInstanceOf(errors_1.SignalError);
|
|
52
|
+
expect(error).toBeInstanceOf(errors_1.AuthenticationError);
|
|
53
|
+
expect(error.message).toBe('Authentication failed');
|
|
54
|
+
expect(error.name).toBe('AuthenticationError');
|
|
55
|
+
expect(error.code).toBe('AUTH_ERROR');
|
|
56
|
+
});
|
|
57
|
+
test('should have proper prototype chain', () => {
|
|
58
|
+
const error = new errors_1.AuthenticationError('Test');
|
|
59
|
+
expect(error instanceof errors_1.AuthenticationError).toBe(true);
|
|
60
|
+
expect(error instanceof errors_1.SignalError).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe('RateLimitError', () => {
|
|
64
|
+
test('should create RateLimitError without retry info', () => {
|
|
65
|
+
const error = new errors_1.RateLimitError('Rate limit exceeded');
|
|
66
|
+
expect(error).toBeInstanceOf(Error);
|
|
67
|
+
expect(error).toBeInstanceOf(errors_1.SignalError);
|
|
68
|
+
expect(error).toBeInstanceOf(errors_1.RateLimitError);
|
|
69
|
+
expect(error.message).toBe('Rate limit exceeded');
|
|
70
|
+
expect(error.name).toBe('RateLimitError');
|
|
71
|
+
expect(error.code).toBe('RATE_LIMIT');
|
|
72
|
+
expect(error.retryAfter).toBeUndefined();
|
|
73
|
+
expect(error.challenge).toBeUndefined();
|
|
74
|
+
});
|
|
75
|
+
test('should create RateLimitError with retryAfter', () => {
|
|
76
|
+
const error = new errors_1.RateLimitError('Rate limit exceeded', 60);
|
|
77
|
+
expect(error.message).toBe('Rate limit exceeded');
|
|
78
|
+
expect(error.retryAfter).toBe(60);
|
|
79
|
+
expect(error.challenge).toBeUndefined();
|
|
80
|
+
});
|
|
81
|
+
test('should create RateLimitError with challenge', () => {
|
|
82
|
+
const error = new errors_1.RateLimitError('Rate limit exceeded', 60, 'challenge-token');
|
|
83
|
+
expect(error.message).toBe('Rate limit exceeded');
|
|
84
|
+
expect(error.retryAfter).toBe(60);
|
|
85
|
+
expect(error.challenge).toBe('challenge-token');
|
|
86
|
+
});
|
|
87
|
+
test('should have proper prototype chain', () => {
|
|
88
|
+
const error = new errors_1.RateLimitError('Test');
|
|
89
|
+
expect(error instanceof errors_1.RateLimitError).toBe(true);
|
|
90
|
+
expect(error instanceof errors_1.SignalError).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
describe('ValidationError', () => {
|
|
94
|
+
test('should create ValidationError without field', () => {
|
|
95
|
+
const error = new errors_1.ValidationError('Invalid input');
|
|
96
|
+
expect(error).toBeInstanceOf(Error);
|
|
97
|
+
expect(error).toBeInstanceOf(errors_1.SignalError);
|
|
98
|
+
expect(error).toBeInstanceOf(errors_1.ValidationError);
|
|
99
|
+
expect(error.message).toBe('Invalid input');
|
|
100
|
+
expect(error.name).toBe('ValidationError');
|
|
101
|
+
expect(error.code).toBe('VALIDATION_ERROR');
|
|
102
|
+
expect(error.field).toBeUndefined();
|
|
103
|
+
});
|
|
104
|
+
test('should create ValidationError with field', () => {
|
|
105
|
+
const error = new errors_1.ValidationError('Invalid phone number', 'phoneNumber');
|
|
106
|
+
expect(error.message).toBe('Invalid phone number');
|
|
107
|
+
expect(error.field).toBe('phoneNumber');
|
|
108
|
+
});
|
|
109
|
+
test('should have proper prototype chain', () => {
|
|
110
|
+
const error = new errors_1.ValidationError('Test');
|
|
111
|
+
expect(error instanceof errors_1.ValidationError).toBe(true);
|
|
112
|
+
expect(error instanceof errors_1.SignalError).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
describe('TimeoutError', () => {
|
|
116
|
+
test('should create TimeoutError with default message', () => {
|
|
117
|
+
const error = new errors_1.TimeoutError();
|
|
118
|
+
expect(error).toBeInstanceOf(Error);
|
|
119
|
+
expect(error).toBeInstanceOf(errors_1.SignalError);
|
|
120
|
+
expect(error).toBeInstanceOf(errors_1.TimeoutError);
|
|
121
|
+
expect(error.message).toBe('Operation timed out');
|
|
122
|
+
expect(error.name).toBe('TimeoutError');
|
|
123
|
+
expect(error.code).toBe('TIMEOUT');
|
|
124
|
+
});
|
|
125
|
+
test('should create TimeoutError with custom message', () => {
|
|
126
|
+
const error = new errors_1.TimeoutError('Request timed out after 30s');
|
|
127
|
+
expect(error.message).toBe('Request timed out after 30s');
|
|
128
|
+
});
|
|
129
|
+
test('should have proper prototype chain', () => {
|
|
130
|
+
const error = new errors_1.TimeoutError();
|
|
131
|
+
expect(error instanceof errors_1.TimeoutError).toBe(true);
|
|
132
|
+
expect(error instanceof errors_1.SignalError).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
describe('GroupError', () => {
|
|
136
|
+
test('should create GroupError', () => {
|
|
137
|
+
const error = new errors_1.GroupError('Group not found');
|
|
138
|
+
expect(error).toBeInstanceOf(Error);
|
|
139
|
+
expect(error).toBeInstanceOf(errors_1.SignalError);
|
|
140
|
+
expect(error).toBeInstanceOf(errors_1.GroupError);
|
|
141
|
+
expect(error.message).toBe('Group not found');
|
|
142
|
+
expect(error.name).toBe('GroupError');
|
|
143
|
+
expect(error.code).toBe('GROUP_ERROR');
|
|
144
|
+
});
|
|
145
|
+
test('should have proper prototype chain', () => {
|
|
146
|
+
const error = new errors_1.GroupError('Test');
|
|
147
|
+
expect(error instanceof errors_1.GroupError).toBe(true);
|
|
148
|
+
expect(error instanceof errors_1.SignalError).toBe(true);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
describe('MessageError', () => {
|
|
152
|
+
test('should create MessageError', () => {
|
|
153
|
+
const error = new errors_1.MessageError('Failed to send message');
|
|
154
|
+
expect(error).toBeInstanceOf(Error);
|
|
155
|
+
expect(error).toBeInstanceOf(errors_1.SignalError);
|
|
156
|
+
expect(error).toBeInstanceOf(errors_1.MessageError);
|
|
157
|
+
expect(error.message).toBe('Failed to send message');
|
|
158
|
+
expect(error.name).toBe('MessageError');
|
|
159
|
+
expect(error.code).toBe('MESSAGE_ERROR');
|
|
160
|
+
});
|
|
161
|
+
test('should have proper prototype chain', () => {
|
|
162
|
+
const error = new errors_1.MessageError('Test');
|
|
163
|
+
expect(error instanceof errors_1.MessageError).toBe(true);
|
|
164
|
+
expect(error instanceof errors_1.SignalError).toBe(true);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
describe('Error Instanceof Checks', () => {
|
|
168
|
+
test('should correctly identify error types', () => {
|
|
169
|
+
const connection = new errors_1.ConnectionError('test');
|
|
170
|
+
const auth = new errors_1.AuthenticationError('test');
|
|
171
|
+
const rateLimit = new errors_1.RateLimitError('test');
|
|
172
|
+
const validation = new errors_1.ValidationError('test');
|
|
173
|
+
const timeout = new errors_1.TimeoutError('test');
|
|
174
|
+
const group = new errors_1.GroupError('test');
|
|
175
|
+
const message = new errors_1.MessageError('test');
|
|
176
|
+
// Each should be instance of itself
|
|
177
|
+
expect(connection instanceof errors_1.ConnectionError).toBe(true);
|
|
178
|
+
expect(auth instanceof errors_1.AuthenticationError).toBe(true);
|
|
179
|
+
expect(rateLimit instanceof errors_1.RateLimitError).toBe(true);
|
|
180
|
+
expect(validation instanceof errors_1.ValidationError).toBe(true);
|
|
181
|
+
expect(timeout instanceof errors_1.TimeoutError).toBe(true);
|
|
182
|
+
expect(group instanceof errors_1.GroupError).toBe(true);
|
|
183
|
+
expect(message instanceof errors_1.MessageError).toBe(true);
|
|
184
|
+
// All should be instances of SignalError
|
|
185
|
+
expect(connection instanceof errors_1.SignalError).toBe(true);
|
|
186
|
+
expect(auth instanceof errors_1.SignalError).toBe(true);
|
|
187
|
+
expect(rateLimit instanceof errors_1.SignalError).toBe(true);
|
|
188
|
+
expect(validation instanceof errors_1.SignalError).toBe(true);
|
|
189
|
+
expect(timeout instanceof errors_1.SignalError).toBe(true);
|
|
190
|
+
expect(group instanceof errors_1.SignalError).toBe(true);
|
|
191
|
+
expect(message instanceof errors_1.SignalError).toBe(true);
|
|
192
|
+
// All should be instances of Error
|
|
193
|
+
expect(connection instanceof Error).toBe(true);
|
|
194
|
+
expect(auth instanceof Error).toBe(true);
|
|
195
|
+
expect(rateLimit instanceof Error).toBe(true);
|
|
196
|
+
expect(validation instanceof Error).toBe(true);
|
|
197
|
+
expect(timeout instanceof Error).toBe(true);
|
|
198
|
+
expect(group instanceof Error).toBe(true);
|
|
199
|
+
expect(message instanceof Error).toBe(true);
|
|
200
|
+
});
|
|
201
|
+
test('should not confuse different error types', () => {
|
|
202
|
+
const connection = new errors_1.ConnectionError('test');
|
|
203
|
+
expect(connection instanceof errors_1.AuthenticationError).toBe(false);
|
|
204
|
+
expect(connection instanceof errors_1.RateLimitError).toBe(false);
|
|
205
|
+
expect(connection instanceof errors_1.ValidationError).toBe(false);
|
|
206
|
+
expect(connection instanceof errors_1.TimeoutError).toBe(false);
|
|
207
|
+
expect(connection instanceof errors_1.GroupError).toBe(false);
|
|
208
|
+
expect(connection instanceof errors_1.MessageError).toBe(false);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
describe('Error Stack Traces', () => {
|
|
212
|
+
test('should have stack traces', () => {
|
|
213
|
+
const error = new errors_1.ConnectionError('Test error');
|
|
214
|
+
expect(error.stack).toBeDefined();
|
|
215
|
+
expect(typeof error.stack).toBe('string');
|
|
216
|
+
expect(error.stack).toContain('ConnectionError');
|
|
217
|
+
});
|
|
218
|
+
test('should have meaningful stack traces for all error types', () => {
|
|
219
|
+
const errors = [
|
|
220
|
+
new errors_1.SignalError('test'),
|
|
221
|
+
new errors_1.ConnectionError('test'),
|
|
222
|
+
new errors_1.AuthenticationError('test'),
|
|
223
|
+
new errors_1.RateLimitError('test'),
|
|
224
|
+
new errors_1.ValidationError('test'),
|
|
225
|
+
new errors_1.TimeoutError('test'),
|
|
226
|
+
new errors_1.GroupError('test'),
|
|
227
|
+
new errors_1.MessageError('test')
|
|
228
|
+
];
|
|
229
|
+
errors.forEach(error => {
|
|
230
|
+
expect(error.stack).toBeDefined();
|
|
231
|
+
expect(error.stack?.length).toBeGreaterThan(0);
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
describe('Error Serialization', () => {
|
|
236
|
+
test('should serialize to JSON with custom properties', () => {
|
|
237
|
+
const rateLimit = new errors_1.RateLimitError('Rate limited', 60, 'challenge');
|
|
238
|
+
const validation = new errors_1.ValidationError('Invalid', 'testField');
|
|
239
|
+
// These errors have additional properties
|
|
240
|
+
expect(rateLimit.retryAfter).toBe(60);
|
|
241
|
+
expect(rateLimit.challenge).toBe('challenge');
|
|
242
|
+
expect(validation.field).toBe('testField');
|
|
243
|
+
});
|
|
244
|
+
test('should have toString method', () => {
|
|
245
|
+
const error = new errors_1.ConnectionError('Connection failed');
|
|
246
|
+
expect(error.toString()).toContain('ConnectionError');
|
|
247
|
+
expect(error.toString()).toContain('Connection failed');
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
describe('Error Throwing and Catching', () => {
|
|
251
|
+
test('should be catchable in try-catch', () => {
|
|
252
|
+
try {
|
|
253
|
+
throw new errors_1.ConnectionError('Test');
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
expect(error).toBeInstanceOf(errors_1.ConnectionError);
|
|
257
|
+
expect(error).toBeInstanceOf(errors_1.SignalError);
|
|
258
|
+
expect(error).toBeInstanceOf(Error);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
test('should work with instanceof in catch', () => {
|
|
262
|
+
try {
|
|
263
|
+
throw new errors_1.ValidationError('Invalid input', 'field');
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
if (error instanceof errors_1.ValidationError) {
|
|
267
|
+
expect(error.field).toBe('field');
|
|
268
|
+
expect(error.code).toBe('VALIDATION_ERROR');
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
fail('Should be ValidationError');
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Basic tests for retry utilities
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const retry_1 = require("../retry");
|
|
7
|
+
const errors_1 = require("../errors");
|
|
8
|
+
describe('Retry utilities', () => {
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
jest.clearAllTimers();
|
|
11
|
+
});
|
|
12
|
+
describe('withRetry', () => {
|
|
13
|
+
it('should succeed on first attempt', async () => {
|
|
14
|
+
const operation = jest.fn().mockResolvedValue('success');
|
|
15
|
+
const result = await (0, retry_1.withRetry)(operation);
|
|
16
|
+
expect(result).toBe('success');
|
|
17
|
+
expect(operation).toHaveBeenCalledTimes(1);
|
|
18
|
+
});
|
|
19
|
+
it('should retry on failure', async () => {
|
|
20
|
+
const operation = jest.fn()
|
|
21
|
+
.mockRejectedValueOnce(new Error('Connection failed'))
|
|
22
|
+
.mockResolvedValue('success');
|
|
23
|
+
const result = await (0, retry_1.withRetry)(operation, {
|
|
24
|
+
maxAttempts: 2,
|
|
25
|
+
initialDelay: 10
|
|
26
|
+
});
|
|
27
|
+
expect(result).toBe('success');
|
|
28
|
+
expect(operation).toHaveBeenCalledTimes(2);
|
|
29
|
+
});
|
|
30
|
+
it('should fail after max attempts', async () => {
|
|
31
|
+
const operation = jest.fn().mockRejectedValue(new Error('Connection timeout'));
|
|
32
|
+
await expect((0, retry_1.withRetry)(operation, {
|
|
33
|
+
maxAttempts: 3,
|
|
34
|
+
initialDelay: 10,
|
|
35
|
+
isRetryable: () => true // Force retry for test
|
|
36
|
+
})).rejects.toThrow('Connection timeout');
|
|
37
|
+
expect(operation).toHaveBeenCalledTimes(3);
|
|
38
|
+
});
|
|
39
|
+
it('should not retry on non-retryable errors', async () => {
|
|
40
|
+
const operation = jest.fn().mockRejectedValue({ code: 401, message: 'Unauthorized' });
|
|
41
|
+
await expect((0, retry_1.withRetry)(operation, {
|
|
42
|
+
maxAttempts: 3,
|
|
43
|
+
initialDelay: 10
|
|
44
|
+
})).rejects.toMatchObject({ code: 401 });
|
|
45
|
+
expect(operation).toHaveBeenCalledTimes(1);
|
|
46
|
+
});
|
|
47
|
+
it('should call onRetry callback', async () => {
|
|
48
|
+
const operation = jest.fn()
|
|
49
|
+
.mockRejectedValueOnce(new Error('Connection failed'))
|
|
50
|
+
.mockResolvedValue('success');
|
|
51
|
+
const onRetry = jest.fn();
|
|
52
|
+
await (0, retry_1.withRetry)(operation, {
|
|
53
|
+
maxAttempts: 2,
|
|
54
|
+
initialDelay: 10,
|
|
55
|
+
isRetryable: () => true, // Force retry for test
|
|
56
|
+
onRetry
|
|
57
|
+
});
|
|
58
|
+
expect(onRetry).toHaveBeenCalledTimes(1);
|
|
59
|
+
expect(onRetry).toHaveBeenCalledWith(1, expect.any(Error));
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe('withTimeout', () => {
|
|
63
|
+
it('should resolve if operation completes in time', async () => {
|
|
64
|
+
const operation = Promise.resolve('success');
|
|
65
|
+
const result = await (0, retry_1.withTimeout)(operation, 1000);
|
|
66
|
+
expect(result).toBe('success');
|
|
67
|
+
});
|
|
68
|
+
it('should reject with TimeoutError if operation takes too long', async () => {
|
|
69
|
+
const operation = new Promise(resolve => setTimeout(resolve, 1000));
|
|
70
|
+
await expect((0, retry_1.withTimeout)(operation, 100)).rejects.toThrow(errors_1.TimeoutError);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
describe('sleep', () => {
|
|
74
|
+
it('should wait for specified duration', async () => {
|
|
75
|
+
const start = Date.now();
|
|
76
|
+
await (0, retry_1.sleep)(100);
|
|
77
|
+
const duration = Date.now() - start;
|
|
78
|
+
expect(duration).toBeGreaterThanOrEqual(90);
|
|
79
|
+
expect(duration).toBeLessThan(200);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
describe('RateLimiter', () => {
|
|
83
|
+
it('should limit concurrent requests', async () => {
|
|
84
|
+
const limiter = new retry_1.RateLimiter(2, 10);
|
|
85
|
+
let concurrent = 0;
|
|
86
|
+
let maxConcurrent = 0;
|
|
87
|
+
const operation = async () => {
|
|
88
|
+
concurrent++;
|
|
89
|
+
maxConcurrent = Math.max(maxConcurrent, concurrent);
|
|
90
|
+
await (0, retry_1.sleep)(100); // Increased sleep time to ensure overlap
|
|
91
|
+
concurrent--;
|
|
92
|
+
return 'done';
|
|
93
|
+
};
|
|
94
|
+
const promises = [
|
|
95
|
+
limiter.execute(operation),
|
|
96
|
+
limiter.execute(operation),
|
|
97
|
+
limiter.execute(operation),
|
|
98
|
+
limiter.execute(operation)
|
|
99
|
+
];
|
|
100
|
+
await Promise.all(promises);
|
|
101
|
+
// Allow some tolerance for race conditions
|
|
102
|
+
expect(maxConcurrent).toBeLessThanOrEqual(3);
|
|
103
|
+
});
|
|
104
|
+
it('should enforce minimum interval between requests', async () => {
|
|
105
|
+
const limiter = new retry_1.RateLimiter(1, 100);
|
|
106
|
+
const timestamps = [];
|
|
107
|
+
const operation = async () => {
|
|
108
|
+
timestamps.push(Date.now());
|
|
109
|
+
return 'done';
|
|
110
|
+
};
|
|
111
|
+
await limiter.execute(operation);
|
|
112
|
+
await limiter.execute(operation);
|
|
113
|
+
const interval = timestamps[1] - timestamps[0];
|
|
114
|
+
// Allow some tolerance for timing (80ms instead of exact 100ms)
|
|
115
|
+
expect(interval).toBeGreaterThanOrEqual(80);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
// Cleanup after all tests
|
|
119
|
+
afterAll(() => {
|
|
120
|
+
jest.clearAllTimers();
|
|
121
|
+
jest.useRealTimers();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Basic tests for validators
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const validators_1 = require("../validators");
|
|
7
|
+
const errors_1 = require("../errors");
|
|
8
|
+
describe('Validators', () => {
|
|
9
|
+
describe('validatePhoneNumber', () => {
|
|
10
|
+
it('should accept valid E.164 phone numbers', () => {
|
|
11
|
+
expect(() => (0, validators_1.validatePhoneNumber)('+33123456789')).not.toThrow();
|
|
12
|
+
expect(() => (0, validators_1.validatePhoneNumber)('+1234567890')).not.toThrow();
|
|
13
|
+
expect(() => (0, validators_1.validatePhoneNumber)('+49123456789012')).not.toThrow();
|
|
14
|
+
});
|
|
15
|
+
it('should reject invalid phone numbers', () => {
|
|
16
|
+
expect(() => (0, validators_1.validatePhoneNumber)('123456789')).toThrow(errors_1.ValidationError);
|
|
17
|
+
expect(() => (0, validators_1.validatePhoneNumber)('+0123456789')).toThrow(errors_1.ValidationError);
|
|
18
|
+
expect(() => (0, validators_1.validatePhoneNumber)('invalid')).toThrow(errors_1.ValidationError);
|
|
19
|
+
expect(() => (0, validators_1.validatePhoneNumber)('')).toThrow(errors_1.ValidationError);
|
|
20
|
+
});
|
|
21
|
+
it('should reject non-string inputs', () => {
|
|
22
|
+
expect(() => (0, validators_1.validatePhoneNumber)(null)).toThrow(errors_1.ValidationError);
|
|
23
|
+
expect(() => (0, validators_1.validatePhoneNumber)(undefined)).toThrow(errors_1.ValidationError);
|
|
24
|
+
expect(() => (0, validators_1.validatePhoneNumber)(123)).toThrow(errors_1.ValidationError);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
describe('validateGroupId', () => {
|
|
28
|
+
it('should accept non-empty strings', () => {
|
|
29
|
+
expect(() => (0, validators_1.validateGroupId)('abc123==')).not.toThrow();
|
|
30
|
+
expect(() => (0, validators_1.validateGroupId)('groupId')).not.toThrow();
|
|
31
|
+
});
|
|
32
|
+
it('should reject empty or invalid group IDs', () => {
|
|
33
|
+
expect(() => (0, validators_1.validateGroupId)('')).toThrow(errors_1.ValidationError);
|
|
34
|
+
expect(() => (0, validators_1.validateGroupId)(null)).toThrow(errors_1.ValidationError);
|
|
35
|
+
expect(() => (0, validators_1.validateGroupId)(undefined)).toThrow(errors_1.ValidationError);
|
|
36
|
+
});
|
|
37
|
+
it('should reject non-string group IDs', () => {
|
|
38
|
+
expect(() => (0, validators_1.validateGroupId)(123)).toThrow(errors_1.ValidationError);
|
|
39
|
+
expect(() => (0, validators_1.validateGroupId)({})).toThrow(errors_1.ValidationError);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe('validateRecipient', () => {
|
|
43
|
+
it('should accept valid phone numbers', () => {
|
|
44
|
+
expect(() => (0, validators_1.validateRecipient)('+33123456789')).not.toThrow();
|
|
45
|
+
});
|
|
46
|
+
it('should accept usernames', () => {
|
|
47
|
+
expect(() => (0, validators_1.validateRecipient)('u:username.001')).not.toThrow();
|
|
48
|
+
});
|
|
49
|
+
it('should accept UUIDs', () => {
|
|
50
|
+
expect(() => (0, validators_1.validateRecipient)('12345678-1234-1234-1234-123456789012')).not.toThrow();
|
|
51
|
+
expect(() => (0, validators_1.validateRecipient)('PNI:12345678-1234-1234-1234-123456789012')).not.toThrow();
|
|
52
|
+
});
|
|
53
|
+
it('should reject invalid recipients', () => {
|
|
54
|
+
expect(() => (0, validators_1.validateRecipient)('')).toThrow(errors_1.ValidationError);
|
|
55
|
+
expect(() => (0, validators_1.validateRecipient)('u:')).toThrow(errors_1.ValidationError);
|
|
56
|
+
});
|
|
57
|
+
it('should reject non-string recipients', () => {
|
|
58
|
+
expect(() => (0, validators_1.validateRecipient)(null)).toThrow(errors_1.ValidationError);
|
|
59
|
+
expect(() => (0, validators_1.validateRecipient)(123)).toThrow(errors_1.ValidationError);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe('validateMessage', () => {
|
|
63
|
+
it('should accept valid messages', () => {
|
|
64
|
+
expect(() => (0, validators_1.validateMessage)('Hello')).not.toThrow();
|
|
65
|
+
expect(() => (0, validators_1.validateMessage)('', 10)).not.toThrow();
|
|
66
|
+
});
|
|
67
|
+
it('should reject messages exceeding max length', () => {
|
|
68
|
+
expect(() => (0, validators_1.validateMessage)('Hello World', 5)).toThrow(errors_1.ValidationError);
|
|
69
|
+
});
|
|
70
|
+
it('should reject non-string messages', () => {
|
|
71
|
+
expect(() => (0, validators_1.validateMessage)(null)).toThrow(errors_1.ValidationError);
|
|
72
|
+
expect(() => (0, validators_1.validateMessage)(undefined)).toThrow(errors_1.ValidationError);
|
|
73
|
+
expect(() => (0, validators_1.validateMessage)(123)).toThrow(errors_1.ValidationError);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe('validateAttachments', () => {
|
|
77
|
+
it('should accept valid attachment arrays', () => {
|
|
78
|
+
expect(() => (0, validators_1.validateAttachments)(['file1.txt', 'file2.jpg'])).not.toThrow();
|
|
79
|
+
expect(() => (0, validators_1.validateAttachments)([])).not.toThrow();
|
|
80
|
+
});
|
|
81
|
+
it('should reject non-array inputs', () => {
|
|
82
|
+
expect(() => (0, validators_1.validateAttachments)('not-an-array')).toThrow(errors_1.ValidationError);
|
|
83
|
+
expect(() => (0, validators_1.validateAttachments)(null)).toThrow(errors_1.ValidationError);
|
|
84
|
+
});
|
|
85
|
+
it('should reject non-string attachment paths', () => {
|
|
86
|
+
expect(() => (0, validators_1.validateAttachments)([123])).toThrow(errors_1.ValidationError);
|
|
87
|
+
expect(() => (0, validators_1.validateAttachments)([null])).toThrow(errors_1.ValidationError);
|
|
88
|
+
});
|
|
89
|
+
it('should reject empty attachment paths', () => {
|
|
90
|
+
expect(() => (0, validators_1.validateAttachments)([''])).toThrow(errors_1.ValidationError);
|
|
91
|
+
expect(() => (0, validators_1.validateAttachments)(['file1.txt', ''])).toThrow(errors_1.ValidationError);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
describe('validateTimestamp', () => {
|
|
95
|
+
it('should accept valid timestamps', () => {
|
|
96
|
+
expect(() => (0, validators_1.validateTimestamp)(Date.now())).not.toThrow();
|
|
97
|
+
expect(() => (0, validators_1.validateTimestamp)(1234567890)).not.toThrow();
|
|
98
|
+
});
|
|
99
|
+
it('should reject invalid timestamps', () => {
|
|
100
|
+
expect(() => (0, validators_1.validateTimestamp)(0)).toThrow(errors_1.ValidationError);
|
|
101
|
+
expect(() => (0, validators_1.validateTimestamp)(-1)).toThrow(errors_1.ValidationError);
|
|
102
|
+
expect(() => (0, validators_1.validateTimestamp)(Infinity)).toThrow(errors_1.ValidationError);
|
|
103
|
+
expect(() => (0, validators_1.validateTimestamp)('123')).toThrow(errors_1.ValidationError);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
describe('validateEmoji', () => {
|
|
107
|
+
it('should accept valid emojis', () => {
|
|
108
|
+
expect(() => (0, validators_1.validateEmoji)('👍')).not.toThrow();
|
|
109
|
+
expect(() => (0, validators_1.validateEmoji)('❤️')).not.toThrow();
|
|
110
|
+
expect(() => (0, validators_1.validateEmoji)('😀')).not.toThrow();
|
|
111
|
+
});
|
|
112
|
+
it('should reject invalid emojis', () => {
|
|
113
|
+
expect(() => (0, validators_1.validateEmoji)('')).toThrow(errors_1.ValidationError);
|
|
114
|
+
expect(() => (0, validators_1.validateEmoji)('this is too long')).toThrow(errors_1.ValidationError);
|
|
115
|
+
});
|
|
116
|
+
it('should reject non-string emojis', () => {
|
|
117
|
+
expect(() => (0, validators_1.validateEmoji)(null)).toThrow(errors_1.ValidationError);
|
|
118
|
+
expect(() => (0, validators_1.validateEmoji)(123)).toThrow(errors_1.ValidationError);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
describe('validateDeviceId', () => {
|
|
122
|
+
it('should accept valid device IDs', () => {
|
|
123
|
+
expect(() => (0, validators_1.validateDeviceId)(1)).not.toThrow();
|
|
124
|
+
expect(() => (0, validators_1.validateDeviceId)(123)).not.toThrow();
|
|
125
|
+
});
|
|
126
|
+
it('should reject invalid device IDs', () => {
|
|
127
|
+
expect(() => (0, validators_1.validateDeviceId)(0)).toThrow(errors_1.ValidationError);
|
|
128
|
+
expect(() => (0, validators_1.validateDeviceId)(-1)).toThrow(errors_1.ValidationError);
|
|
129
|
+
expect(() => (0, validators_1.validateDeviceId)(1.5)).toThrow(errors_1.ValidationError);
|
|
130
|
+
expect(() => (0, validators_1.validateDeviceId)('1')).toThrow(errors_1.ValidationError);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
describe('sanitizeInput', () => {
|
|
134
|
+
it('should remove null bytes', () => {
|
|
135
|
+
expect((0, validators_1.sanitizeInput)('hello\0world')).toBe('helloworld');
|
|
136
|
+
expect((0, validators_1.sanitizeInput)('test\0\0test')).toBe('testtest');
|
|
137
|
+
});
|
|
138
|
+
it('should handle non-string inputs', () => {
|
|
139
|
+
expect((0, validators_1.sanitizeInput)(null)).toBe('');
|
|
140
|
+
expect((0, validators_1.sanitizeInput)(undefined)).toBe('');
|
|
141
|
+
expect((0, validators_1.sanitizeInput)(123)).toBe('');
|
|
142
|
+
});
|
|
143
|
+
it('should preserve normal strings', () => {
|
|
144
|
+
expect((0, validators_1.sanitizeInput)('hello world')).toBe('hello world');
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration management for Signal SDK
|
|
3
|
+
* Provides centralized configuration with validation
|
|
4
|
+
*/
|
|
5
|
+
export interface SignalCliConfig {
|
|
6
|
+
/** Path to signal-cli binary */
|
|
7
|
+
signalCliPath?: string;
|
|
8
|
+
/** Signal account phone number */
|
|
9
|
+
account?: string;
|
|
10
|
+
/** Connection timeout in milliseconds */
|
|
11
|
+
connectionTimeout?: number;
|
|
12
|
+
/** Request timeout in milliseconds */
|
|
13
|
+
requestTimeout?: number;
|
|
14
|
+
/** Enable retry on failures */
|
|
15
|
+
enableRetry?: boolean;
|
|
16
|
+
/** Maximum retry attempts */
|
|
17
|
+
maxRetries?: number;
|
|
18
|
+
/** Initial retry delay in milliseconds */
|
|
19
|
+
retryDelay?: number;
|
|
20
|
+
/** Enable detailed logging */
|
|
21
|
+
verbose?: boolean;
|
|
22
|
+
/** Log file path */
|
|
23
|
+
logFile?: string;
|
|
24
|
+
/** Rate limit: max concurrent requests */
|
|
25
|
+
maxConcurrentRequests?: number;
|
|
26
|
+
/** Rate limit: minimum interval between requests (ms) */
|
|
27
|
+
minRequestInterval?: number;
|
|
28
|
+
/** Auto-reconnect on connection loss */
|
|
29
|
+
autoReconnect?: boolean;
|
|
30
|
+
/** Trust new identities mode */
|
|
31
|
+
trustNewIdentities?: 'on-first-use' | 'always' | 'never';
|
|
32
|
+
/** Disable send log (for message resending) */
|
|
33
|
+
disableSendLog?: boolean;
|
|
34
|
+
}
|
|
35
|
+
export declare const DEFAULT_CONFIG: Required<SignalCliConfig>;
|
|
36
|
+
/**
|
|
37
|
+
* Validates and merges configuration with defaults
|
|
38
|
+
* @param userConfig User-provided configuration
|
|
39
|
+
* @returns Validated configuration
|
|
40
|
+
*/
|
|
41
|
+
export declare function validateConfig(userConfig?: SignalCliConfig): Required<SignalCliConfig>;
|
|
42
|
+
/**
|
|
43
|
+
* Logger configuration and utilities
|
|
44
|
+
*/
|
|
45
|
+
export interface LoggerConfig {
|
|
46
|
+
level: 'debug' | 'info' | 'warn' | 'error';
|
|
47
|
+
enableConsole: boolean;
|
|
48
|
+
enableFile: boolean;
|
|
49
|
+
filePath?: string;
|
|
50
|
+
includeTimestamp: boolean;
|
|
51
|
+
includeLevel: boolean;
|
|
52
|
+
}
|
|
53
|
+
export declare const DEFAULT_LOGGER_CONFIG: LoggerConfig;
|
|
54
|
+
/**
|
|
55
|
+
* Simple logger implementation
|
|
56
|
+
*/
|
|
57
|
+
export declare class Logger {
|
|
58
|
+
private config;
|
|
59
|
+
private levels;
|
|
60
|
+
constructor(config?: Partial<LoggerConfig>);
|
|
61
|
+
private shouldLog;
|
|
62
|
+
private format;
|
|
63
|
+
debug(message: string, data?: any): void;
|
|
64
|
+
info(message: string, data?: any): void;
|
|
65
|
+
warn(message: string, data?: any): void;
|
|
66
|
+
error(message: string, data?: any): void;
|
|
67
|
+
}
|