@unito/integration-sdk 0.1.0 → 0.1.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.
Files changed (69) hide show
  1. package/dist/src/errors.d.ts +3 -2
  2. package/dist/src/errors.js +11 -9
  3. package/dist/src/httpErrors.d.ts +3 -0
  4. package/dist/src/httpErrors.js +6 -1
  5. package/dist/src/index.d.ts +2 -1
  6. package/dist/src/index.js +2 -0
  7. package/dist/src/middlewares/credentials.d.ts +4 -1
  8. package/dist/src/resources/provider.d.ts +35 -12
  9. package/dist/src/resources/provider.js +59 -34
  10. package/dist/test/errors.test.js +7 -8
  11. package/dist/test/handler.test.js +1 -1
  12. package/dist/test/resources/provider.test.d.ts +1 -0
  13. package/dist/test/resources/provider.test.js +229 -0
  14. package/package.json +1 -3
  15. package/src/errors.ts +12 -9
  16. package/src/httpErrors.ts +7 -1
  17. package/src/index.ts +4 -0
  18. package/src/middlewares/credentials.ts +1 -1
  19. package/src/resources/provider.ts +89 -47
  20. package/test/errors.test.ts +7 -8
  21. package/test/handler.test.ts +1 -1
  22. package/test/resources/provider.test.ts +285 -0
  23. package/dist/src/api/index.d.ts +0 -2
  24. package/dist/src/api/index.d.ts.map +0 -1
  25. package/dist/src/api/index.js +0 -2
  26. package/dist/src/api/index.js.map +0 -1
  27. package/dist/src/app/errors/HTTPError.d.ts +0 -5
  28. package/dist/src/app/errors/HTTPError.d.ts.map +0 -1
  29. package/dist/src/app/errors/HTTPError.js +0 -8
  30. package/dist/src/app/errors/HTTPError.js.map +0 -1
  31. package/dist/src/app/errors/HTTPNotFoundError.d.ts +0 -5
  32. package/dist/src/app/errors/HTTPNotFoundError.d.ts.map +0 -1
  33. package/dist/src/app/errors/HTTPNotFoundError.js +0 -7
  34. package/dist/src/app/errors/HTTPNotFoundError.js.map +0 -1
  35. package/dist/src/app/errors/HTTPUnprocessableEntityError.d.ts +0 -5
  36. package/dist/src/app/errors/HTTPUnprocessableEntityError.d.ts.map +0 -1
  37. package/dist/src/app/errors/HTTPUnprocessableEntityError.js +0 -7
  38. package/dist/src/app/errors/HTTPUnprocessableEntityError.js.map +0 -1
  39. package/dist/src/app/errors/index.d.ts +0 -4
  40. package/dist/src/app/errors/index.d.ts.map +0 -1
  41. package/dist/src/app/errors/index.js +0 -4
  42. package/dist/src/app/errors/index.js.map +0 -1
  43. package/dist/src/app/index.d.ts +0 -6
  44. package/dist/src/app/index.d.ts.map +0 -1
  45. package/dist/src/app/index.js +0 -80
  46. package/dist/src/app/index.js.map +0 -1
  47. package/dist/src/app/integration.d.ts +0 -5
  48. package/dist/src/app/integration.d.ts.map +0 -1
  49. package/dist/src/app/integration.js +0 -86
  50. package/dist/src/app/integration.js.map +0 -1
  51. package/dist/src/app/itemNode.d.ts +0 -8
  52. package/dist/src/app/itemNode.d.ts.map +0 -1
  53. package/dist/src/app/itemNode.js +0 -13
  54. package/dist/src/app/itemNode.js.map +0 -1
  55. package/dist/src/app/middlewares/withCorrelationId.d.ts +0 -11
  56. package/dist/src/app/middlewares/withCorrelationId.d.ts.map +0 -1
  57. package/dist/src/app/middlewares/withCorrelationId.js +0 -8
  58. package/dist/src/app/middlewares/withCorrelationId.js.map +0 -1
  59. package/dist/src/app/middlewares/withLogger.d.ts +0 -12
  60. package/dist/src/app/middlewares/withLogger.d.ts.map +0 -1
  61. package/dist/src/app/middlewares/withLogger.js +0 -18
  62. package/dist/src/app/middlewares/withLogger.js.map +0 -1
  63. package/dist/src/index.d.ts.map +0 -1
  64. package/dist/src/index.js.map +0 -1
  65. package/dist/src/resources/cache.d.ts.map +0 -1
  66. package/dist/src/resources/cache.js.map +0 -1
  67. package/dist/src/resources/logger.d.ts.map +0 -1
  68. package/dist/src/resources/logger.js.map +0 -1
  69. package/dist/tsconfig.tsbuildinfo +0 -1
package/src/httpErrors.ts CHANGED
@@ -39,6 +39,12 @@ export class UnprocessableEntityError extends HttpError {
39
39
 
40
40
  export class RateLimitExceededError extends HttpError {
41
41
  constructor(message?: string) {
42
- super(message || 'Unprocessable Entity', 429);
42
+ super(message || 'Rate Limit Exceeded', 429);
43
+ }
44
+ }
45
+
46
+ export class WouldExceedLimitError extends HttpError {
47
+ constructor(message?: string) {
48
+ super(message || 'Would Exceed Limit', 429);
43
49
  }
44
50
  }
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /* c8 ignore start */
1
2
  export * as Api from '@unito/integration-api';
2
3
  export { default as Integration } from './integration.js';
3
4
  export * from './handler.js';
@@ -5,6 +6,9 @@ export {
5
6
  Provider,
6
7
  type Response as ProviderResponse,
7
8
  type RequestOptions as ProviderRequestOptions,
9
+ type RateLimiter,
8
10
  } from './resources/provider.js';
11
+ export type { Credentials } from './middlewares/credentials.js';
9
12
  export * as HttpErrors from './httpErrors.js';
10
13
  export * from './resources/context.js';
14
+ /* c8 ignore stop */
@@ -10,7 +10,7 @@ declare global {
10
10
  }
11
11
  }
12
12
 
13
- export type Credentials = Record<string, unknown>;
13
+ export type Credentials = { accessToken?: string; [keys: string]: unknown };
14
14
 
15
15
  const CREDENTIALS_HEADER = 'X-Unito-Credentials';
16
16
 
@@ -1,38 +1,59 @@
1
- import { handleErrorResponse as throwHttpError } from '../errors.js';
1
+ import { buildHttpError } from '../errors.js';
2
2
  import { Credentials } from '../middlewares/credentials.js';
3
3
 
4
+ /**
5
+ * RateLimiter is a wrapper function that you can provide to limit the rate of calls to the provider based on the
6
+ * caller's credentials.
7
+ *
8
+ * When necessary, the Provider's Response headers can be inspected to update the rate limit before being returned.
9
+ *
10
+ * NOTE: make sure to return one of the supported HttpErrors from the SDK, otherwise the error will be translated to a
11
+ * generic server (500) error.
12
+ *
13
+ * @param context - The credentials of the caller.
14
+ * @param targetFunction - The function to call the provider.
15
+ * @returns The response from the provider.
16
+ * @throws RateLimitExceededError when the rate limit is exceeded.
17
+ * @throws WouldExceedRateLimitError when the next call would exceed the rate limit.
18
+ * @throws HttpError when the provider returns an error.
19
+ */
20
+ export type RateLimiter = <T>(
21
+ context: { credentials: Credentials },
22
+ targetFunction: () => Promise<Response<T>>,
23
+ ) => Promise<Response<T>>;
24
+
4
25
  export interface RequestOptions {
5
26
  credentials: Credentials;
6
27
  queryParams?: { [key: string]: string };
7
- body?: Record<string, unknown>;
8
28
  additionnalheaders?: { [key: string]: string };
9
29
  }
10
30
 
11
31
  export interface Response<T> {
12
- data: T;
32
+ data: T | undefined;
33
+ status: number;
13
34
  headers: Headers;
14
35
  }
15
36
 
16
37
  export class Provider {
17
- protected prepareRequest:
18
- | ((context: { credentials: Credentials }) => {
19
- /**
20
- * The base URL of the provider.
21
- */
22
- url: string;
23
- /**
24
- * The additional headers to add to the request.
25
- */
26
- headers: Record<string, string>;
27
- })
28
- | undefined = undefined;
29
-
30
- constructor(options: { prepareRequest: typeof Provider.prototype.prepareRequest }) {
38
+ protected rateLimiter: RateLimiter | undefined = undefined;
39
+ protected prepareRequest: (context: { credentials: Credentials }) => {
40
+ url: string;
41
+ headers: Record<string, string>;
42
+ };
43
+
44
+ /**
45
+ * Initialize a Provider with the given options.
46
+ *
47
+ * @property prepareRequest - function to define the Provider's base URL and specific headers to add to the request.
48
+ * @property rateLimiter - function to limit the rate of calls to the provider based on the caller's credentials.
49
+ */
50
+ constructor(options: { prepareRequest: typeof Provider.prototype.prepareRequest; rateLimiter?: RateLimiter }) {
31
51
  this.prepareRequest = options.prepareRequest;
52
+ this.rateLimiter = options.rateLimiter;
32
53
  }
33
54
 
34
55
  public async get<T>(endpoint: string, options: RequestOptions): Promise<Response<T>> {
35
- return this.fetchWrapper<T>(endpoint, {
56
+ return this.fetchWrapper<T>(endpoint, null, {
36
57
  ...options,
37
58
  method: 'GET',
38
59
  defaultHeaders: {
@@ -42,8 +63,8 @@ export class Provider {
42
63
  });
43
64
  }
44
65
 
45
- public async post<T>(endpoint: string, options: RequestOptions): Promise<Response<T>> {
46
- return this.fetchWrapper<T>(endpoint, {
66
+ public async post<T>(endpoint: string, body: Record<string, unknown>, options: RequestOptions): Promise<Response<T>> {
67
+ return this.fetchWrapper<T>(endpoint, body, {
47
68
  ...options,
48
69
  method: 'POST',
49
70
  defaultHeaders: {
@@ -53,8 +74,23 @@ export class Provider {
53
74
  });
54
75
  }
55
76
 
56
- public async patch<T>(endpoint: string, options: RequestOptions): Promise<Response<T>> {
57
- return this.fetchWrapper<T>(endpoint, {
77
+ public async put<T>(endpoint: string, body: Record<string, unknown>, options: RequestOptions): Promise<Response<T>> {
78
+ return this.fetchWrapper<T>(endpoint, body, {
79
+ ...options,
80
+ method: 'PUT',
81
+ defaultHeaders: {
82
+ 'Content-Type': 'application/x-www-form-urlencoded',
83
+ Accept: 'application/json',
84
+ },
85
+ });
86
+ }
87
+
88
+ public async patch<T>(
89
+ endpoint: string,
90
+ body: Record<string, unknown>,
91
+ options: RequestOptions,
92
+ ): Promise<Response<T>> {
93
+ return this.fetchWrapper<T>(endpoint, body, {
58
94
  ...options,
59
95
  method: 'PATCH',
60
96
  defaultHeaders: {
@@ -64,14 +100,22 @@ export class Provider {
64
100
  });
65
101
  }
66
102
 
103
+ public async delete<T>(endpoint: string, options: RequestOptions): Promise<Response<T>> {
104
+ return this.fetchWrapper<T>(endpoint, null, {
105
+ ...options,
106
+ method: 'DELETE',
107
+ defaultHeaders: {
108
+ 'Content-Type': 'application/x-www-form-urlencoded',
109
+ Accept: 'application/json',
110
+ },
111
+ });
112
+ }
113
+
67
114
  private async fetchWrapper<T>(
68
115
  endpoint: string,
116
+ body: Record<string, unknown> | null,
69
117
  options: RequestOptions & { defaultHeaders: { 'Content-Type': string; Accept: string }; method: string },
70
118
  ): Promise<Response<T>> {
71
- if (!this.prepareRequest) {
72
- throw new Error('Provider not initialized');
73
- }
74
-
75
119
  const { url: providerUrl, headers: providerHeaders } = this.prepareRequest({ credentials: options.credentials });
76
120
 
77
121
  let absoluteUrl = [providerUrl, endpoint.charAt(0) === '/' ? endpoint.substring(1) : endpoint].join('/');
@@ -82,39 +126,37 @@ export class Provider {
82
126
 
83
127
  const headers = { ...options.defaultHeaders, ...providerHeaders, ...options.additionnalheaders };
84
128
 
85
- let body: string | null = null;
129
+ let stringifiedBody: string | null = null;
86
130
 
87
- if (options.body) {
131
+ if (body) {
88
132
  if (headers['Content-Type'] === 'application/x-www-form-urlencoded') {
89
- body = new URLSearchParams(options.body as Record<string, string>).toString(); // this doesn't support objects!
133
+ stringifiedBody = new URLSearchParams(body as Record<string, string>).toString();
90
134
  } else if (headers['Content-Type'] === 'application/json') {
91
- body = JSON.stringify(options.body);
135
+ stringifiedBody = JSON.stringify(body);
92
136
  }
93
137
  }
94
138
 
95
- const callToProvider = async () =>
96
- await fetch(absoluteUrl, {
139
+ const callToProvider = async (): Promise<Response<T>> => {
140
+ const response = await fetch(absoluteUrl, {
97
141
  method: options.method,
98
142
  headers,
99
- body: body,
143
+ body: stringifiedBody,
100
144
  });
101
145
 
102
- // TODO: add back rate limiter
103
- const response = await callToProvider();
104
-
105
- if (response.status >= 400) {
106
- const textResult = await response.text();
107
- throwHttpError(response.status, textResult);
108
- }
146
+ if (response.status >= 400) {
147
+ const textResult = await response.text();
148
+ throw buildHttpError(response.status, textResult);
149
+ }
109
150
 
110
- let data;
151
+ try {
152
+ const data: T | undefined = response.body ? await response.json() : undefined;
111
153
 
112
- try {
113
- data = await response.json();
114
- } catch {
115
- throwHttpError(400, 'Invalid JSON response');
116
- }
154
+ return { status: response.status, headers: response.headers, data };
155
+ } catch {
156
+ throw buildHttpError(400, 'Invalid JSON response');
157
+ }
158
+ };
117
159
 
118
- return { headers: response.headers, data };
160
+ return this.rateLimiter ? this.rateLimiter(options, callToProvider) : callToProvider();
119
161
  }
120
162
  }
@@ -5,13 +5,12 @@ import * as httpErrors from '../src/httpErrors.js';
5
5
 
6
6
  describe('handleErrorResponse', () => {
7
7
  it('returns correct httpError given status code', () => {
8
- assert.throws(() => errors.handleErrorResponse(400, 'bad request'), httpErrors.BadRequestError);
9
- assert.throws(() => errors.handleErrorResponse(401, 'unauthorized'), httpErrors.UnauthorizedError);
10
- assert.throws(() => errors.handleErrorResponse(403, 'forbidden'), httpErrors.UnauthorizedError);
11
- assert.throws(() => errors.handleErrorResponse(404, 'not found'), httpErrors.NotFoundError);
12
- assert.throws(() => errors.handleErrorResponse(408, 'timeout'), httpErrors.TimeoutError);
13
- assert.throws(() => errors.handleErrorResponse(422, 'unprocessable entity'), httpErrors.UnprocessableEntityError);
14
- assert.throws(() => errors.handleErrorResponse(429, 'rate limit exceeded'), httpErrors.RateLimitExceededError);
15
- assert.throws(() => errors.handleErrorResponse(500, 'internal server error'), httpErrors.HttpError);
8
+ assert.ok(errors.buildHttpError(401, 'unauthorized') instanceof httpErrors.UnauthorizedError);
9
+ assert.ok(errors.buildHttpError(403, 'forbidden') instanceof httpErrors.UnauthorizedError);
10
+ assert.ok(errors.buildHttpError(404, 'not found') instanceof httpErrors.NotFoundError);
11
+ assert.ok(errors.buildHttpError(408, 'timeout') instanceof httpErrors.TimeoutError);
12
+ assert.ok(errors.buildHttpError(422, 'unprocessable entity') instanceof httpErrors.UnprocessableEntityError);
13
+ assert.ok(errors.buildHttpError(429, 'rate limit exceeded') instanceof httpErrors.RateLimitExceededError);
14
+ assert.ok(errors.buildHttpError(500, 'internal server error') instanceof httpErrors.HttpError);
16
15
  });
17
16
  });
@@ -18,7 +18,7 @@ describe('Handler', () => {
18
18
  it('returns a Handler', () => {
19
19
  const itemHandler = new Handler('/', {});
20
20
 
21
- assert.equal(itemHandler instanceof Handler, true);
21
+ assert.ok(itemHandler instanceof Handler);
22
22
  });
23
23
 
24
24
  it('validates path', () => {
@@ -0,0 +1,285 @@
1
+ import assert from 'node:assert/strict';
2
+ import { describe, it } from 'node:test';
3
+
4
+ import { Provider } from '../../src/resources/provider.js';
5
+
6
+ describe('Provider', () => {
7
+ const provider = new Provider({
8
+ prepareRequest: requestOptions => {
9
+ return {
10
+ url: `www.${requestOptions.credentials.domain ?? 'myApi.com'}`,
11
+ headers: {
12
+ 'X-Custom-Provider-Header': 'value',
13
+ 'X-Provider-Credential-Header': requestOptions.credentials.apiKey as string,
14
+ },
15
+ };
16
+ },
17
+ });
18
+
19
+ it('get', async context => {
20
+ const response = new Response('{"data": "value"}', {
21
+ status: 200,
22
+ headers: { 'Content-Type': 'application/json' },
23
+ });
24
+
25
+ const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
26
+
27
+ const actualResponse = await provider.get('/endpoint', {
28
+ credentials: { apiKey: 'apikey#1111' },
29
+ additionnalheaders: { 'X-Additional-Header': 'value1' },
30
+ });
31
+
32
+ assert.equal(fetchMock.mock.calls.length, 1);
33
+ assert.deepEqual(fetchMock.mock.calls[0]?.arguments, [
34
+ 'www.myApi.com/endpoint',
35
+ {
36
+ method: 'GET',
37
+ body: null,
38
+ headers: {
39
+ 'Content-Type': 'application/json',
40
+ Accept: 'application/json',
41
+ 'X-Custom-Provider-Header': 'value',
42
+ 'X-Provider-Credential-Header': 'apikey#1111',
43
+ 'X-Additional-Header': 'value1',
44
+ },
45
+ },
46
+ ]);
47
+ assert.deepEqual(actualResponse, { status: 200, headers: response.headers, data: { data: 'value' } });
48
+ });
49
+
50
+ it('post with url encoded body', async context => {
51
+ const response = new Response('{"data": "value"}', {
52
+ status: 201,
53
+ headers: { 'Content-Type': 'application/json' },
54
+ });
55
+
56
+ const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
57
+
58
+ const actualResponse = await provider.post(
59
+ '/endpoint',
60
+ {
61
+ data: 'createdItemInfo',
62
+ },
63
+ {
64
+ credentials: { apiKey: 'apikey#1111' },
65
+ additionnalheaders: { 'X-Additional-Header': 'value1' },
66
+ },
67
+ );
68
+
69
+ assert.equal(fetchMock.mock.calls.length, 1);
70
+ assert.deepEqual(fetchMock.mock.calls[0]?.arguments, [
71
+ 'www.myApi.com/endpoint',
72
+ {
73
+ method: 'POST',
74
+ body: 'data=createdItemInfo',
75
+ headers: {
76
+ 'Content-Type': 'application/x-www-form-urlencoded',
77
+ Accept: 'application/json',
78
+ 'X-Custom-Provider-Header': 'value',
79
+ 'X-Provider-Credential-Header': 'apikey#1111',
80
+ 'X-Additional-Header': 'value1',
81
+ },
82
+ },
83
+ ]);
84
+ assert.deepEqual(actualResponse, { status: 201, headers: response.headers, data: { data: 'value' } });
85
+ });
86
+
87
+ it('put with json body', async context => {
88
+ const response = new Response('{"data": "value"}', {
89
+ status: 201,
90
+ headers: { 'Content-Type': 'application/json' },
91
+ });
92
+
93
+ const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
94
+
95
+ // Removing leading '/' on endpoint to make sure we support both cases
96
+ const actualResponse = await provider.put(
97
+ 'endpoint/123',
98
+ {
99
+ data: 'updatedItemInfo',
100
+ },
101
+ {
102
+ credentials: { apiKey: 'apikey#1111' },
103
+ additionnalheaders: { 'X-Additional-Header': 'value1', 'Content-Type': 'application/json' },
104
+ },
105
+ );
106
+
107
+ assert.equal(fetchMock.mock.calls.length, 1);
108
+ assert.deepEqual(fetchMock.mock.calls[0]?.arguments, [
109
+ 'www.myApi.com/endpoint/123',
110
+ {
111
+ method: 'PUT',
112
+ body: JSON.stringify({ data: 'updatedItemInfo' }),
113
+ headers: {
114
+ 'Content-Type': 'application/json',
115
+ Accept: 'application/json',
116
+ 'X-Custom-Provider-Header': 'value',
117
+ 'X-Provider-Credential-Header': 'apikey#1111',
118
+ 'X-Additional-Header': 'value1',
119
+ },
120
+ },
121
+ ]);
122
+ assert.deepEqual(actualResponse, { status: 201, headers: response.headers, data: { data: 'value' } });
123
+ });
124
+
125
+ it('patch with query params', async context => {
126
+ const response = new Response('{"data": "value"}', {
127
+ status: 201,
128
+ headers: { 'Content-Type': 'application/json' },
129
+ });
130
+
131
+ const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
132
+
133
+ const actualResponse = await provider.patch(
134
+ '/endpoint/123',
135
+ {
136
+ data: 'updatedItemInfo',
137
+ },
138
+ {
139
+ credentials: { apiKey: 'apikey#1111' },
140
+ queryParams: { param1: 'value1', param2: 'value2' },
141
+ additionnalheaders: { 'X-Additional-Header': 'value1', 'Content-Type': 'application/json' },
142
+ },
143
+ );
144
+
145
+ assert.equal(fetchMock.mock.calls.length, 1);
146
+ assert.deepEqual(fetchMock.mock.calls[0]?.arguments, [
147
+ 'www.myApi.com/endpoint/123?param1=value1&param2=value2',
148
+ {
149
+ method: 'PATCH',
150
+ body: JSON.stringify({ data: 'updatedItemInfo' }),
151
+ headers: {
152
+ 'Content-Type': 'application/json',
153
+ Accept: 'application/json',
154
+ 'X-Custom-Provider-Header': 'value',
155
+ 'X-Provider-Credential-Header': 'apikey#1111',
156
+ 'X-Additional-Header': 'value1',
157
+ },
158
+ },
159
+ ]);
160
+ assert.deepEqual(actualResponse, { status: 201, headers: response.headers, data: { data: 'value' } });
161
+ });
162
+
163
+ it('delete', async context => {
164
+ const response = new Response(undefined, {
165
+ status: 204,
166
+ headers: { 'Content-Type': 'application/json' },
167
+ });
168
+
169
+ const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
170
+
171
+ const actualResponse = await provider.delete('/endpoint/123', {
172
+ credentials: { apiKey: 'apikey#1111' },
173
+ additionnalheaders: { 'X-Additional-Header': 'value1', 'Content-Type': 'application/json' },
174
+ });
175
+
176
+ assert.equal(fetchMock.mock.calls.length, 1);
177
+ assert.deepEqual(fetchMock.mock.calls[0]?.arguments, [
178
+ 'www.myApi.com/endpoint/123',
179
+ {
180
+ method: 'DELETE',
181
+ body: null,
182
+ headers: {
183
+ 'Content-Type': 'application/json',
184
+ Accept: 'application/json',
185
+ 'X-Custom-Provider-Header': 'value',
186
+ 'X-Provider-Credential-Header': 'apikey#1111',
187
+ 'X-Additional-Header': 'value1',
188
+ },
189
+ },
190
+ ]);
191
+ assert.deepEqual(actualResponse, { status: 204, headers: response.headers, data: undefined });
192
+ });
193
+
194
+ it('uses rate limiter if provided', async context => {
195
+ const mockRateLimiter = context.mock.fn((_context, request) => Promise.resolve(request()));
196
+
197
+ const rateLimitedProvider = new Provider({
198
+ prepareRequest: requestOptions => {
199
+ return {
200
+ url: `www.${requestOptions.credentials.domain ?? 'myApi.com'}`,
201
+ headers: {
202
+ 'X-Custom-Provider-Header': 'value',
203
+ 'X-Provider-Credential-Header': requestOptions.credentials.apiKey as string,
204
+ },
205
+ };
206
+ },
207
+ rateLimiter: mockRateLimiter,
208
+ });
209
+
210
+ const response = new Response(undefined, {
211
+ status: 204,
212
+ headers: { 'Content-Type': 'application/json' },
213
+ });
214
+
215
+ const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
216
+
217
+ const options = {
218
+ credentials: { apiKey: 'apikey#1111' },
219
+ additionnalheaders: { 'X-Additional-Header': 'value1', 'Content-Type': 'application/json' },
220
+ };
221
+
222
+ const actualResponse = await rateLimitedProvider.delete('/endpoint/123', options);
223
+
224
+ assert.equal(mockRateLimiter.mock.calls.length, 1);
225
+ assert.deepEqual(mockRateLimiter.mock.calls[0]?.arguments[0]?.credentials, options.credentials);
226
+ assert.equal(fetchMock.mock.calls.length, 1);
227
+ assert.deepEqual(fetchMock.mock.calls[0]?.arguments, [
228
+ 'www.myApi.com/endpoint/123',
229
+ {
230
+ method: 'DELETE',
231
+ body: null,
232
+ headers: {
233
+ 'Content-Type': 'application/json',
234
+ Accept: 'application/json',
235
+ 'X-Custom-Provider-Header': 'value',
236
+ 'X-Provider-Credential-Header': 'apikey#1111',
237
+ 'X-Additional-Header': 'value1',
238
+ },
239
+ },
240
+ ]);
241
+ assert.deepEqual(actualResponse, { status: 204, headers: response.headers, data: undefined });
242
+ });
243
+
244
+ it('throws on invalid json response', async context => {
245
+ const response = new Response('{invalidJSON}', {
246
+ status: 200,
247
+ });
248
+
249
+ context.mock.method(global, 'fetch', () => Promise.resolve(response));
250
+
251
+ assert.rejects(() =>
252
+ provider.get('/endpoint/123', {
253
+ credentials: { apiKey: 'apikey#1111' },
254
+ }),
255
+ );
256
+ });
257
+
258
+ it('throws on status 400', async context => {
259
+ const response = new Response(undefined, {
260
+ status: 400,
261
+ });
262
+
263
+ context.mock.method(global, 'fetch', () => Promise.resolve(response));
264
+
265
+ assert.rejects(() =>
266
+ provider.get('/endpoint/123', {
267
+ credentials: { apiKey: 'apikey#1111' },
268
+ }),
269
+ );
270
+ });
271
+
272
+ it('throws on status 429', async context => {
273
+ const response = new Response(undefined, {
274
+ status: 429,
275
+ });
276
+
277
+ context.mock.method(global, 'fetch', () => Promise.resolve(response));
278
+
279
+ assert.rejects(() =>
280
+ provider.get('/endpoint/123', {
281
+ credentials: { apiKey: 'apikey#1111' },
282
+ }),
283
+ );
284
+ });
285
+ });
@@ -1,2 +0,0 @@
1
- export * from '@unito/integration-api';
2
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/api/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC"}
@@ -1,2 +0,0 @@
1
- export * from '@unito/integration-api';
2
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/api/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC"}
@@ -1,5 +0,0 @@
1
- export declare class HTTPError extends Error {
2
- readonly status: number;
3
- constructor(message: string, status: number);
4
- }
5
- //# sourceMappingURL=HTTPError.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"HTTPError.d.ts","sourceRoot":"","sources":["../../../../src/app/errors/HTTPError.ts"],"names":[],"mappings":"AAAA,qBAAa,SAAU,SAAQ,KAAK;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAO;gBAElB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAI5C"}
@@ -1,8 +0,0 @@
1
- export class HTTPError extends Error {
2
- status = 500;
3
- constructor(message, status) {
4
- super(message);
5
- this.status = status;
6
- }
7
- }
8
- //# sourceMappingURL=HTTPError.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"HTTPError.js","sourceRoot":"","sources":["../../../../src/app/errors/HTTPError.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,SAAU,SAAQ,KAAK;IACzB,MAAM,GAAW,GAAG,CAAC;IAE9B,YAAY,OAAe,EAAE,MAAc;QACzC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF"}
@@ -1,5 +0,0 @@
1
- import { HTTPError } from './HTTPError.js';
2
- export declare class HTTPNotFoundError extends HTTPError {
3
- constructor(message?: string);
4
- }
5
- //# sourceMappingURL=HTTPNotFoundError.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"HTTPNotFoundError.d.ts","sourceRoot":"","sources":["../../../../src/app/errors/HTTPNotFoundError.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,qBAAa,iBAAkB,SAAQ,SAAS;gBAClC,OAAO,CAAC,EAAE,MAAM;CAG7B"}
@@ -1,7 +0,0 @@
1
- import { HTTPError } from './HTTPError.js';
2
- export class HTTPNotFoundError extends HTTPError {
3
- constructor(message) {
4
- super(message || 'Not found', 404);
5
- }
6
- }
7
- //# sourceMappingURL=HTTPNotFoundError.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"HTTPNotFoundError.js","sourceRoot":"","sources":["../../../../src/app/errors/HTTPNotFoundError.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,MAAM,OAAO,iBAAkB,SAAQ,SAAS;IAC9C,YAAY,OAAgB;QAC1B,KAAK,CAAC,OAAO,IAAI,WAAW,EAAE,GAAG,CAAC,CAAC;IACrC,CAAC;CACF"}
@@ -1,5 +0,0 @@
1
- import { HTTPError } from './HTTPError.js';
2
- export declare class HTTPUnprocessableEntityError extends HTTPError {
3
- constructor(message?: string);
4
- }
5
- //# sourceMappingURL=HTTPUnprocessableEntityError.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"HTTPUnprocessableEntityError.d.ts","sourceRoot":"","sources":["../../../../src/app/errors/HTTPUnprocessableEntityError.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,qBAAa,4BAA6B,SAAQ,SAAS;gBAC7C,OAAO,CAAC,EAAE,MAAM;CAG7B"}
@@ -1,7 +0,0 @@
1
- import { HTTPError } from './HTTPError.js';
2
- export class HTTPUnprocessableEntityError extends HTTPError {
3
- constructor(message) {
4
- super(message || 'Unprocessable Entity', 422);
5
- }
6
- }
7
- //# sourceMappingURL=HTTPUnprocessableEntityError.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"HTTPUnprocessableEntityError.js","sourceRoot":"","sources":["../../../../src/app/errors/HTTPUnprocessableEntityError.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,MAAM,OAAO,4BAA6B,SAAQ,SAAS;IACzD,YAAY,OAAgB;QAC1B,KAAK,CAAC,OAAO,IAAI,sBAAsB,EAAE,GAAG,CAAC,CAAC;IAChD,CAAC;CACF"}
@@ -1,4 +0,0 @@
1
- export { HTTPError } from './HTTPError.js';
2
- export { HTTPNotFoundError } from './HTTPNotFoundError.js';
3
- export { HTTPUnprocessableEntityError } from './HTTPUnprocessableEntityError.js';
4
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/app/errors/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAC"}
@@ -1,4 +0,0 @@
1
- export { HTTPError } from './HTTPError.js';
2
- export { HTTPNotFoundError } from './HTTPNotFoundError.js';
3
- export { HTTPUnprocessableEntityError } from './HTTPUnprocessableEntityError.js';
4
- //# sourceMappingURL=index.js.map