@unito/integration-sdk 2.4.4 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +80 -0
- package/dist/src/helpers.d.ts +14 -0
- package/dist/src/helpers.js +20 -0
- package/dist/src/index.cjs +37 -4
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/resources/provider.d.ts +1 -2
- package/dist/src/resources/provider.js +16 -4
- package/dist/test/helpers.test.js +95 -1
- package/dist/test/resources/provider.test.js +100 -40
- package/package.json +1 -1
- package/src/helpers.ts +27 -0
- package/src/index.ts +1 -1
- package/src/resources/provider.ts +21 -6
- package/test/helpers.test.ts +110 -1
- package/test/resources/provider.test.ts +100 -40
|
@@ -42,7 +42,7 @@ describe('Provider', () => {
|
|
|
42
42
|
it('get', async (context) => {
|
|
43
43
|
const response = new Response('{"data": "value"}', {
|
|
44
44
|
status: 200,
|
|
45
|
-
headers: { 'Content-Type': 'application/json' },
|
|
45
|
+
headers: new Headers({ 'Content-Type': 'application/json' }),
|
|
46
46
|
});
|
|
47
47
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
48
48
|
const actualResponse = await provider.get('/endpoint', {
|
|
@@ -66,12 +66,16 @@ describe('Provider', () => {
|
|
|
66
66
|
},
|
|
67
67
|
},
|
|
68
68
|
]);
|
|
69
|
-
assert.deepEqual(actualResponse, {
|
|
69
|
+
assert.deepEqual(actualResponse, {
|
|
70
|
+
status: 200,
|
|
71
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
72
|
+
body: { data: 'value' },
|
|
73
|
+
});
|
|
70
74
|
});
|
|
71
75
|
it('accepts text/html type response', async (context) => {
|
|
72
76
|
const response = new Response('', {
|
|
73
77
|
status: 200,
|
|
74
|
-
headers: { 'Content-Type': 'text/html; charset=UTF-8' },
|
|
78
|
+
headers: new Headers({ 'Content-Type': 'text/html; charset=UTF-8' }),
|
|
75
79
|
});
|
|
76
80
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
77
81
|
const actualResponse = await provider.get('/endpoint', {
|
|
@@ -95,12 +99,16 @@ describe('Provider', () => {
|
|
|
95
99
|
},
|
|
96
100
|
},
|
|
97
101
|
]);
|
|
98
|
-
assert.deepEqual(actualResponse, {
|
|
102
|
+
assert.deepEqual(actualResponse, {
|
|
103
|
+
status: 200,
|
|
104
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
105
|
+
body: '',
|
|
106
|
+
});
|
|
99
107
|
});
|
|
100
108
|
it('accepts application/schema+json type response', async (context) => {
|
|
101
109
|
const response = new Response('{"data": "value"}', {
|
|
102
110
|
status: 200,
|
|
103
|
-
headers: { 'Content-Type': 'application/schema+json; charset=UTF-8' },
|
|
111
|
+
headers: new Headers({ 'Content-Type': 'application/schema+json; charset=UTF-8' }),
|
|
104
112
|
});
|
|
105
113
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
106
114
|
const actualResponse = await provider.get('/endpoint', {
|
|
@@ -124,12 +132,16 @@ describe('Provider', () => {
|
|
|
124
132
|
},
|
|
125
133
|
},
|
|
126
134
|
]);
|
|
127
|
-
assert.deepEqual(actualResponse, {
|
|
135
|
+
assert.deepEqual(actualResponse, {
|
|
136
|
+
status: 200,
|
|
137
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
138
|
+
body: { data: 'value' },
|
|
139
|
+
});
|
|
128
140
|
});
|
|
129
141
|
it('accepts application/swagger+json type response', async (context) => {
|
|
130
142
|
const response = new Response('{"data": "value"}', {
|
|
131
143
|
status: 200,
|
|
132
|
-
headers: { 'Content-Type': 'application/swagger+json; charset=UTF-8' },
|
|
144
|
+
headers: new Headers({ 'Content-Type': 'application/swagger+json; charset=UTF-8' }),
|
|
133
145
|
});
|
|
134
146
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
135
147
|
const actualResponse = await provider.get('/endpoint', {
|
|
@@ -153,12 +165,16 @@ describe('Provider', () => {
|
|
|
153
165
|
},
|
|
154
166
|
},
|
|
155
167
|
]);
|
|
156
|
-
assert.deepEqual(actualResponse, {
|
|
168
|
+
assert.deepEqual(actualResponse, {
|
|
169
|
+
status: 200,
|
|
170
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
171
|
+
body: { data: 'value' },
|
|
172
|
+
});
|
|
157
173
|
});
|
|
158
174
|
it('accepts application/vnd.oracle.resource+json type response', async (context) => {
|
|
159
175
|
const response = new Response('{"data": "value"}', {
|
|
160
176
|
status: 200,
|
|
161
|
-
headers: { 'Content-Type': 'application/vnd.oracle.resource+json; type=collection; charset=UTF-8' },
|
|
177
|
+
headers: new Headers({ 'Content-Type': 'application/vnd.oracle.resource+json; type=collection; charset=UTF-8' }),
|
|
162
178
|
});
|
|
163
179
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
164
180
|
const actualResponse = await provider.get('/endpoint', {
|
|
@@ -182,12 +198,16 @@ describe('Provider', () => {
|
|
|
182
198
|
},
|
|
183
199
|
},
|
|
184
200
|
]);
|
|
185
|
-
assert.deepEqual(actualResponse, {
|
|
201
|
+
assert.deepEqual(actualResponse, {
|
|
202
|
+
status: 200,
|
|
203
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
204
|
+
body: { data: 'value' },
|
|
205
|
+
});
|
|
186
206
|
});
|
|
187
207
|
it('returns the raw response body if specified', async (context) => {
|
|
188
208
|
const response = new Response(`IMAGINE A HUGE PAYLOAD`, {
|
|
189
209
|
status: 200,
|
|
190
|
-
headers: { 'Content-Type': 'image/png' },
|
|
210
|
+
headers: new Headers({ 'Content-Type': 'image/png' }),
|
|
191
211
|
});
|
|
192
212
|
context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
193
213
|
const providerResponse = await provider.streamingGet('/endpoint/123', {
|
|
@@ -206,7 +226,7 @@ describe('Provider', () => {
|
|
|
206
226
|
it('gets an endpoint which is an absolute url', async (context) => {
|
|
207
227
|
const response = new Response('{"data": "value"}', {
|
|
208
228
|
status: 200,
|
|
209
|
-
headers: { 'Content-Type': 'application/json' },
|
|
229
|
+
headers: new Headers({ 'Content-Type': 'application/json' }),
|
|
210
230
|
});
|
|
211
231
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
212
232
|
const actualResponse = await provider.get('https://my-cdn.my-domain.com/file.png', {
|
|
@@ -228,12 +248,16 @@ describe('Provider', () => {
|
|
|
228
248
|
},
|
|
229
249
|
},
|
|
230
250
|
]);
|
|
231
|
-
assert.deepEqual(actualResponse, {
|
|
251
|
+
assert.deepEqual(actualResponse, {
|
|
252
|
+
status: 200,
|
|
253
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
254
|
+
body: { data: 'value' },
|
|
255
|
+
});
|
|
232
256
|
});
|
|
233
257
|
it('gets on provider url', async (context) => {
|
|
234
258
|
const response = new Response('{"data": "value"}', {
|
|
235
259
|
status: 200,
|
|
236
|
-
headers: { 'Content-Type': 'application/json' },
|
|
260
|
+
headers: new Headers({ 'Content-Type': 'application/json' }),
|
|
237
261
|
});
|
|
238
262
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
239
263
|
const actualResponse = await provider.get('', {
|
|
@@ -255,12 +279,16 @@ describe('Provider', () => {
|
|
|
255
279
|
},
|
|
256
280
|
},
|
|
257
281
|
]);
|
|
258
|
-
assert.deepEqual(actualResponse, {
|
|
282
|
+
assert.deepEqual(actualResponse, {
|
|
283
|
+
status: 200,
|
|
284
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
285
|
+
body: { data: 'value' },
|
|
286
|
+
});
|
|
259
287
|
});
|
|
260
288
|
it('post with url encoded body', async (context) => {
|
|
261
289
|
const response = new Response('{"data": "value"}', {
|
|
262
290
|
status: 201,
|
|
263
|
-
headers: { 'Content-Type': 'application/json' },
|
|
291
|
+
headers: new Headers({ 'Content-Type': 'application/json' }),
|
|
264
292
|
});
|
|
265
293
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
266
294
|
const actualResponse = await provider.post('/endpoint', {
|
|
@@ -287,12 +315,16 @@ describe('Provider', () => {
|
|
|
287
315
|
},
|
|
288
316
|
},
|
|
289
317
|
]);
|
|
290
|
-
assert.deepEqual(actualResponse, {
|
|
318
|
+
assert.deepEqual(actualResponse, {
|
|
319
|
+
status: 201,
|
|
320
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
321
|
+
body: { data: 'value' },
|
|
322
|
+
});
|
|
291
323
|
});
|
|
292
324
|
it('accepts an array as body for post request', async (context) => {
|
|
293
325
|
const response = new Response('{"data": "value"}', {
|
|
294
326
|
status: 201,
|
|
295
|
-
headers: { 'Content-Type': 'application/json' },
|
|
327
|
+
headers: new Headers({ 'Content-Type': 'application/json' }),
|
|
296
328
|
});
|
|
297
329
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
298
330
|
const actualResponse = await provider.post('/endpoint', [
|
|
@@ -320,12 +352,16 @@ describe('Provider', () => {
|
|
|
320
352
|
},
|
|
321
353
|
},
|
|
322
354
|
]);
|
|
323
|
-
assert.deepEqual(actualResponse, {
|
|
355
|
+
assert.deepEqual(actualResponse, {
|
|
356
|
+
status: 201,
|
|
357
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
358
|
+
body: { data: 'value' },
|
|
359
|
+
});
|
|
324
360
|
});
|
|
325
361
|
it('put with json body', async (context) => {
|
|
326
362
|
const response = new Response('{"data": "value"}', {
|
|
327
363
|
status: 201,
|
|
328
|
-
headers: { 'Content-Type': 'application/json' },
|
|
364
|
+
headers: new Headers({ 'Content-Type': 'application/json' }),
|
|
329
365
|
});
|
|
330
366
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
331
367
|
// Removing leading '/' on endpoint to make sure we support both cases
|
|
@@ -353,12 +389,16 @@ describe('Provider', () => {
|
|
|
353
389
|
},
|
|
354
390
|
},
|
|
355
391
|
]);
|
|
356
|
-
assert.deepEqual(actualResponse, {
|
|
392
|
+
assert.deepEqual(actualResponse, {
|
|
393
|
+
status: 201,
|
|
394
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
395
|
+
body: { data: 'value' },
|
|
396
|
+
});
|
|
357
397
|
});
|
|
358
398
|
it('putBuffer with Buffer body', async (context) => {
|
|
359
399
|
const response = new Response('{"data": "value"}', {
|
|
360
400
|
status: 201,
|
|
361
|
-
headers: { 'Content-Type': 'application/json' },
|
|
401
|
+
headers: new Headers({ 'Content-Type': 'application/json' }),
|
|
362
402
|
});
|
|
363
403
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
364
404
|
const buffer = Buffer.from('binary data content');
|
|
@@ -385,12 +425,16 @@ describe('Provider', () => {
|
|
|
385
425
|
},
|
|
386
426
|
},
|
|
387
427
|
]);
|
|
388
|
-
assert.deepEqual(actualResponse, {
|
|
428
|
+
assert.deepEqual(actualResponse, {
|
|
429
|
+
status: 201,
|
|
430
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
431
|
+
body: { data: 'value' },
|
|
432
|
+
});
|
|
389
433
|
});
|
|
390
434
|
it('patch with query params', async (context) => {
|
|
391
435
|
const response = new Response('{"data": "value"}', {
|
|
392
436
|
status: 201,
|
|
393
|
-
headers: { 'Content-Type': 'application/json' },
|
|
437
|
+
headers: new Headers({ 'Content-Type': 'application/json' }),
|
|
394
438
|
});
|
|
395
439
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
396
440
|
const actualResponse = await provider.patch('/endpoint/123', {
|
|
@@ -418,12 +462,16 @@ describe('Provider', () => {
|
|
|
418
462
|
},
|
|
419
463
|
},
|
|
420
464
|
]);
|
|
421
|
-
assert.deepEqual(actualResponse, {
|
|
465
|
+
assert.deepEqual(actualResponse, {
|
|
466
|
+
status: 201,
|
|
467
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
468
|
+
body: { data: 'value' },
|
|
469
|
+
});
|
|
422
470
|
});
|
|
423
471
|
it('delete', async (context) => {
|
|
424
472
|
const response = new Response(undefined, {
|
|
425
473
|
status: 204,
|
|
426
|
-
headers: { 'Content-Type': 'application/json' },
|
|
474
|
+
headers: new Headers({ 'Content-Type': 'application/json' }),
|
|
427
475
|
});
|
|
428
476
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
429
477
|
const actualResponse = await provider.delete('/endpoint/123', {
|
|
@@ -447,12 +495,16 @@ describe('Provider', () => {
|
|
|
447
495
|
},
|
|
448
496
|
},
|
|
449
497
|
]);
|
|
450
|
-
assert.deepEqual(actualResponse, {
|
|
498
|
+
assert.deepEqual(actualResponse, {
|
|
499
|
+
status: 204,
|
|
500
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
501
|
+
body: undefined,
|
|
502
|
+
});
|
|
451
503
|
});
|
|
452
504
|
it('deleteWithBody', async (context) => {
|
|
453
505
|
const response = new Response('{"success": true}', {
|
|
454
506
|
status: 200,
|
|
455
|
-
headers: { 'Content-Type': 'application/json' },
|
|
507
|
+
headers: new Headers({ 'Content-Type': 'application/json' }),
|
|
456
508
|
});
|
|
457
509
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
458
510
|
const requestBody = { webhookIds: [1, 2, 3] };
|
|
@@ -478,7 +530,11 @@ describe('Provider', () => {
|
|
|
478
530
|
},
|
|
479
531
|
},
|
|
480
532
|
]);
|
|
481
|
-
assert.deepEqual(actualResponse, {
|
|
533
|
+
assert.deepEqual(actualResponse, {
|
|
534
|
+
status: 200,
|
|
535
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
536
|
+
body: { success: true },
|
|
537
|
+
});
|
|
482
538
|
});
|
|
483
539
|
it('uses rate limiter if provided', async (context) => {
|
|
484
540
|
const mockRateLimiter = context.mock.fn((_context, request) => Promise.resolve(request()));
|
|
@@ -496,7 +552,7 @@ describe('Provider', () => {
|
|
|
496
552
|
});
|
|
497
553
|
const response = new Response(undefined, {
|
|
498
554
|
status: 204,
|
|
499
|
-
headers: { 'Content-Type': 'application/json' },
|
|
555
|
+
headers: new Headers({ 'Content-Type': 'application/json' }),
|
|
500
556
|
});
|
|
501
557
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
502
558
|
const options = {
|
|
@@ -523,7 +579,11 @@ describe('Provider', () => {
|
|
|
523
579
|
},
|
|
524
580
|
},
|
|
525
581
|
]);
|
|
526
|
-
assert.deepEqual(actualResponse, {
|
|
582
|
+
assert.deepEqual(actualResponse, {
|
|
583
|
+
status: 204,
|
|
584
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
585
|
+
body: undefined,
|
|
586
|
+
});
|
|
527
587
|
});
|
|
528
588
|
it('uses custom error handler if provided', async (context) => {
|
|
529
589
|
const rateLimitedProvider = new Provider({
|
|
@@ -542,7 +602,7 @@ describe('Provider', () => {
|
|
|
542
602
|
});
|
|
543
603
|
const response = new Response(undefined, {
|
|
544
604
|
status: 400,
|
|
545
|
-
headers: { 'Content-Type': 'application/json' },
|
|
605
|
+
headers: new Headers({ 'Content-Type': 'application/json' }),
|
|
546
606
|
});
|
|
547
607
|
context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
548
608
|
const options = {
|
|
@@ -583,7 +643,7 @@ describe('Provider', () => {
|
|
|
583
643
|
});
|
|
584
644
|
const response = new Response(undefined, {
|
|
585
645
|
status: 400,
|
|
586
|
-
headers: { 'Content-Type': 'application/json' },
|
|
646
|
+
headers: new Headers({ 'Content-Type': 'application/json' }),
|
|
587
647
|
});
|
|
588
648
|
context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
589
649
|
const options = {
|
|
@@ -619,7 +679,7 @@ describe('Provider', () => {
|
|
|
619
679
|
});
|
|
620
680
|
const response = new Response(undefined, {
|
|
621
681
|
status: 404,
|
|
622
|
-
headers: { 'Content-Type': 'application/json' },
|
|
682
|
+
headers: new Headers({ 'Content-Type': 'application/json' }),
|
|
623
683
|
});
|
|
624
684
|
context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
625
685
|
const options = {
|
|
@@ -641,7 +701,7 @@ describe('Provider', () => {
|
|
|
641
701
|
it('returns valid json response', async (context) => {
|
|
642
702
|
const response = new Response(`{ "validJson": true }`, {
|
|
643
703
|
status: 200,
|
|
644
|
-
headers: { 'Content-Type': 'application/json;charset=utf-8' },
|
|
704
|
+
headers: new Headers({ 'Content-Type': 'application/json;charset=utf-8' }),
|
|
645
705
|
});
|
|
646
706
|
context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
647
707
|
const providerResponse = await provider.get('/endpoint/123', {
|
|
@@ -669,7 +729,7 @@ describe('Provider', () => {
|
|
|
669
729
|
it('returns streamable response on streaming get calls', async (context) => {
|
|
670
730
|
const response = new Response(`IMAGINE A HUGE PAYLOAD`, {
|
|
671
731
|
status: 200,
|
|
672
|
-
headers: { 'Content-Type': 'video/mp4' },
|
|
732
|
+
headers: new Headers({ 'Content-Type': 'video/mp4' }),
|
|
673
733
|
});
|
|
674
734
|
context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
675
735
|
const providerResponse = await provider.streamingGet('/endpoint/123', {
|
|
@@ -683,7 +743,7 @@ describe('Provider', () => {
|
|
|
683
743
|
it('returns successfully on unexpected content-type response with no body', async (context) => {
|
|
684
744
|
const response = new Response(null, {
|
|
685
745
|
status: 201,
|
|
686
|
-
headers: { 'Content-Type': 'html/text' },
|
|
746
|
+
headers: new Headers({ 'Content-Type': 'html/text' }),
|
|
687
747
|
});
|
|
688
748
|
context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
689
749
|
const providerResponse = await provider.post('/endpoint/123', {}, {
|
|
@@ -693,13 +753,13 @@ describe('Provider', () => {
|
|
|
693
753
|
});
|
|
694
754
|
assert.ok(providerResponse);
|
|
695
755
|
assert.strictEqual(providerResponse.status, response.status);
|
|
696
|
-
assert.
|
|
756
|
+
assert.deepEqual(providerResponse.headers, Object.fromEntries(response.headers.entries()));
|
|
697
757
|
assert.strictEqual(providerResponse.body, undefined);
|
|
698
758
|
});
|
|
699
759
|
it('throws on invalid json response', async (context) => {
|
|
700
760
|
const response = new Response('{invalidJSON}', {
|
|
701
761
|
status: 200,
|
|
702
|
-
headers: { 'Content-Type': 'application/json' },
|
|
762
|
+
headers: new Headers({ 'Content-Type': 'application/json' }),
|
|
703
763
|
});
|
|
704
764
|
context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
705
765
|
let error;
|
|
@@ -719,7 +779,7 @@ describe('Provider', () => {
|
|
|
719
779
|
it('throws on unexpected content-type response', async (context) => {
|
|
720
780
|
const response = new Response('text', {
|
|
721
781
|
status: 200,
|
|
722
|
-
headers: { 'Content-Type': 'application/text' },
|
|
782
|
+
headers: new Headers({ 'Content-Type': 'application/text' }),
|
|
723
783
|
});
|
|
724
784
|
context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
725
785
|
let error;
|
package/package.json
CHANGED
package/src/helpers.ts
CHANGED
|
@@ -35,3 +35,30 @@ export function getApplicableFilters(context: { filters: Filter[] }, fields: Fie
|
|
|
35
35
|
|
|
36
36
|
return applicableFilters;
|
|
37
37
|
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Use this helper function to build the query params for a collection next page URL.
|
|
41
|
+
* It re-encodes the parsed filters and selects back into query-param form,
|
|
42
|
+
* so integrations can easily construct `nextPage` URLs.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* const params = buildCollectionQueryParams({ filters: context.filters, selects: context.selects });
|
|
46
|
+
* params.append('cursor', nextCursor);
|
|
47
|
+
* return { info: { nextPage: `/items?${params.toString()}` }, data: [...] };
|
|
48
|
+
*/
|
|
49
|
+
export function buildCollectionQueryParams(params?: { filters?: Filter[]; selects?: string[] }): URLSearchParams {
|
|
50
|
+
const result = new URLSearchParams();
|
|
51
|
+
|
|
52
|
+
if (params?.filters?.length) {
|
|
53
|
+
result.set(
|
|
54
|
+
'filter',
|
|
55
|
+
params.filters.map(f => `${f.field}${f.operator}${(f.values ?? []).map(encodeURIComponent).join('|')}`).join(','),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (params?.selects?.length) {
|
|
60
|
+
result.set('select', params.selects.map(encodeURIComponent).join(','));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return result;
|
|
64
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -14,7 +14,7 @@ export type { Secrets } from './middlewares/secrets.js';
|
|
|
14
14
|
export type { Credentials } from './middlewares/credentials.js';
|
|
15
15
|
export type { Filter } from './middlewares/filters.js';
|
|
16
16
|
export * as HttpErrors from './httpErrors.js';
|
|
17
|
-
export { getApplicableFilters } from './helpers.js';
|
|
17
|
+
export { buildCollectionQueryParams, getApplicableFilters } from './helpers.js';
|
|
18
18
|
export * from './resources/context.js';
|
|
19
19
|
export { type default as Logger, NULL_LOGGER } from './resources/logger.js';
|
|
20
20
|
/* c8 ignore stop */
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import https from 'https';
|
|
2
2
|
import FormData from 'form-data';
|
|
3
3
|
import * as stream from 'stream';
|
|
4
|
-
import { IncomingHttpHeaders } from 'http';
|
|
5
4
|
|
|
6
5
|
import { buildHttpError } from '../errors.js';
|
|
7
6
|
import * as HttpErrors from '../httpErrors.js';
|
|
@@ -57,7 +56,7 @@ export type RequestOptions = {
|
|
|
57
56
|
export type Response<T> = {
|
|
58
57
|
body: T;
|
|
59
58
|
status: number;
|
|
60
|
-
headers:
|
|
59
|
+
headers: Record<string, string>;
|
|
61
60
|
};
|
|
62
61
|
|
|
63
62
|
export type PreparedRequest = {
|
|
@@ -231,7 +230,15 @@ export class Provider {
|
|
|
231
230
|
if (body.error) {
|
|
232
231
|
reject(this.handleError(400, body.error.message, options));
|
|
233
232
|
}
|
|
234
|
-
resolve({
|
|
233
|
+
resolve({
|
|
234
|
+
status: 201,
|
|
235
|
+
headers: Object.fromEntries(
|
|
236
|
+
Object.entries(response.headers)
|
|
237
|
+
.filter((entry): entry is [string, string | string[]] => entry[1] !== undefined)
|
|
238
|
+
.map(([key, value]) => [key, Array.isArray(value) ? value.join(',') : value]),
|
|
239
|
+
),
|
|
240
|
+
body,
|
|
241
|
+
});
|
|
235
242
|
} catch (error) {
|
|
236
243
|
reject(this.handleError(500, `Failed to parse response body: "${error}"`, options));
|
|
237
244
|
}
|
|
@@ -346,7 +353,11 @@ export class Provider {
|
|
|
346
353
|
const body = responseBody ? JSON.parse(responseBody) : undefined;
|
|
347
354
|
safeResolve({
|
|
348
355
|
status: response.statusCode || 200,
|
|
349
|
-
headers:
|
|
356
|
+
headers: Object.fromEntries(
|
|
357
|
+
Object.entries(response.headers)
|
|
358
|
+
.filter((entry): entry is [string, string | string[]] => entry[1] !== undefined)
|
|
359
|
+
.map(([key, value]) => [key, Array.isArray(value) ? value.join(',') : value]),
|
|
360
|
+
),
|
|
350
361
|
body,
|
|
351
362
|
});
|
|
352
363
|
} catch (error) {
|
|
@@ -607,7 +618,11 @@ export class Provider {
|
|
|
607
618
|
throw this.handleError(response.status, textResult, options);
|
|
608
619
|
} else if (response.status === 204 || response.body === null) {
|
|
609
620
|
// No content: return without inspecting the body
|
|
610
|
-
return {
|
|
621
|
+
return {
|
|
622
|
+
status: response.status,
|
|
623
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
624
|
+
body: undefined as unknown as T,
|
|
625
|
+
};
|
|
611
626
|
}
|
|
612
627
|
|
|
613
628
|
const responseContentType = response.headers.get('content-type');
|
|
@@ -642,7 +657,7 @@ export class Provider {
|
|
|
642
657
|
throw this.handleError(500, 'Unsupported Content-Type', options);
|
|
643
658
|
}
|
|
644
659
|
|
|
645
|
-
return { status: response.status, headers: response.headers, body };
|
|
660
|
+
return { status: response.status, headers: Object.fromEntries(response.headers.entries()), body };
|
|
646
661
|
};
|
|
647
662
|
|
|
648
663
|
return this.rateLimiter ? this.rateLimiter(options, callToProvider) : callToProvider();
|
package/test/helpers.test.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { FieldValueTypes, OperatorTypes, Semantics } from '@unito/integration-ap
|
|
|
2
2
|
|
|
3
3
|
import assert from 'node:assert/strict';
|
|
4
4
|
import { describe, it } from 'node:test';
|
|
5
|
-
import { getApplicableFilters } from '../src/helpers.js';
|
|
5
|
+
import { buildCollectionQueryParams, getApplicableFilters } from '../src/helpers.js';
|
|
6
6
|
|
|
7
7
|
describe('Helpers', () => {
|
|
8
8
|
describe('getApplicableFilters', () => {
|
|
@@ -72,4 +72,113 @@ describe('Helpers', () => {
|
|
|
72
72
|
assert.deepEqual(actual, []);
|
|
73
73
|
});
|
|
74
74
|
});
|
|
75
|
+
|
|
76
|
+
describe('buildCollectionQueryParams', () => {
|
|
77
|
+
it('encodes a single filter with one value', () => {
|
|
78
|
+
const result = buildCollectionQueryParams({
|
|
79
|
+
filters: [{ field: 'status', operator: OperatorTypes.EQUAL, values: ['active'] }],
|
|
80
|
+
});
|
|
81
|
+
assert.equal(result.get('filter'), 'status=active');
|
|
82
|
+
assert.equal(result.get('select'), null);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('encodes filter values with special characters', () => {
|
|
86
|
+
const result = buildCollectionQueryParams({
|
|
87
|
+
filters: [{ field: 'status', operator: OperatorTypes.EQUAL, values: ['foo,bar'] }],
|
|
88
|
+
});
|
|
89
|
+
assert.equal(result.get('filter'), 'status=foo%2Cbar');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('encodes filter values containing pipe characters', () => {
|
|
93
|
+
const result = buildCollectionQueryParams({
|
|
94
|
+
filters: [{ field: 'tags', operator: OperatorTypes.EQUAL, values: ['a|b'] }],
|
|
95
|
+
});
|
|
96
|
+
assert.equal(result.get('filter'), 'tags=a%7Cb');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('joins multiple values for one filter with |', () => {
|
|
100
|
+
const result = buildCollectionQueryParams({
|
|
101
|
+
filters: [{ field: 'status', operator: OperatorTypes.EQUAL, values: ['active', 'pending'] }],
|
|
102
|
+
});
|
|
103
|
+
assert.equal(result.get('filter'), 'status=active|pending');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('joins multiple filters with ,', () => {
|
|
107
|
+
const result = buildCollectionQueryParams({
|
|
108
|
+
filters: [
|
|
109
|
+
{ field: 'status', operator: OperatorTypes.EQUAL, values: ['active'] },
|
|
110
|
+
{ field: 'name', operator: OperatorTypes.INCLUDE, values: ['john'] },
|
|
111
|
+
],
|
|
112
|
+
});
|
|
113
|
+
assert.equal(result.get('filter'), 'status=active,name*=john');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('handles IS_NULL operator (no values)', () => {
|
|
117
|
+
const result = buildCollectionQueryParams({
|
|
118
|
+
filters: [{ field: 'status', operator: OperatorTypes.IS_NULL, values: [] }],
|
|
119
|
+
});
|
|
120
|
+
assert.equal(result.get('filter'), 'status!!');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('handles IS_NULL operator with undefined values', () => {
|
|
124
|
+
const result = buildCollectionQueryParams({
|
|
125
|
+
filters: [{ field: 'status', operator: OperatorTypes.IS_NULL, values: undefined }],
|
|
126
|
+
});
|
|
127
|
+
assert.equal(result.get('filter'), 'status!!');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('handles IS_NOT_NULL operator (no values)', () => {
|
|
131
|
+
const result = buildCollectionQueryParams({
|
|
132
|
+
filters: [{ field: 'assignee', operator: OperatorTypes.IS_NOT_NULL, values: [] }],
|
|
133
|
+
});
|
|
134
|
+
assert.equal(result.get('filter'), 'assignee!');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('encodes selects', () => {
|
|
138
|
+
const result = buildCollectionQueryParams({
|
|
139
|
+
selects: ['name', 'dept.id'],
|
|
140
|
+
});
|
|
141
|
+
assert.equal(result.get('select'), 'name,dept.id');
|
|
142
|
+
assert.equal(result.get('filter'), null);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('encodes selects with special characters', () => {
|
|
146
|
+
const result = buildCollectionQueryParams({
|
|
147
|
+
selects: ['field with spaces'],
|
|
148
|
+
});
|
|
149
|
+
assert.equal(result.get('select'), 'field%20with%20spaces');
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('encodes both filters and selects', () => {
|
|
153
|
+
const result = buildCollectionQueryParams({
|
|
154
|
+
filters: [{ field: 'status', operator: OperatorTypes.EQUAL, values: ['active'] }],
|
|
155
|
+
selects: ['name', 'status'],
|
|
156
|
+
});
|
|
157
|
+
assert.equal(result.get('filter'), 'status=active');
|
|
158
|
+
assert.equal(result.get('select'), 'name,status');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('returns empty URLSearchParams when called with no arguments', () => {
|
|
162
|
+
const result = buildCollectionQueryParams();
|
|
163
|
+
assert.equal(result.toString(), '');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('omits filter key for empty filters array', () => {
|
|
167
|
+
const result = buildCollectionQueryParams({ filters: [] });
|
|
168
|
+
assert.equal(result.get('filter'), null);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('omits select key for empty selects array', () => {
|
|
172
|
+
const result = buildCollectionQueryParams({ selects: [] });
|
|
173
|
+
assert.equal(result.get('select'), null);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('can be extended with additional params (e.g. cursor)', () => {
|
|
177
|
+
const result = buildCollectionQueryParams({
|
|
178
|
+
selects: ['name'],
|
|
179
|
+
});
|
|
180
|
+
result.append('cursor', 'abc123');
|
|
181
|
+
assert.equal(result.toString(), 'select=name&cursor=abc123');
|
|
182
|
+
});
|
|
183
|
+
});
|
|
75
184
|
});
|