@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
|
|
|
4
7
|
import { Provider } from '../../src/resources/provider.js';
|
|
5
8
|
import * as HttpErrors from '../../src/httpErrors.js';
|
|
@@ -26,6 +29,24 @@ describe('Provider', () => {
|
|
|
26
29
|
|
|
27
30
|
const logger = new Logger();
|
|
28
31
|
|
|
32
|
+
// Helper to spy on https.request and capture options
|
|
33
|
+
const spyOnHttpsRequest = (): { getCapturedOptions: () => any; restore: () => void } => {
|
|
34
|
+
let capturedOptions: any;
|
|
35
|
+
const originalRequest = https.request;
|
|
36
|
+
|
|
37
|
+
(https as any).request = function (options: any, callback: any) {
|
|
38
|
+
capturedOptions = options;
|
|
39
|
+
return originalRequest.call(https, options, callback);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
getCapturedOptions: () => capturedOptions,
|
|
44
|
+
restore: () => {
|
|
45
|
+
(https as any).request = originalRequest;
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
29
50
|
it('get', async context => {
|
|
30
51
|
const response = new Response('{"data": "value"}', {
|
|
31
52
|
status: 200,
|
|
@@ -36,7 +57,7 @@ describe('Provider', () => {
|
|
|
36
57
|
|
|
37
58
|
const actualResponse = await provider.get('/endpoint', {
|
|
38
59
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
39
|
-
logger
|
|
60
|
+
logger,
|
|
40
61
|
signal: new AbortController().signal,
|
|
41
62
|
additionnalheaders: { 'X-Additional-Header': 'value1' },
|
|
42
63
|
});
|
|
@@ -69,7 +90,7 @@ describe('Provider', () => {
|
|
|
69
90
|
|
|
70
91
|
const actualResponse = await provider.get('/endpoint', {
|
|
71
92
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
72
|
-
logger
|
|
93
|
+
logger,
|
|
73
94
|
signal: new AbortController().signal,
|
|
74
95
|
additionnalheaders: { 'X-Additional-Header': 'value1', Accept: 'text/html; charset=UTF-8' },
|
|
75
96
|
});
|
|
@@ -104,7 +125,7 @@ describe('Provider', () => {
|
|
|
104
125
|
|
|
105
126
|
const actualResponse = await provider.get('/endpoint', {
|
|
106
127
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
107
|
-
logger
|
|
128
|
+
logger,
|
|
108
129
|
signal: new AbortController().signal,
|
|
109
130
|
additionnalheaders: { 'X-Additional-Header': 'value1', Accept: 'application/schema+json; charset=UTF-8' },
|
|
110
131
|
});
|
|
@@ -139,7 +160,7 @@ describe('Provider', () => {
|
|
|
139
160
|
|
|
140
161
|
const actualResponse = await provider.get('/endpoint', {
|
|
141
162
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
142
|
-
logger
|
|
163
|
+
logger,
|
|
143
164
|
signal: new AbortController().signal,
|
|
144
165
|
additionnalheaders: { 'X-Additional-Header': 'value1', Accept: 'application/swagger+json; charset=UTF-8' },
|
|
145
166
|
});
|
|
@@ -174,7 +195,7 @@ describe('Provider', () => {
|
|
|
174
195
|
|
|
175
196
|
const actualResponse = await provider.get('/endpoint', {
|
|
176
197
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
177
|
-
logger
|
|
198
|
+
logger,
|
|
178
199
|
signal: new AbortController().signal,
|
|
179
200
|
additionnalheaders: { 'X-Additional-Header': 'value1' },
|
|
180
201
|
});
|
|
@@ -209,7 +230,7 @@ describe('Provider', () => {
|
|
|
209
230
|
|
|
210
231
|
const providerResponse = await provider.streamingGet('/endpoint/123', {
|
|
211
232
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
212
|
-
logger
|
|
233
|
+
logger,
|
|
213
234
|
signal: new AbortController().signal,
|
|
214
235
|
additionnalheaders: {
|
|
215
236
|
Accept: 'application/json',
|
|
@@ -232,7 +253,7 @@ describe('Provider', () => {
|
|
|
232
253
|
|
|
233
254
|
const actualResponse = await provider.get('https://my-cdn.my-domain.com/file.png', {
|
|
234
255
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
235
|
-
logger
|
|
256
|
+
logger,
|
|
236
257
|
signal: new AbortController().signal,
|
|
237
258
|
});
|
|
238
259
|
|
|
@@ -263,7 +284,7 @@ describe('Provider', () => {
|
|
|
263
284
|
|
|
264
285
|
const actualResponse = await provider.get('', {
|
|
265
286
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
266
|
-
logger
|
|
287
|
+
logger,
|
|
267
288
|
signal: new AbortController().signal,
|
|
268
289
|
});
|
|
269
290
|
|
|
@@ -299,7 +320,7 @@ describe('Provider', () => {
|
|
|
299
320
|
},
|
|
300
321
|
{
|
|
301
322
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
302
|
-
logger
|
|
323
|
+
logger,
|
|
303
324
|
signal: new AbortController().signal,
|
|
304
325
|
additionnalheaders: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-Additional-Header': 'value1' },
|
|
305
326
|
},
|
|
@@ -340,7 +361,7 @@ describe('Provider', () => {
|
|
|
340
361
|
],
|
|
341
362
|
{
|
|
342
363
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
343
|
-
logger
|
|
364
|
+
logger,
|
|
344
365
|
signal: new AbortController().signal,
|
|
345
366
|
additionnalheaders: { 'Content-Type': 'application/json-patch+json', 'X-Additional-Header': 'value1' },
|
|
346
367
|
},
|
|
@@ -381,7 +402,7 @@ describe('Provider', () => {
|
|
|
381
402
|
},
|
|
382
403
|
{
|
|
383
404
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
384
|
-
logger
|
|
405
|
+
logger,
|
|
385
406
|
signal: new AbortController().signal,
|
|
386
407
|
additionnalheaders: { 'X-Additional-Header': 'value1', 'Content-Type': 'application/json' },
|
|
387
408
|
},
|
|
@@ -419,7 +440,7 @@ describe('Provider', () => {
|
|
|
419
440
|
// What matters is that the body of put is a buffer
|
|
420
441
|
const actualResponse = await provider.putBuffer('endpoint/123', buffer, {
|
|
421
442
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
422
|
-
logger
|
|
443
|
+
logger,
|
|
423
444
|
signal: new AbortController().signal,
|
|
424
445
|
additionnalheaders: { 'X-Additional-Header': 'value1', 'Content-Type': 'application/octet-stream' },
|
|
425
446
|
});
|
|
@@ -458,7 +479,7 @@ describe('Provider', () => {
|
|
|
458
479
|
},
|
|
459
480
|
{
|
|
460
481
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
461
|
-
logger
|
|
482
|
+
logger,
|
|
462
483
|
signal: new AbortController().signal,
|
|
463
484
|
queryParams: { param1: 'value1', param2: 'value2' },
|
|
464
485
|
additionnalheaders: { 'X-Additional-Header': 'value1' },
|
|
@@ -494,7 +515,7 @@ describe('Provider', () => {
|
|
|
494
515
|
|
|
495
516
|
const actualResponse = await provider.delete('/endpoint/123', {
|
|
496
517
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
497
|
-
logger
|
|
518
|
+
logger,
|
|
498
519
|
signal: new AbortController().signal,
|
|
499
520
|
additionnalheaders: { 'X-Additional-Header': 'value1' },
|
|
500
521
|
});
|
|
@@ -530,7 +551,7 @@ describe('Provider', () => {
|
|
|
530
551
|
'/webhook',
|
|
531
552
|
{
|
|
532
553
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
533
|
-
logger
|
|
554
|
+
logger,
|
|
534
555
|
signal: new AbortController().signal,
|
|
535
556
|
additionnalheaders: { 'X-Additional-Header': 'value1' },
|
|
536
557
|
},
|
|
@@ -581,7 +602,7 @@ describe('Provider', () => {
|
|
|
581
602
|
|
|
582
603
|
const options = {
|
|
583
604
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
584
|
-
logger
|
|
605
|
+
logger,
|
|
585
606
|
signal: new AbortController().signal,
|
|
586
607
|
additionnalheaders: { 'X-Additional-Header': 'value1' },
|
|
587
608
|
};
|
|
@@ -634,7 +655,7 @@ describe('Provider', () => {
|
|
|
634
655
|
|
|
635
656
|
const options = {
|
|
636
657
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
637
|
-
logger
|
|
658
|
+
logger,
|
|
638
659
|
signal: new AbortController().signal,
|
|
639
660
|
additionnalheaders: { 'X-Additional-Header': 'value1' },
|
|
640
661
|
};
|
|
@@ -681,7 +702,7 @@ describe('Provider', () => {
|
|
|
681
702
|
|
|
682
703
|
const options = {
|
|
683
704
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
684
|
-
logger
|
|
705
|
+
logger,
|
|
685
706
|
signal: new AbortController().signal,
|
|
686
707
|
additionnalheaders: { 'X-Additional-Header': 'value1' },
|
|
687
708
|
};
|
|
@@ -722,7 +743,7 @@ describe('Provider', () => {
|
|
|
722
743
|
|
|
723
744
|
const options = {
|
|
724
745
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
725
|
-
logger
|
|
746
|
+
logger,
|
|
726
747
|
signal: new AbortController().signal,
|
|
727
748
|
additionnalheaders: { 'X-Additional-Header': 'value1' },
|
|
728
749
|
};
|
|
@@ -748,7 +769,7 @@ describe('Provider', () => {
|
|
|
748
769
|
|
|
749
770
|
const providerResponse = await provider.get<{ validJson: boolean }>('/endpoint/123', {
|
|
750
771
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
751
|
-
logger
|
|
772
|
+
logger,
|
|
752
773
|
signal: new AbortController().signal,
|
|
753
774
|
});
|
|
754
775
|
|
|
@@ -766,7 +787,7 @@ describe('Provider', () => {
|
|
|
766
787
|
|
|
767
788
|
const providerResponse = await provider.get<{ validJson: boolean }>('/endpoint/123', {
|
|
768
789
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
769
|
-
logger
|
|
790
|
+
logger,
|
|
770
791
|
signal: new AbortController().signal,
|
|
771
792
|
});
|
|
772
793
|
|
|
@@ -784,7 +805,7 @@ describe('Provider', () => {
|
|
|
784
805
|
|
|
785
806
|
const providerResponse = await provider.streamingGet('/endpoint/123', {
|
|
786
807
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
787
|
-
logger
|
|
808
|
+
logger,
|
|
788
809
|
signal: new AbortController().signal,
|
|
789
810
|
});
|
|
790
811
|
|
|
@@ -805,7 +826,7 @@ describe('Provider', () => {
|
|
|
805
826
|
{},
|
|
806
827
|
{
|
|
807
828
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
808
|
-
logger
|
|
829
|
+
logger,
|
|
809
830
|
signal: new AbortController().signal,
|
|
810
831
|
},
|
|
811
832
|
);
|
|
@@ -829,7 +850,7 @@ describe('Provider', () => {
|
|
|
829
850
|
try {
|
|
830
851
|
await provider.get('/endpoint/123', {
|
|
831
852
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
832
|
-
logger
|
|
853
|
+
logger,
|
|
833
854
|
signal: new AbortController().signal,
|
|
834
855
|
});
|
|
835
856
|
} catch (e) {
|
|
@@ -853,7 +874,7 @@ describe('Provider', () => {
|
|
|
853
874
|
try {
|
|
854
875
|
await provider.get('/endpoint/123', {
|
|
855
876
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
856
|
-
logger
|
|
877
|
+
logger,
|
|
857
878
|
signal: new AbortController().signal,
|
|
858
879
|
});
|
|
859
880
|
} catch (e) {
|
|
@@ -877,7 +898,7 @@ describe('Provider', () => {
|
|
|
877
898
|
await provider.get('/endpoint/123', {
|
|
878
899
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
879
900
|
signal: new AbortController().signal,
|
|
880
|
-
logger
|
|
901
|
+
logger,
|
|
881
902
|
});
|
|
882
903
|
} catch (e) {
|
|
883
904
|
error = e;
|
|
@@ -900,7 +921,7 @@ describe('Provider', () => {
|
|
|
900
921
|
await provider.get('/endpoint/123', {
|
|
901
922
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
902
923
|
signal: new AbortController().signal,
|
|
903
|
-
logger
|
|
924
|
+
logger,
|
|
904
925
|
});
|
|
905
926
|
} catch (e) {
|
|
906
927
|
error = e;
|
|
@@ -923,7 +944,7 @@ describe('Provider', () => {
|
|
|
923
944
|
await provider.get('/endpoint/123', {
|
|
924
945
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
925
946
|
signal: new AbortController().signal,
|
|
926
|
-
logger
|
|
947
|
+
logger,
|
|
927
948
|
});
|
|
928
949
|
} catch (e) {
|
|
929
950
|
error = e;
|
|
@@ -944,7 +965,7 @@ describe('Provider', () => {
|
|
|
944
965
|
await provider.get('/endpoint/123', {
|
|
945
966
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
946
967
|
signal: new AbortController().signal,
|
|
947
|
-
logger
|
|
968
|
+
logger,
|
|
948
969
|
});
|
|
949
970
|
} catch (e) {
|
|
950
971
|
error = e;
|
|
@@ -972,7 +993,7 @@ describe('Provider', () => {
|
|
|
972
993
|
await provider.get('/endpoint/123', {
|
|
973
994
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
974
995
|
signal: new AbortController().signal,
|
|
975
|
-
logger
|
|
996
|
+
logger,
|
|
976
997
|
});
|
|
977
998
|
} catch (e) {
|
|
978
999
|
error = e;
|
|
@@ -992,7 +1013,7 @@ describe('Provider', () => {
|
|
|
992
1013
|
await provider.get('/endpoint/123', {
|
|
993
1014
|
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
994
1015
|
signal: new AbortController().signal,
|
|
995
|
-
logger
|
|
1016
|
+
logger,
|
|
996
1017
|
});
|
|
997
1018
|
|
|
998
1019
|
assert.equal(loggerStub.mock.callCount(), 1);
|
|
@@ -1001,4 +1022,383 @@ describe('Provider', () => {
|
|
|
1001
1022
|
/Connector API Request GET www.myApi.com\/endpoint\/123 201 - \d+ ms/,
|
|
1002
1023
|
);
|
|
1003
1024
|
});
|
|
1025
|
+
|
|
1026
|
+
// Stream upload will not load the data in memory and sending a binary in the body
|
|
1027
|
+
it('postStream streams data without buffering', async () => {
|
|
1028
|
+
const streamProvider = new Provider({
|
|
1029
|
+
prepareRequest: requestOptions => ({
|
|
1030
|
+
url: `https://www.${requestOptions.credentials.domain ?? 'myApi.com'}`,
|
|
1031
|
+
headers: {
|
|
1032
|
+
'X-Custom-Provider-Header': 'value',
|
|
1033
|
+
'X-Provider-Credential-Header': requestOptions.credentials.apiKey as string,
|
|
1034
|
+
},
|
|
1035
|
+
}),
|
|
1036
|
+
});
|
|
1037
|
+
|
|
1038
|
+
const testData = 'test binary data for streaming';
|
|
1039
|
+
const stream = Readable.from([testData]);
|
|
1040
|
+
|
|
1041
|
+
const scope = nock('https://www.myApi.com')
|
|
1042
|
+
.post('/upload', testData)
|
|
1043
|
+
.matchHeader('content-type', 'application/octet-stream')
|
|
1044
|
+
.matchHeader('accept', 'application/json')
|
|
1045
|
+
.matchHeader('x-custom-provider-header', 'value')
|
|
1046
|
+
.matchHeader('x-provider-credential-header', 'apikey#1111')
|
|
1047
|
+
.reply(201, { success: true, id: '12345' });
|
|
1048
|
+
|
|
1049
|
+
const response = await streamProvider.postStream('/upload', stream, {
|
|
1050
|
+
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
1051
|
+
logger,
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
assert.ok(scope.isDone(), 'HTTPS request should have been made');
|
|
1055
|
+
assert.equal(response.status, 201);
|
|
1056
|
+
assert.deepEqual(response.body, { success: true, id: '12345' });
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
it('postStream with query params', async () => {
|
|
1060
|
+
const streamProvider = new Provider({
|
|
1061
|
+
prepareRequest: requestOptions => ({
|
|
1062
|
+
url: `https://www.myApi.com`,
|
|
1063
|
+
headers: {
|
|
1064
|
+
'X-Custom-Provider-Header': 'value',
|
|
1065
|
+
'X-Provider-Credential-Header': requestOptions.credentials.apiKey as string,
|
|
1066
|
+
},
|
|
1067
|
+
}),
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
const testData = 'data with params';
|
|
1071
|
+
const stream = Readable.from([testData]);
|
|
1072
|
+
|
|
1073
|
+
const scope = nock('https://www.myApi.com')
|
|
1074
|
+
.post('/upload?key=value&format=json', testData)
|
|
1075
|
+
.reply(200, { uploaded: true });
|
|
1076
|
+
|
|
1077
|
+
const response = await streamProvider.postStream('/upload', stream, {
|
|
1078
|
+
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
1079
|
+
logger,
|
|
1080
|
+
queryParams: { key: 'value', format: 'json' },
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
assert.ok(scope.isDone());
|
|
1084
|
+
assert.equal(response.status, 200);
|
|
1085
|
+
assert.deepEqual(response.body, { uploaded: true });
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
it('postStream handles error responses', async () => {
|
|
1089
|
+
const streamProvider = new Provider({
|
|
1090
|
+
prepareRequest: requestOptions => ({
|
|
1091
|
+
url: `https://www.myApi.com`,
|
|
1092
|
+
headers: {
|
|
1093
|
+
'X-Custom-Provider-Header': 'value',
|
|
1094
|
+
'X-Provider-Credential-Header': requestOptions.credentials.apiKey as string,
|
|
1095
|
+
},
|
|
1096
|
+
}),
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
const stream = Readable.from(['error test data']);
|
|
1100
|
+
|
|
1101
|
+
nock('https://www.myApi.com').post('/upload').reply(400, 'Bad request error');
|
|
1102
|
+
|
|
1103
|
+
let error;
|
|
1104
|
+
try {
|
|
1105
|
+
await streamProvider.postStream('/upload', stream, {
|
|
1106
|
+
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
1107
|
+
logger,
|
|
1108
|
+
});
|
|
1109
|
+
} catch (e) {
|
|
1110
|
+
error = e;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
assert.ok(error instanceof HttpErrors.BadRequestError);
|
|
1114
|
+
assert.equal(error.message, 'Bad request error');
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
it('postStream with rate limiter', async () => {
|
|
1118
|
+
let rateLimiterCalled = false;
|
|
1119
|
+
let rateLimiterCredentials;
|
|
1120
|
+
|
|
1121
|
+
const mockRateLimiter = async (options: any, request: () => Promise<any>) => {
|
|
1122
|
+
rateLimiterCalled = true;
|
|
1123
|
+
rateLimiterCredentials = options.credentials;
|
|
1124
|
+
return request();
|
|
1125
|
+
};
|
|
1126
|
+
|
|
1127
|
+
const rateLimitedProvider = new Provider({
|
|
1128
|
+
prepareRequest: requestOptions => ({
|
|
1129
|
+
url: `https://www.myApi.com`,
|
|
1130
|
+
headers: {
|
|
1131
|
+
'X-Custom-Provider-Header': 'value',
|
|
1132
|
+
'X-Provider-Credential-Header': requestOptions.credentials.apiKey as string,
|
|
1133
|
+
},
|
|
1134
|
+
}),
|
|
1135
|
+
rateLimiter: mockRateLimiter,
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
const testData = 'rate limited data';
|
|
1139
|
+
const stream = Readable.from([testData]);
|
|
1140
|
+
|
|
1141
|
+
nock('https://www.myApi.com').post('/upload', testData).reply(201, { success: true });
|
|
1142
|
+
|
|
1143
|
+
await rateLimitedProvider.postStream('/upload', stream, {
|
|
1144
|
+
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
1145
|
+
logger,
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
assert.ok(rateLimiterCalled, 'Rate limiter should have been called');
|
|
1149
|
+
assert.deepEqual(rateLimiterCredentials, {
|
|
1150
|
+
apiKey: 'apikey#1111',
|
|
1151
|
+
unitoCredentialId: '123',
|
|
1152
|
+
});
|
|
1153
|
+
});
|
|
1154
|
+
|
|
1155
|
+
it('postStream sets timeout to 0 (no timeout)', async () => {
|
|
1156
|
+
const streamProvider = new Provider({
|
|
1157
|
+
prepareRequest: requestOptions => ({
|
|
1158
|
+
url: `https://www.myApi.com`,
|
|
1159
|
+
headers: {
|
|
1160
|
+
'X-Custom-Provider-Header': 'value',
|
|
1161
|
+
'X-Provider-Credential-Header': requestOptions.credentials.apiKey as string,
|
|
1162
|
+
},
|
|
1163
|
+
}),
|
|
1164
|
+
});
|
|
1165
|
+
|
|
1166
|
+
const testData = 'timeout test data';
|
|
1167
|
+
const stream = Readable.from([testData]);
|
|
1168
|
+
|
|
1169
|
+
const spy = spyOnHttpsRequest();
|
|
1170
|
+
const scope = nock('https://www.myApi.com').post('/upload', testData).reply(201, { success: true });
|
|
1171
|
+
|
|
1172
|
+
try {
|
|
1173
|
+
await streamProvider.postStream('/upload', stream, {
|
|
1174
|
+
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
1175
|
+
logger,
|
|
1176
|
+
});
|
|
1177
|
+
|
|
1178
|
+
assert.ok(scope.isDone());
|
|
1179
|
+
assert.equal(spy.getCapturedOptions().timeout, 0, 'Timeout should be set to 0 (no timeout)');
|
|
1180
|
+
} finally {
|
|
1181
|
+
spy.restore();
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1184
|
+
|
|
1185
|
+
it('postStream handles AbortSignal for request cancellation', async () => {
|
|
1186
|
+
const streamProvider = new Provider({
|
|
1187
|
+
prepareRequest: requestOptions => ({
|
|
1188
|
+
url: `https://www.myApi.com`,
|
|
1189
|
+
headers: {
|
|
1190
|
+
'X-Custom-Provider-Header': 'value',
|
|
1191
|
+
'X-Provider-Credential-Header': requestOptions.credentials.apiKey as string,
|
|
1192
|
+
},
|
|
1193
|
+
}),
|
|
1194
|
+
});
|
|
1195
|
+
|
|
1196
|
+
const stream = Readable.from(['abort signal test']);
|
|
1197
|
+
const abortController = new AbortController();
|
|
1198
|
+
|
|
1199
|
+
// Simulate aborting the request immediately
|
|
1200
|
+
abortController.abort();
|
|
1201
|
+
|
|
1202
|
+
let error;
|
|
1203
|
+
try {
|
|
1204
|
+
await streamProvider.postStream('/upload', stream, {
|
|
1205
|
+
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
1206
|
+
logger,
|
|
1207
|
+
signal: abortController.signal,
|
|
1208
|
+
});
|
|
1209
|
+
} catch (e) {
|
|
1210
|
+
error = e;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
assert.ok(error instanceof HttpErrors.TimeoutError);
|
|
1214
|
+
assert.equal(error.message, 'Timeout');
|
|
1215
|
+
});
|
|
1216
|
+
|
|
1217
|
+
it('postStream handles AbortSignal timeout during request', async () => {
|
|
1218
|
+
const streamProvider = new Provider({
|
|
1219
|
+
prepareRequest: requestOptions => ({
|
|
1220
|
+
url: `https://www.myApi.com`,
|
|
1221
|
+
headers: {
|
|
1222
|
+
'X-Custom-Provider-Header': 'value',
|
|
1223
|
+
'X-Provider-Credential-Header': requestOptions.credentials.apiKey as string,
|
|
1224
|
+
},
|
|
1225
|
+
}),
|
|
1226
|
+
});
|
|
1227
|
+
|
|
1228
|
+
const stream = Readable.from(['timeout during request test']);
|
|
1229
|
+
const abortController = new AbortController();
|
|
1230
|
+
|
|
1231
|
+
// Delay response to simulate a slow server
|
|
1232
|
+
nock('https://www.myApi.com').post('/upload').delayConnection(100).reply(201, { success: true });
|
|
1233
|
+
|
|
1234
|
+
// Abort after 50ms
|
|
1235
|
+
setTimeout(() => abortController.abort(), 50);
|
|
1236
|
+
|
|
1237
|
+
let error;
|
|
1238
|
+
try {
|
|
1239
|
+
await streamProvider.postStream('/upload', stream, {
|
|
1240
|
+
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
1241
|
+
logger,
|
|
1242
|
+
signal: abortController.signal,
|
|
1243
|
+
});
|
|
1244
|
+
} catch (e) {
|
|
1245
|
+
error = e;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
assert.ok(error instanceof HttpErrors.TimeoutError);
|
|
1249
|
+
assert.equal(error.message, 'Timeout');
|
|
1250
|
+
});
|
|
1251
|
+
|
|
1252
|
+
it('postStream cleans up AbortSignal listener on success', async () => {
|
|
1253
|
+
const streamProvider = new Provider({
|
|
1254
|
+
prepareRequest: requestOptions => ({
|
|
1255
|
+
url: `https://www.myApi.com`,
|
|
1256
|
+
headers: {
|
|
1257
|
+
'X-Custom-Provider-Header': 'value',
|
|
1258
|
+
'X-Provider-Credential-Header': requestOptions.credentials.apiKey as string,
|
|
1259
|
+
},
|
|
1260
|
+
}),
|
|
1261
|
+
});
|
|
1262
|
+
|
|
1263
|
+
const testData = 'cleanup test';
|
|
1264
|
+
const stream = Readable.from([testData]);
|
|
1265
|
+
const abortController = new AbortController();
|
|
1266
|
+
|
|
1267
|
+
const scope = nock('https://www.myApi.com').post('/upload', testData).reply(201, { success: true });
|
|
1268
|
+
|
|
1269
|
+
const response = await streamProvider.postStream('/upload', stream, {
|
|
1270
|
+
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
1271
|
+
logger,
|
|
1272
|
+
signal: abortController.signal,
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
assert.ok(scope.isDone());
|
|
1276
|
+
assert.equal(response.status, 201);
|
|
1277
|
+
|
|
1278
|
+
// Verify the listener was removed by checking that aborting after completion
|
|
1279
|
+
// doesn't cause any side effects (if listener wasn't removed, this could cause issues)
|
|
1280
|
+
assert.doesNotThrow(() => {
|
|
1281
|
+
abortController.abort();
|
|
1282
|
+
}, 'Aborting after completion should not throw or cause issues');
|
|
1283
|
+
|
|
1284
|
+
// Verify the signal is indeed aborted
|
|
1285
|
+
assert.ok(abortController.signal.aborted, 'Signal should be aborted');
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
it('postForm handles AbortSignal for request cancellation', async () => {
|
|
1289
|
+
const FormData = (await import('form-data')).default;
|
|
1290
|
+
|
|
1291
|
+
const formProvider = new Provider({
|
|
1292
|
+
prepareRequest: requestOptions => ({
|
|
1293
|
+
url: `https://www.myApi.com`,
|
|
1294
|
+
headers: {
|
|
1295
|
+
'X-Custom-Provider-Header': 'value',
|
|
1296
|
+
'X-Provider-Credential-Header': requestOptions.credentials.apiKey as string,
|
|
1297
|
+
},
|
|
1298
|
+
}),
|
|
1299
|
+
});
|
|
1300
|
+
|
|
1301
|
+
const form = new FormData();
|
|
1302
|
+
form.append('field1', 'value1');
|
|
1303
|
+
form.append('field2', 'value2');
|
|
1304
|
+
|
|
1305
|
+
const abortController = new AbortController();
|
|
1306
|
+
|
|
1307
|
+
// Simulate aborting the request immediately
|
|
1308
|
+
abortController.abort();
|
|
1309
|
+
|
|
1310
|
+
let error;
|
|
1311
|
+
try {
|
|
1312
|
+
await formProvider.postForm('/upload-form', form, {
|
|
1313
|
+
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
1314
|
+
logger,
|
|
1315
|
+
signal: abortController.signal,
|
|
1316
|
+
});
|
|
1317
|
+
} catch (e) {
|
|
1318
|
+
error = e;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
assert.ok(error instanceof HttpErrors.TimeoutError);
|
|
1322
|
+
assert.equal(error.message, 'Timeout');
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
it('postForm handles AbortSignal timeout during request', async () => {
|
|
1326
|
+
const FormData = (await import('form-data')).default;
|
|
1327
|
+
|
|
1328
|
+
const formProvider = new Provider({
|
|
1329
|
+
prepareRequest: requestOptions => ({
|
|
1330
|
+
url: `https://www.myApi.com`,
|
|
1331
|
+
headers: {
|
|
1332
|
+
'X-Custom-Provider-Header': 'value',
|
|
1333
|
+
'X-Provider-Credential-Header': requestOptions.credentials.apiKey as string,
|
|
1334
|
+
},
|
|
1335
|
+
}),
|
|
1336
|
+
});
|
|
1337
|
+
|
|
1338
|
+
const form = new FormData();
|
|
1339
|
+
form.append('field1', 'value1');
|
|
1340
|
+
|
|
1341
|
+
const abortController = new AbortController();
|
|
1342
|
+
|
|
1343
|
+
// Mock a delayed response
|
|
1344
|
+
nock('https://www.myApi.com').post('/upload-form').delayConnection(100).reply(201, { success: true, id: '12345' });
|
|
1345
|
+
|
|
1346
|
+
// Abort after 50ms
|
|
1347
|
+
setTimeout(() => abortController.abort(), 50);
|
|
1348
|
+
|
|
1349
|
+
let error;
|
|
1350
|
+
try {
|
|
1351
|
+
await formProvider.postForm('/upload-form', form, {
|
|
1352
|
+
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
1353
|
+
logger,
|
|
1354
|
+
signal: abortController.signal,
|
|
1355
|
+
});
|
|
1356
|
+
} catch (e) {
|
|
1357
|
+
error = e;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
assert.ok(error instanceof HttpErrors.TimeoutError);
|
|
1361
|
+
assert.equal(error.message, 'Timeout');
|
|
1362
|
+
});
|
|
1363
|
+
|
|
1364
|
+
it('postForm successfully completes with AbortSignal provided', async () => {
|
|
1365
|
+
const FormData = (await import('form-data')).default;
|
|
1366
|
+
|
|
1367
|
+
const formProvider = new Provider({
|
|
1368
|
+
prepareRequest: requestOptions => ({
|
|
1369
|
+
url: `https://www.myApi.com`,
|
|
1370
|
+
headers: {
|
|
1371
|
+
'X-Custom-Provider-Header': 'value',
|
|
1372
|
+
'X-Provider-Credential-Header': requestOptions.credentials.apiKey as string,
|
|
1373
|
+
},
|
|
1374
|
+
}),
|
|
1375
|
+
});
|
|
1376
|
+
|
|
1377
|
+
const form = new FormData();
|
|
1378
|
+
form.append('field1', 'value1');
|
|
1379
|
+
form.append('field2', 'value2');
|
|
1380
|
+
|
|
1381
|
+
const abortController = new AbortController();
|
|
1382
|
+
|
|
1383
|
+
const scope = nock('https://www.myApi.com').post('/upload-form').reply(201, { success: true, id: '12345' });
|
|
1384
|
+
|
|
1385
|
+
const response = await formProvider.postForm('/upload-form', form, {
|
|
1386
|
+
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
1387
|
+
logger,
|
|
1388
|
+
signal: abortController.signal,
|
|
1389
|
+
});
|
|
1390
|
+
|
|
1391
|
+
assert.ok(scope.isDone());
|
|
1392
|
+
assert.equal(response.status, 201);
|
|
1393
|
+
assert.deepEqual(response.body, { success: true, id: '12345' });
|
|
1394
|
+
|
|
1395
|
+
// Verify the listener was removed by checking that aborting after completion
|
|
1396
|
+
// doesn't cause any side effects
|
|
1397
|
+
assert.doesNotThrow(() => {
|
|
1398
|
+
abortController.abort();
|
|
1399
|
+
}, 'Aborting after completion should not throw or cause issues');
|
|
1400
|
+
|
|
1401
|
+
// Verify the signal is indeed aborted
|
|
1402
|
+
assert.ok(abortController.signal.aborted, 'Signal should be aborted');
|
|
1403
|
+
});
|
|
1004
1404
|
});
|