@unito/integration-sdk 2.3.13 → 2.3.15
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/dist/src/index.cjs +126 -1
- package/dist/src/resources/provider.d.ts +12 -1
- package/dist/src/resources/provider.js +126 -1
- package/dist/test/resources/provider.test.js +365 -31
- package/package.json +2 -1
- package/src/resources/provider.ts +152 -3
- package/test/resources/provider.test.ts +431 -31
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import { describe, it } from 'node:test';
|
|
3
|
+
import { Readable } from 'stream';
|
|
4
|
+
import nock from 'nock';
|
|
5
|
+
import https from 'https';
|
|
3
6
|
import { Provider } from '../../src/resources/provider.js';
|
|
4
7
|
import * as HttpErrors from '../../src/httpErrors.js';
|
|
5
8
|
import Logger from '../../src/resources/logger.js';
|
|
@@ -21,6 +24,21 @@ describe('Provider', () => {
|
|
|
21
24
|
},
|
|
22
25
|
});
|
|
23
26
|
const logger = new Logger();
|
|
27
|
+
// Helper to spy on https.request and capture options
|
|
28
|
+
const spyOnHttpsRequest = () => {
|
|
29
|
+
let capturedOptions;
|
|
30
|
+
const originalRequest = https.request;
|
|
31
|
+
https.request = function (options, callback) {
|
|
32
|
+
capturedOptions = options;
|
|
33
|
+
return originalRequest.call(https, options, callback);
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
getCapturedOptions: () => capturedOptions,
|
|
37
|
+
restore: () => {
|
|
38
|
+
https.request = originalRequest;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
};
|
|
24
42
|
it('get', async (context) => {
|
|
25
43
|
const response = new Response('{"data": "value"}', {
|
|
26
44
|
status: 200,
|
|
@@ -29,7 +47,7 @@ describe('Provider', () => {
|
|
|
29
47
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
30
48
|
const actualResponse = await provider.get('/endpoint', {
|
|
31
49
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
32
|
-
logger
|
|
50
|
+
logger,
|
|
33
51
|
signal: new AbortController().signal,
|
|
34
52
|
additionnalheaders: { 'X-Additional-Header': 'value1' },
|
|
35
53
|
});
|
|
@@ -58,7 +76,7 @@ describe('Provider', () => {
|
|
|
58
76
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
59
77
|
const actualResponse = await provider.get('/endpoint', {
|
|
60
78
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
61
|
-
logger
|
|
79
|
+
logger,
|
|
62
80
|
signal: new AbortController().signal,
|
|
63
81
|
additionnalheaders: { 'X-Additional-Header': 'value1', Accept: 'text/html; charset=UTF-8' },
|
|
64
82
|
});
|
|
@@ -87,7 +105,7 @@ describe('Provider', () => {
|
|
|
87
105
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
88
106
|
const actualResponse = await provider.get('/endpoint', {
|
|
89
107
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
90
|
-
logger
|
|
108
|
+
logger,
|
|
91
109
|
signal: new AbortController().signal,
|
|
92
110
|
additionnalheaders: { 'X-Additional-Header': 'value1', Accept: 'application/schema+json; charset=UTF-8' },
|
|
93
111
|
});
|
|
@@ -116,7 +134,7 @@ describe('Provider', () => {
|
|
|
116
134
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
117
135
|
const actualResponse = await provider.get('/endpoint', {
|
|
118
136
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
119
|
-
logger
|
|
137
|
+
logger,
|
|
120
138
|
signal: new AbortController().signal,
|
|
121
139
|
additionnalheaders: { 'X-Additional-Header': 'value1', Accept: 'application/swagger+json; charset=UTF-8' },
|
|
122
140
|
});
|
|
@@ -145,7 +163,7 @@ describe('Provider', () => {
|
|
|
145
163
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
146
164
|
const actualResponse = await provider.get('/endpoint', {
|
|
147
165
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
148
|
-
logger
|
|
166
|
+
logger,
|
|
149
167
|
signal: new AbortController().signal,
|
|
150
168
|
additionnalheaders: { 'X-Additional-Header': 'value1' },
|
|
151
169
|
});
|
|
@@ -174,7 +192,7 @@ describe('Provider', () => {
|
|
|
174
192
|
context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
175
193
|
const providerResponse = await provider.streamingGet('/endpoint/123', {
|
|
176
194
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
177
|
-
logger
|
|
195
|
+
logger,
|
|
178
196
|
signal: new AbortController().signal,
|
|
179
197
|
additionnalheaders: {
|
|
180
198
|
Accept: 'application/json',
|
|
@@ -193,7 +211,7 @@ describe('Provider', () => {
|
|
|
193
211
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
194
212
|
const actualResponse = await provider.get('https://my-cdn.my-domain.com/file.png', {
|
|
195
213
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
196
|
-
logger
|
|
214
|
+
logger,
|
|
197
215
|
signal: new AbortController().signal,
|
|
198
216
|
});
|
|
199
217
|
assert.equal(fetchMock.mock.calls.length, 1);
|
|
@@ -220,7 +238,7 @@ describe('Provider', () => {
|
|
|
220
238
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
221
239
|
const actualResponse = await provider.get('', {
|
|
222
240
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
223
|
-
logger
|
|
241
|
+
logger,
|
|
224
242
|
signal: new AbortController().signal,
|
|
225
243
|
});
|
|
226
244
|
assert.equal(fetchMock.mock.calls.length, 1);
|
|
@@ -249,7 +267,7 @@ describe('Provider', () => {
|
|
|
249
267
|
data: 'createdItemInfo',
|
|
250
268
|
}, {
|
|
251
269
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
252
|
-
logger
|
|
270
|
+
logger,
|
|
253
271
|
signal: new AbortController().signal,
|
|
254
272
|
additionnalheaders: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-Additional-Header': 'value1' },
|
|
255
273
|
});
|
|
@@ -282,7 +300,7 @@ describe('Provider', () => {
|
|
|
282
300
|
{ data: '3', data2: '4' },
|
|
283
301
|
], {
|
|
284
302
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
285
|
-
logger
|
|
303
|
+
logger,
|
|
286
304
|
signal: new AbortController().signal,
|
|
287
305
|
additionnalheaders: { 'Content-Type': 'application/json-patch+json', 'X-Additional-Header': 'value1' },
|
|
288
306
|
});
|
|
@@ -315,7 +333,7 @@ describe('Provider', () => {
|
|
|
315
333
|
data: 'updatedItemInfo',
|
|
316
334
|
}, {
|
|
317
335
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
318
|
-
logger
|
|
336
|
+
logger,
|
|
319
337
|
signal: new AbortController().signal,
|
|
320
338
|
additionnalheaders: { 'X-Additional-Header': 'value1', 'Content-Type': 'application/json' },
|
|
321
339
|
});
|
|
@@ -347,7 +365,7 @@ describe('Provider', () => {
|
|
|
347
365
|
// What matters is that the body of put is a buffer
|
|
348
366
|
const actualResponse = await provider.putBuffer('endpoint/123', buffer, {
|
|
349
367
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
350
|
-
logger
|
|
368
|
+
logger,
|
|
351
369
|
signal: new AbortController().signal,
|
|
352
370
|
additionnalheaders: { 'X-Additional-Header': 'value1', 'Content-Type': 'application/octet-stream' },
|
|
353
371
|
});
|
|
@@ -379,7 +397,7 @@ describe('Provider', () => {
|
|
|
379
397
|
data: 'updatedItemInfo',
|
|
380
398
|
}, {
|
|
381
399
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
382
|
-
logger
|
|
400
|
+
logger,
|
|
383
401
|
signal: new AbortController().signal,
|
|
384
402
|
queryParams: { param1: 'value1', param2: 'value2' },
|
|
385
403
|
additionnalheaders: { 'X-Additional-Header': 'value1' },
|
|
@@ -410,7 +428,7 @@ describe('Provider', () => {
|
|
|
410
428
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
411
429
|
const actualResponse = await provider.delete('/endpoint/123', {
|
|
412
430
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
413
|
-
logger
|
|
431
|
+
logger,
|
|
414
432
|
signal: new AbortController().signal,
|
|
415
433
|
additionnalheaders: { 'X-Additional-Header': 'value1' },
|
|
416
434
|
});
|
|
@@ -440,7 +458,7 @@ describe('Provider', () => {
|
|
|
440
458
|
const requestBody = { webhookIds: [1, 2, 3] };
|
|
441
459
|
const actualResponse = await provider.delete('/webhook', {
|
|
442
460
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
443
|
-
logger
|
|
461
|
+
logger,
|
|
444
462
|
signal: new AbortController().signal,
|
|
445
463
|
additionnalheaders: { 'X-Additional-Header': 'value1' },
|
|
446
464
|
}, requestBody);
|
|
@@ -483,7 +501,7 @@ describe('Provider', () => {
|
|
|
483
501
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
484
502
|
const options = {
|
|
485
503
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
486
|
-
logger
|
|
504
|
+
logger,
|
|
487
505
|
signal: new AbortController().signal,
|
|
488
506
|
additionnalheaders: { 'X-Additional-Header': 'value1' },
|
|
489
507
|
};
|
|
@@ -529,7 +547,7 @@ describe('Provider', () => {
|
|
|
529
547
|
context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
530
548
|
const options = {
|
|
531
549
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
532
|
-
logger
|
|
550
|
+
logger,
|
|
533
551
|
signal: new AbortController().signal,
|
|
534
552
|
additionnalheaders: { 'X-Additional-Header': 'value1' },
|
|
535
553
|
};
|
|
@@ -570,7 +588,7 @@ describe('Provider', () => {
|
|
|
570
588
|
context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
571
589
|
const options = {
|
|
572
590
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
573
|
-
logger
|
|
591
|
+
logger,
|
|
574
592
|
signal: new AbortController().signal,
|
|
575
593
|
additionnalheaders: { 'X-Additional-Header': 'value1' },
|
|
576
594
|
};
|
|
@@ -606,7 +624,7 @@ describe('Provider', () => {
|
|
|
606
624
|
context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
607
625
|
const options = {
|
|
608
626
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
609
|
-
logger
|
|
627
|
+
logger,
|
|
610
628
|
signal: new AbortController().signal,
|
|
611
629
|
additionnalheaders: { 'X-Additional-Header': 'value1' },
|
|
612
630
|
};
|
|
@@ -628,7 +646,7 @@ describe('Provider', () => {
|
|
|
628
646
|
context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
629
647
|
const providerResponse = await provider.get('/endpoint/123', {
|
|
630
648
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
631
|
-
logger
|
|
649
|
+
logger,
|
|
632
650
|
signal: new AbortController().signal,
|
|
633
651
|
});
|
|
634
652
|
assert.ok(providerResponse);
|
|
@@ -642,7 +660,7 @@ describe('Provider', () => {
|
|
|
642
660
|
context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
643
661
|
const providerResponse = await provider.get('/endpoint/123', {
|
|
644
662
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
645
|
-
logger
|
|
663
|
+
logger,
|
|
646
664
|
signal: new AbortController().signal,
|
|
647
665
|
});
|
|
648
666
|
assert.ok(providerResponse);
|
|
@@ -656,7 +674,7 @@ describe('Provider', () => {
|
|
|
656
674
|
context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
657
675
|
const providerResponse = await provider.streamingGet('/endpoint/123', {
|
|
658
676
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
659
|
-
logger
|
|
677
|
+
logger,
|
|
660
678
|
signal: new AbortController().signal,
|
|
661
679
|
});
|
|
662
680
|
assert.ok(providerResponse);
|
|
@@ -670,7 +688,7 @@ describe('Provider', () => {
|
|
|
670
688
|
context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
671
689
|
const providerResponse = await provider.post('/endpoint/123', {}, {
|
|
672
690
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
673
|
-
logger
|
|
691
|
+
logger,
|
|
674
692
|
signal: new AbortController().signal,
|
|
675
693
|
});
|
|
676
694
|
assert.ok(providerResponse);
|
|
@@ -688,7 +706,7 @@ describe('Provider', () => {
|
|
|
688
706
|
try {
|
|
689
707
|
await provider.get('/endpoint/123', {
|
|
690
708
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
691
|
-
logger
|
|
709
|
+
logger,
|
|
692
710
|
signal: new AbortController().signal,
|
|
693
711
|
});
|
|
694
712
|
}
|
|
@@ -708,7 +726,7 @@ describe('Provider', () => {
|
|
|
708
726
|
try {
|
|
709
727
|
await provider.get('/endpoint/123', {
|
|
710
728
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
711
|
-
logger
|
|
729
|
+
logger,
|
|
712
730
|
signal: new AbortController().signal,
|
|
713
731
|
});
|
|
714
732
|
}
|
|
@@ -728,7 +746,7 @@ describe('Provider', () => {
|
|
|
728
746
|
await provider.get('/endpoint/123', {
|
|
729
747
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
730
748
|
signal: new AbortController().signal,
|
|
731
|
-
logger
|
|
749
|
+
logger,
|
|
732
750
|
});
|
|
733
751
|
}
|
|
734
752
|
catch (e) {
|
|
@@ -748,7 +766,7 @@ describe('Provider', () => {
|
|
|
748
766
|
await provider.get('/endpoint/123', {
|
|
749
767
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
750
768
|
signal: new AbortController().signal,
|
|
751
|
-
logger
|
|
769
|
+
logger,
|
|
752
770
|
});
|
|
753
771
|
}
|
|
754
772
|
catch (e) {
|
|
@@ -768,7 +786,7 @@ describe('Provider', () => {
|
|
|
768
786
|
await provider.get('/endpoint/123', {
|
|
769
787
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
770
788
|
signal: new AbortController().signal,
|
|
771
|
-
logger
|
|
789
|
+
logger,
|
|
772
790
|
});
|
|
773
791
|
}
|
|
774
792
|
catch (e) {
|
|
@@ -786,7 +804,7 @@ describe('Provider', () => {
|
|
|
786
804
|
await provider.get('/endpoint/123', {
|
|
787
805
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
788
806
|
signal: new AbortController().signal,
|
|
789
|
-
logger
|
|
807
|
+
logger,
|
|
790
808
|
});
|
|
791
809
|
}
|
|
792
810
|
catch (e) {
|
|
@@ -810,7 +828,7 @@ describe('Provider', () => {
|
|
|
810
828
|
await provider.get('/endpoint/123', {
|
|
811
829
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
812
830
|
signal: new AbortController().signal,
|
|
813
|
-
logger
|
|
831
|
+
logger,
|
|
814
832
|
});
|
|
815
833
|
}
|
|
816
834
|
catch (e) {
|
|
@@ -826,9 +844,325 @@ describe('Provider', () => {
|
|
|
826
844
|
await provider.get('/endpoint/123', {
|
|
827
845
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
828
846
|
signal: new AbortController().signal,
|
|
829
|
-
logger
|
|
847
|
+
logger,
|
|
830
848
|
});
|
|
831
849
|
assert.equal(loggerStub.mock.callCount(), 1);
|
|
832
850
|
assert.match(String(loggerStub.mock.calls[0]?.arguments[0]), /Connector API Request GET www.myApi.com\/endpoint\/123 201 - \d+ ms/);
|
|
833
851
|
});
|
|
852
|
+
// Stream upload will not load the data in memory and sending a binary in the body
|
|
853
|
+
it('postStream streams data without buffering', async () => {
|
|
854
|
+
const streamProvider = new Provider({
|
|
855
|
+
prepareRequest: requestOptions => ({
|
|
856
|
+
url: `https://www.${requestOptions.credentials.domain ?? 'myApi.com'}`,
|
|
857
|
+
headers: {
|
|
858
|
+
'X-Custom-Provider-Header': 'value',
|
|
859
|
+
'X-Provider-Credential-Header': requestOptions.credentials.apiKey,
|
|
860
|
+
},
|
|
861
|
+
}),
|
|
862
|
+
});
|
|
863
|
+
const testData = 'test binary data for streaming';
|
|
864
|
+
const stream = Readable.from([testData]);
|
|
865
|
+
const scope = nock('https://www.myApi.com')
|
|
866
|
+
.post('/upload', testData)
|
|
867
|
+
.matchHeader('content-type', 'application/octet-stream')
|
|
868
|
+
.matchHeader('accept', 'application/json')
|
|
869
|
+
.matchHeader('x-custom-provider-header', 'value')
|
|
870
|
+
.matchHeader('x-provider-credential-header', 'apikey#1111')
|
|
871
|
+
.reply(201, { success: true, id: '12345' });
|
|
872
|
+
const response = await streamProvider.postStream('/upload', stream, {
|
|
873
|
+
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
874
|
+
logger,
|
|
875
|
+
});
|
|
876
|
+
assert.ok(scope.isDone(), 'HTTPS request should have been made');
|
|
877
|
+
assert.equal(response.status, 201);
|
|
878
|
+
assert.deepEqual(response.body, { success: true, id: '12345' });
|
|
879
|
+
});
|
|
880
|
+
it('postStream with query params', async () => {
|
|
881
|
+
const streamProvider = new Provider({
|
|
882
|
+
prepareRequest: requestOptions => ({
|
|
883
|
+
url: `https://www.myApi.com`,
|
|
884
|
+
headers: {
|
|
885
|
+
'X-Custom-Provider-Header': 'value',
|
|
886
|
+
'X-Provider-Credential-Header': requestOptions.credentials.apiKey,
|
|
887
|
+
},
|
|
888
|
+
}),
|
|
889
|
+
});
|
|
890
|
+
const testData = 'data with params';
|
|
891
|
+
const stream = Readable.from([testData]);
|
|
892
|
+
const scope = nock('https://www.myApi.com')
|
|
893
|
+
.post('/upload?key=value&format=json', testData)
|
|
894
|
+
.reply(200, { uploaded: true });
|
|
895
|
+
const response = await streamProvider.postStream('/upload', stream, {
|
|
896
|
+
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
897
|
+
logger,
|
|
898
|
+
queryParams: { key: 'value', format: 'json' },
|
|
899
|
+
});
|
|
900
|
+
assert.ok(scope.isDone());
|
|
901
|
+
assert.equal(response.status, 200);
|
|
902
|
+
assert.deepEqual(response.body, { uploaded: true });
|
|
903
|
+
});
|
|
904
|
+
it('postStream handles error responses', async () => {
|
|
905
|
+
const streamProvider = new Provider({
|
|
906
|
+
prepareRequest: requestOptions => ({
|
|
907
|
+
url: `https://www.myApi.com`,
|
|
908
|
+
headers: {
|
|
909
|
+
'X-Custom-Provider-Header': 'value',
|
|
910
|
+
'X-Provider-Credential-Header': requestOptions.credentials.apiKey,
|
|
911
|
+
},
|
|
912
|
+
}),
|
|
913
|
+
});
|
|
914
|
+
const stream = Readable.from(['error test data']);
|
|
915
|
+
nock('https://www.myApi.com').post('/upload').reply(400, 'Bad request error');
|
|
916
|
+
let error;
|
|
917
|
+
try {
|
|
918
|
+
await streamProvider.postStream('/upload', stream, {
|
|
919
|
+
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
920
|
+
logger,
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
catch (e) {
|
|
924
|
+
error = e;
|
|
925
|
+
}
|
|
926
|
+
assert.ok(error instanceof HttpErrors.BadRequestError);
|
|
927
|
+
assert.equal(error.message, 'Bad request error');
|
|
928
|
+
});
|
|
929
|
+
it('postStream with rate limiter', async () => {
|
|
930
|
+
let rateLimiterCalled = false;
|
|
931
|
+
let rateLimiterCredentials;
|
|
932
|
+
const mockRateLimiter = async (options, request) => {
|
|
933
|
+
rateLimiterCalled = true;
|
|
934
|
+
rateLimiterCredentials = options.credentials;
|
|
935
|
+
return request();
|
|
936
|
+
};
|
|
937
|
+
const rateLimitedProvider = new Provider({
|
|
938
|
+
prepareRequest: requestOptions => ({
|
|
939
|
+
url: `https://www.myApi.com`,
|
|
940
|
+
headers: {
|
|
941
|
+
'X-Custom-Provider-Header': 'value',
|
|
942
|
+
'X-Provider-Credential-Header': requestOptions.credentials.apiKey,
|
|
943
|
+
},
|
|
944
|
+
}),
|
|
945
|
+
rateLimiter: mockRateLimiter,
|
|
946
|
+
});
|
|
947
|
+
const testData = 'rate limited data';
|
|
948
|
+
const stream = Readable.from([testData]);
|
|
949
|
+
nock('https://www.myApi.com').post('/upload', testData).reply(201, { success: true });
|
|
950
|
+
await rateLimitedProvider.postStream('/upload', stream, {
|
|
951
|
+
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
952
|
+
logger,
|
|
953
|
+
});
|
|
954
|
+
assert.ok(rateLimiterCalled, 'Rate limiter should have been called');
|
|
955
|
+
assert.deepEqual(rateLimiterCredentials, {
|
|
956
|
+
apiKey: 'apikey#1111',
|
|
957
|
+
unitoCredentialId: '123',
|
|
958
|
+
});
|
|
959
|
+
});
|
|
960
|
+
it('postStream sets timeout to 0 (no timeout)', async () => {
|
|
961
|
+
const streamProvider = new Provider({
|
|
962
|
+
prepareRequest: requestOptions => ({
|
|
963
|
+
url: `https://www.myApi.com`,
|
|
964
|
+
headers: {
|
|
965
|
+
'X-Custom-Provider-Header': 'value',
|
|
966
|
+
'X-Provider-Credential-Header': requestOptions.credentials.apiKey,
|
|
967
|
+
},
|
|
968
|
+
}),
|
|
969
|
+
});
|
|
970
|
+
const testData = 'timeout test data';
|
|
971
|
+
const stream = Readable.from([testData]);
|
|
972
|
+
const spy = spyOnHttpsRequest();
|
|
973
|
+
const scope = nock('https://www.myApi.com').post('/upload', testData).reply(201, { success: true });
|
|
974
|
+
try {
|
|
975
|
+
await streamProvider.postStream('/upload', stream, {
|
|
976
|
+
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
977
|
+
logger,
|
|
978
|
+
});
|
|
979
|
+
assert.ok(scope.isDone());
|
|
980
|
+
assert.equal(spy.getCapturedOptions().timeout, 0, 'Timeout should be set to 0 (no timeout)');
|
|
981
|
+
}
|
|
982
|
+
finally {
|
|
983
|
+
spy.restore();
|
|
984
|
+
}
|
|
985
|
+
});
|
|
986
|
+
it('postStream handles AbortSignal for request cancellation', async () => {
|
|
987
|
+
const streamProvider = new Provider({
|
|
988
|
+
prepareRequest: requestOptions => ({
|
|
989
|
+
url: `https://www.myApi.com`,
|
|
990
|
+
headers: {
|
|
991
|
+
'X-Custom-Provider-Header': 'value',
|
|
992
|
+
'X-Provider-Credential-Header': requestOptions.credentials.apiKey,
|
|
993
|
+
},
|
|
994
|
+
}),
|
|
995
|
+
});
|
|
996
|
+
const stream = Readable.from(['abort signal test']);
|
|
997
|
+
const abortController = new AbortController();
|
|
998
|
+
// Simulate aborting the request immediately
|
|
999
|
+
abortController.abort();
|
|
1000
|
+
let error;
|
|
1001
|
+
try {
|
|
1002
|
+
await streamProvider.postStream('/upload', stream, {
|
|
1003
|
+
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
1004
|
+
logger,
|
|
1005
|
+
signal: abortController.signal,
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
catch (e) {
|
|
1009
|
+
error = e;
|
|
1010
|
+
}
|
|
1011
|
+
assert.ok(error instanceof HttpErrors.TimeoutError);
|
|
1012
|
+
assert.equal(error.message, 'Timeout');
|
|
1013
|
+
});
|
|
1014
|
+
it('postStream handles AbortSignal timeout during request', async () => {
|
|
1015
|
+
const streamProvider = new Provider({
|
|
1016
|
+
prepareRequest: requestOptions => ({
|
|
1017
|
+
url: `https://www.myApi.com`,
|
|
1018
|
+
headers: {
|
|
1019
|
+
'X-Custom-Provider-Header': 'value',
|
|
1020
|
+
'X-Provider-Credential-Header': requestOptions.credentials.apiKey,
|
|
1021
|
+
},
|
|
1022
|
+
}),
|
|
1023
|
+
});
|
|
1024
|
+
const stream = Readable.from(['timeout during request test']);
|
|
1025
|
+
const abortController = new AbortController();
|
|
1026
|
+
// Delay response to simulate a slow server
|
|
1027
|
+
nock('https://www.myApi.com').post('/upload').delayConnection(100).reply(201, { success: true });
|
|
1028
|
+
// Abort after 50ms
|
|
1029
|
+
setTimeout(() => abortController.abort(), 50);
|
|
1030
|
+
let error;
|
|
1031
|
+
try {
|
|
1032
|
+
await streamProvider.postStream('/upload', stream, {
|
|
1033
|
+
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
1034
|
+
logger,
|
|
1035
|
+
signal: abortController.signal,
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
catch (e) {
|
|
1039
|
+
error = e;
|
|
1040
|
+
}
|
|
1041
|
+
assert.ok(error instanceof HttpErrors.TimeoutError);
|
|
1042
|
+
assert.equal(error.message, 'Timeout');
|
|
1043
|
+
});
|
|
1044
|
+
it('postStream cleans up AbortSignal listener on success', async () => {
|
|
1045
|
+
const streamProvider = new Provider({
|
|
1046
|
+
prepareRequest: requestOptions => ({
|
|
1047
|
+
url: `https://www.myApi.com`,
|
|
1048
|
+
headers: {
|
|
1049
|
+
'X-Custom-Provider-Header': 'value',
|
|
1050
|
+
'X-Provider-Credential-Header': requestOptions.credentials.apiKey,
|
|
1051
|
+
},
|
|
1052
|
+
}),
|
|
1053
|
+
});
|
|
1054
|
+
const testData = 'cleanup test';
|
|
1055
|
+
const stream = Readable.from([testData]);
|
|
1056
|
+
const abortController = new AbortController();
|
|
1057
|
+
const scope = nock('https://www.myApi.com').post('/upload', testData).reply(201, { success: true });
|
|
1058
|
+
const response = await streamProvider.postStream('/upload', stream, {
|
|
1059
|
+
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
1060
|
+
logger,
|
|
1061
|
+
signal: abortController.signal,
|
|
1062
|
+
});
|
|
1063
|
+
assert.ok(scope.isDone());
|
|
1064
|
+
assert.equal(response.status, 201);
|
|
1065
|
+
// Verify the listener was removed by checking that aborting after completion
|
|
1066
|
+
// doesn't cause any side effects (if listener wasn't removed, this could cause issues)
|
|
1067
|
+
assert.doesNotThrow(() => {
|
|
1068
|
+
abortController.abort();
|
|
1069
|
+
}, 'Aborting after completion should not throw or cause issues');
|
|
1070
|
+
// Verify the signal is indeed aborted
|
|
1071
|
+
assert.ok(abortController.signal.aborted, 'Signal should be aborted');
|
|
1072
|
+
});
|
|
1073
|
+
it('postForm handles AbortSignal for request cancellation', async () => {
|
|
1074
|
+
const FormData = (await import('form-data')).default;
|
|
1075
|
+
const formProvider = new Provider({
|
|
1076
|
+
prepareRequest: requestOptions => ({
|
|
1077
|
+
url: `https://www.myApi.com`,
|
|
1078
|
+
headers: {
|
|
1079
|
+
'X-Custom-Provider-Header': 'value',
|
|
1080
|
+
'X-Provider-Credential-Header': requestOptions.credentials.apiKey,
|
|
1081
|
+
},
|
|
1082
|
+
}),
|
|
1083
|
+
});
|
|
1084
|
+
const form = new FormData();
|
|
1085
|
+
form.append('field1', 'value1');
|
|
1086
|
+
form.append('field2', 'value2');
|
|
1087
|
+
const abortController = new AbortController();
|
|
1088
|
+
// Simulate aborting the request immediately
|
|
1089
|
+
abortController.abort();
|
|
1090
|
+
let error;
|
|
1091
|
+
try {
|
|
1092
|
+
await formProvider.postForm('/upload-form', form, {
|
|
1093
|
+
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
1094
|
+
logger,
|
|
1095
|
+
signal: abortController.signal,
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
catch (e) {
|
|
1099
|
+
error = e;
|
|
1100
|
+
}
|
|
1101
|
+
assert.ok(error instanceof HttpErrors.TimeoutError);
|
|
1102
|
+
assert.equal(error.message, 'Timeout');
|
|
1103
|
+
});
|
|
1104
|
+
it('postForm handles AbortSignal timeout during request', async () => {
|
|
1105
|
+
const FormData = (await import('form-data')).default;
|
|
1106
|
+
const formProvider = new Provider({
|
|
1107
|
+
prepareRequest: requestOptions => ({
|
|
1108
|
+
url: `https://www.myApi.com`,
|
|
1109
|
+
headers: {
|
|
1110
|
+
'X-Custom-Provider-Header': 'value',
|
|
1111
|
+
'X-Provider-Credential-Header': requestOptions.credentials.apiKey,
|
|
1112
|
+
},
|
|
1113
|
+
}),
|
|
1114
|
+
});
|
|
1115
|
+
const form = new FormData();
|
|
1116
|
+
form.append('field1', 'value1');
|
|
1117
|
+
const abortController = new AbortController();
|
|
1118
|
+
// Mock a delayed response
|
|
1119
|
+
nock('https://www.myApi.com').post('/upload-form').delayConnection(100).reply(201, { success: true, id: '12345' });
|
|
1120
|
+
// Abort after 50ms
|
|
1121
|
+
setTimeout(() => abortController.abort(), 50);
|
|
1122
|
+
let error;
|
|
1123
|
+
try {
|
|
1124
|
+
await formProvider.postForm('/upload-form', form, {
|
|
1125
|
+
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
1126
|
+
logger,
|
|
1127
|
+
signal: abortController.signal,
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
catch (e) {
|
|
1131
|
+
error = e;
|
|
1132
|
+
}
|
|
1133
|
+
assert.ok(error instanceof HttpErrors.TimeoutError);
|
|
1134
|
+
assert.equal(error.message, 'Timeout');
|
|
1135
|
+
});
|
|
1136
|
+
it('postForm successfully completes with AbortSignal provided', async () => {
|
|
1137
|
+
const FormData = (await import('form-data')).default;
|
|
1138
|
+
const formProvider = new Provider({
|
|
1139
|
+
prepareRequest: requestOptions => ({
|
|
1140
|
+
url: `https://www.myApi.com`,
|
|
1141
|
+
headers: {
|
|
1142
|
+
'X-Custom-Provider-Header': 'value',
|
|
1143
|
+
'X-Provider-Credential-Header': requestOptions.credentials.apiKey,
|
|
1144
|
+
},
|
|
1145
|
+
}),
|
|
1146
|
+
});
|
|
1147
|
+
const form = new FormData();
|
|
1148
|
+
form.append('field1', 'value1');
|
|
1149
|
+
form.append('field2', 'value2');
|
|
1150
|
+
const abortController = new AbortController();
|
|
1151
|
+
const scope = nock('https://www.myApi.com').post('/upload-form').reply(201, { success: true, id: '12345' });
|
|
1152
|
+
const response = await formProvider.postForm('/upload-form', form, {
|
|
1153
|
+
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
1154
|
+
logger,
|
|
1155
|
+
signal: abortController.signal,
|
|
1156
|
+
});
|
|
1157
|
+
assert.ok(scope.isDone());
|
|
1158
|
+
assert.equal(response.status, 201);
|
|
1159
|
+
assert.deepEqual(response.body, { success: true, id: '12345' });
|
|
1160
|
+
// Verify the listener was removed by checking that aborting after completion
|
|
1161
|
+
// doesn't cause any side effects
|
|
1162
|
+
assert.doesNotThrow(() => {
|
|
1163
|
+
abortController.abort();
|
|
1164
|
+
}, 'Aborting after completion should not throw or cause issues');
|
|
1165
|
+
// Verify the signal is indeed aborted
|
|
1166
|
+
assert.ok(abortController.signal.aborted, 'Signal should be aborted');
|
|
1167
|
+
});
|
|
834
1168
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unito/integration-sdk",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.15",
|
|
4
4
|
"description": "Integration SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "dist/src/index.d.ts",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"@typescript-eslint/eslint-plugin": "7.x",
|
|
45
45
|
"@typescript-eslint/parser": "7.x",
|
|
46
46
|
"eslint": "8.x",
|
|
47
|
+
"nock": "14.x",
|
|
47
48
|
"nodemon": "3.x",
|
|
48
49
|
"prettier": "3.x",
|
|
49
50
|
"rollup": "4.x",
|