@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.
@@ -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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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.13",
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",