@unito/integration-sdk 0.1.5 → 0.1.7

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.
@@ -16,7 +16,7 @@ function assertValidConfiguration(path, pathWithIdentifier, handlers) {
16
16
  const hasIndividualHandlers = individualHandlers.some(handler => handler in handlers);
17
17
  const hasCollectionHandlers = collectionHandlers.some(handler => handler in handlers);
18
18
  if (hasIndividualHandlers && hasCollectionHandlers) {
19
- throw new InvalidHandler(`The provided path '${path}' doesn't differentiate between individual and collection level operation, so you cannot define both. `);
19
+ throw new InvalidHandler(`The provided path '${path}' doesn't differentiate between individual and collection level operation, so you cannot define both.`);
20
20
  }
21
21
  }
22
22
  }
@@ -83,7 +83,7 @@ export class Handler {
83
83
  console.debug(`\x1b[33mMounting handler at path ${this.pathWithIdentifier}`);
84
84
  if (this.handlers.getCollection) {
85
85
  const handler = this.handlers.getCollection;
86
- console.debug(` Enabling GET ${this.path}`);
86
+ console.debug(` Enabling getCollection at GET ${this.path}`);
87
87
  router.get(this.path, async (req, res) => {
88
88
  if (!res.locals.credentials) {
89
89
  throw new UnauthorizedError();
@@ -101,7 +101,7 @@ export class Handler {
101
101
  }
102
102
  if (this.handlers.createItem) {
103
103
  const handler = this.handlers.createItem;
104
- console.debug(` Enabling POST ${this.path}`);
104
+ console.debug(` Enabling createItem at POST ${this.path}`);
105
105
  router.post(this.path, async (req, res) => {
106
106
  if (!res.locals.credentials) {
107
107
  throw new UnauthorizedError();
@@ -119,7 +119,7 @@ export class Handler {
119
119
  }
120
120
  if (this.handlers.getItem) {
121
121
  const handler = this.handlers.getItem;
122
- console.debug(` Enabling GET ${this.pathWithIdentifier}`);
122
+ console.debug(` Enabling getItem at GET ${this.pathWithIdentifier}`);
123
123
  router.get(this.pathWithIdentifier, async (req, res) => {
124
124
  if (!res.locals.credentials) {
125
125
  throw new UnauthorizedError();
@@ -135,7 +135,7 @@ export class Handler {
135
135
  }
136
136
  if (this.handlers.updateItem) {
137
137
  const handler = this.handlers.updateItem;
138
- console.debug(` Enabling PATCH ${this.pathWithIdentifier}`);
138
+ console.debug(` Enabling updateItem at PATCH ${this.pathWithIdentifier}`);
139
139
  router.patch(this.pathWithIdentifier, async (req, res) => {
140
140
  if (!res.locals.credentials) {
141
141
  throw new UnauthorizedError();
@@ -153,7 +153,7 @@ export class Handler {
153
153
  }
154
154
  if (this.handlers.deleteItem) {
155
155
  const handler = this.handlers.deleteItem;
156
- console.debug(` Enabling DELETE ${this.pathWithIdentifier}`);
156
+ console.debug(` Enabling deleteItem at DELETE ${this.pathWithIdentifier}`);
157
157
  router.delete(this.pathWithIdentifier, async (req, res) => {
158
158
  if (!res.locals.credentials) {
159
159
  throw new UnauthorizedError();
@@ -169,7 +169,7 @@ export class Handler {
169
169
  }
170
170
  if (this.handlers.getCredentialAccount) {
171
171
  const handler = this.handlers.getCredentialAccount;
172
- console.debug(` Enabling GET ${this.pathWithIdentifier}`);
172
+ console.debug(` Enabling getCredentialAccount at GET ${this.pathWithIdentifier}`);
173
173
  router.get(this.pathWithIdentifier, async (req, res) => {
174
174
  if (!res.locals.credentials) {
175
175
  throw new UnauthorizedError();
@@ -185,7 +185,7 @@ export class Handler {
185
185
  }
186
186
  if (this.handlers.acknowledgeWebhooks) {
187
187
  const handler = this.handlers.acknowledgeWebhooks;
188
- console.debug(` Enabling POST ${this.pathWithIdentifier}`);
188
+ console.debug(` Enabling acknowledgeWebhooks at POST ${this.pathWithIdentifier}`);
189
189
  router.post(this.pathWithIdentifier, async (req, res) => {
190
190
  assertWebhookParseRequestPayload(req.body);
191
191
  const response = await handler({
@@ -199,7 +199,7 @@ export class Handler {
199
199
  }
200
200
  if (this.handlers.parseWebhooks) {
201
201
  const handler = this.handlers.parseWebhooks;
202
- console.debug(` Enabling POST ${this.pathWithIdentifier}`);
202
+ console.debug(` Enabling parseWebhooks at POST ${this.pathWithIdentifier}`);
203
203
  router.post(this.pathWithIdentifier, async (req, res) => {
204
204
  assertWebhookParseRequestPayload(req.body);
205
205
  const response = await handler({
@@ -213,7 +213,7 @@ export class Handler {
213
213
  }
214
214
  if (this.handlers.updateWebhookSubscriptions) {
215
215
  const handler = this.handlers.updateWebhookSubscriptions;
216
- console.debug(` Enabling PUT ${this.pathWithIdentifier}`);
216
+ console.debug(` Enabling updateWebhookSubscriptions at PUT ${this.pathWithIdentifier}`);
217
217
  router.put(this.pathWithIdentifier, async (req, res) => {
218
218
  if (!res.locals.credentials) {
219
219
  throw new UnauthorizedError();
@@ -1,9 +1,14 @@
1
1
  import { HandlersInput } from './handler.js';
2
+ type Options = {
3
+ port?: number;
4
+ };
2
5
  export default class Integration {
3
6
  private handlers;
4
7
  private instance;
5
8
  private cache;
6
- constructor();
9
+ private port;
10
+ constructor(options?: Options);
7
11
  addHandler(path: string, handlers: HandlersInput): void;
8
12
  start(): void;
9
13
  }
14
+ export {};
@@ -18,7 +18,9 @@ export default class Integration {
18
18
  handlers;
19
19
  instance = undefined;
20
20
  cache = undefined;
21
- constructor() {
21
+ port;
22
+ constructor(options = {}) {
23
+ this.port = options.port || 9200;
22
24
  this.handlers = [];
23
25
  }
24
26
  addHandler(path, handlers) {
@@ -85,7 +87,8 @@ export default class Integration {
85
87
  app.use(errorsMiddleware);
86
88
  // Must be the last handler.
87
89
  app.use(notFoundMiddleware);
88
- this.instance = app.listen(process.env.PORT || 9200, () => console.info(`Server started on port ${process.env.PORT || 9200}.`));
90
+ // Start the server.
91
+ this.instance = app.listen(this.port, () => console.info(`Server started on port ${this.port}.`));
89
92
  // Trap exit signals.
90
93
  ['SIGTERM', 'SIGINT', 'SIGUSR2'].forEach(signalType => {
91
94
  process.once(signalType, async () => {
@@ -1,4 +1,5 @@
1
1
  import { Credentials } from '../middlewares/credentials.js';
2
+ import Logger from '../resources/logger.js';
2
3
  /**
3
4
  * RateLimiter is a wrapper function that you can provide to limit the rate of calls to the provider based on the
4
5
  * caller's credentials.
@@ -17,9 +18,11 @@ import { Credentials } from '../middlewares/credentials.js';
17
18
  */
18
19
  export type RateLimiter = <T>(context: {
19
20
  credentials: Credentials;
21
+ logger: Logger;
20
22
  }, targetFunction: () => Promise<Response<T>>) => Promise<Response<T>>;
21
23
  export interface RequestOptions {
22
24
  credentials: Credentials;
25
+ logger: Logger;
23
26
  queryParams?: {
24
27
  [key: string]: string;
25
28
  };
@@ -28,7 +31,7 @@ export interface RequestOptions {
28
31
  };
29
32
  }
30
33
  export interface Response<T> {
31
- data: T;
34
+ body: T;
32
35
  status: number;
33
36
  headers: Headers;
34
37
  }
@@ -36,6 +39,7 @@ export declare class Provider {
36
39
  protected rateLimiter: RateLimiter | undefined;
37
40
  protected prepareRequest: (context: {
38
41
  credentials: Credentials;
42
+ logger: Logger;
39
43
  }) => {
40
44
  url: string;
41
45
  headers: Record<string, string>;
@@ -63,7 +63,7 @@ export class Provider {
63
63
  });
64
64
  }
65
65
  async fetchWrapper(endpoint, body, options) {
66
- const { url: providerUrl, headers: providerHeaders } = this.prepareRequest({ credentials: options.credentials });
66
+ const { url: providerUrl, headers: providerHeaders } = this.prepareRequest(options);
67
67
  let absoluteUrl = [providerUrl, endpoint.charAt(0) === '/' ? endpoint.substring(1) : endpoint].join('/');
68
68
  if (options.queryParams) {
69
69
  absoluteUrl = `${absoluteUrl}?${new URLSearchParams(options.queryParams)}`;
@@ -89,8 +89,8 @@ export class Provider {
89
89
  throw buildHttpError(response.status, textResult);
90
90
  }
91
91
  try {
92
- const data = response.body ? await response.json() : undefined;
93
- return { status: response.status, headers: response.headers, data };
92
+ const body = response.body ? await response.json() : undefined;
93
+ return { status: response.status, headers: response.headers, body };
94
94
  }
95
95
  catch {
96
96
  throw buildHttpError(400, 'Invalid JSON response');
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,26 @@
1
+ import assert from 'node:assert/strict';
2
+ import { describe, it } from 'node:test';
3
+ import Integration from '../src/integration.js';
4
+ describe('Integration', () => {
5
+ describe('constructor', () => {
6
+ it('defaults to port 9200', () => {
7
+ const integration = new Integration();
8
+ assert.equal(integration['port'], 9200);
9
+ });
10
+ it('accepts an optional port', () => {
11
+ const integration = new Integration({ port: 1234 });
12
+ assert.equal(integration['port'], 1234);
13
+ });
14
+ });
15
+ describe('addHandler', () => {
16
+ it('works', () => {
17
+ const integration = new Integration();
18
+ integration.addHandler('/', {});
19
+ const handler = integration['handlers'][0];
20
+ assert.ok(handler);
21
+ assert.equal(handler['path'], '/');
22
+ assert.equal(handler['pathWithIdentifier'], '/');
23
+ assert.deepEqual(handler['handlers'], {});
24
+ });
25
+ });
26
+ });
@@ -15,6 +15,14 @@ describe('filters middleware', () => {
15
15
  ],
16
16
  });
17
17
  });
18
+ it('decodes URI components', () => {
19
+ const request = { query: { filter: 'status=foo%2Cbar!!%2C%3Fbaz%3D!%3Equx' } };
20
+ const response = { locals: {} };
21
+ middleware(request, response, () => { });
22
+ assert.deepEqual(response.locals, {
23
+ filters: [{ field: 'status', operator: OperatorType.EQUAL, values: ['foo,bar!!,?baz=!>qux'] }],
24
+ });
25
+ });
18
26
  it('no data', () => {
19
27
  const request = { query: {} };
20
28
  const response = { locals: {} };
@@ -1,6 +1,7 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import { describe, it } from 'node:test';
3
3
  import { Provider } from '../../src/resources/provider.js';
4
+ import Logger from '../../src/resources/logger.js';
4
5
  describe('Provider', () => {
5
6
  const provider = new Provider({
6
7
  prepareRequest: requestOptions => {
@@ -13,6 +14,7 @@ describe('Provider', () => {
13
14
  };
14
15
  },
15
16
  });
17
+ const logger = new Logger();
16
18
  it('get', async (context) => {
17
19
  const response = new Response('{"data": "value"}', {
18
20
  status: 200,
@@ -21,6 +23,7 @@ describe('Provider', () => {
21
23
  const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
22
24
  const actualResponse = await provider.get('/endpoint', {
23
25
  credentials: { apiKey: 'apikey#1111' },
26
+ logger: logger,
24
27
  additionnalheaders: { 'X-Additional-Header': 'value1' },
25
28
  });
26
29
  assert.equal(fetchMock.mock.calls.length, 1);
@@ -38,7 +41,7 @@ describe('Provider', () => {
38
41
  },
39
42
  },
40
43
  ]);
41
- assert.deepEqual(actualResponse, { status: 200, headers: response.headers, data: { data: 'value' } });
44
+ assert.deepEqual(actualResponse, { status: 200, headers: response.headers, body: { data: 'value' } });
42
45
  });
43
46
  it('post with url encoded body', async (context) => {
44
47
  const response = new Response('{"data": "value"}', {
@@ -50,6 +53,7 @@ describe('Provider', () => {
50
53
  data: 'createdItemInfo',
51
54
  }, {
52
55
  credentials: { apiKey: 'apikey#1111' },
56
+ logger: logger,
53
57
  additionnalheaders: { 'X-Additional-Header': 'value1' },
54
58
  });
55
59
  assert.equal(fetchMock.mock.calls.length, 1);
@@ -67,7 +71,7 @@ describe('Provider', () => {
67
71
  },
68
72
  },
69
73
  ]);
70
- assert.deepEqual(actualResponse, { status: 201, headers: response.headers, data: { data: 'value' } });
74
+ assert.deepEqual(actualResponse, { status: 201, headers: response.headers, body: { data: 'value' } });
71
75
  });
72
76
  it('put with json body', async (context) => {
73
77
  const response = new Response('{"data": "value"}', {
@@ -80,6 +84,7 @@ describe('Provider', () => {
80
84
  data: 'updatedItemInfo',
81
85
  }, {
82
86
  credentials: { apiKey: 'apikey#1111' },
87
+ logger: logger,
83
88
  additionnalheaders: { 'X-Additional-Header': 'value1', 'Content-Type': 'application/json' },
84
89
  });
85
90
  assert.equal(fetchMock.mock.calls.length, 1);
@@ -97,7 +102,7 @@ describe('Provider', () => {
97
102
  },
98
103
  },
99
104
  ]);
100
- assert.deepEqual(actualResponse, { status: 201, headers: response.headers, data: { data: 'value' } });
105
+ assert.deepEqual(actualResponse, { status: 201, headers: response.headers, body: { data: 'value' } });
101
106
  });
102
107
  it('patch with query params', async (context) => {
103
108
  const response = new Response('{"data": "value"}', {
@@ -109,6 +114,7 @@ describe('Provider', () => {
109
114
  data: 'updatedItemInfo',
110
115
  }, {
111
116
  credentials: { apiKey: 'apikey#1111' },
117
+ logger: logger,
112
118
  queryParams: { param1: 'value1', param2: 'value2' },
113
119
  additionnalheaders: { 'X-Additional-Header': 'value1', 'Content-Type': 'application/json' },
114
120
  });
@@ -127,7 +133,7 @@ describe('Provider', () => {
127
133
  },
128
134
  },
129
135
  ]);
130
- assert.deepEqual(actualResponse, { status: 201, headers: response.headers, data: { data: 'value' } });
136
+ assert.deepEqual(actualResponse, { status: 201, headers: response.headers, body: { data: 'value' } });
131
137
  });
132
138
  it('delete', async (context) => {
133
139
  const response = new Response(undefined, {
@@ -137,6 +143,7 @@ describe('Provider', () => {
137
143
  const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
138
144
  const actualResponse = await provider.delete('/endpoint/123', {
139
145
  credentials: { apiKey: 'apikey#1111' },
146
+ logger: logger,
140
147
  additionnalheaders: { 'X-Additional-Header': 'value1', 'Content-Type': 'application/json' },
141
148
  });
142
149
  assert.equal(fetchMock.mock.calls.length, 1);
@@ -154,7 +161,7 @@ describe('Provider', () => {
154
161
  },
155
162
  },
156
163
  ]);
157
- assert.deepEqual(actualResponse, { status: 204, headers: response.headers, data: undefined });
164
+ assert.deepEqual(actualResponse, { status: 204, headers: response.headers, body: undefined });
158
165
  });
159
166
  it('uses rate limiter if provided', async (context) => {
160
167
  const mockRateLimiter = context.mock.fn((_context, request) => Promise.resolve(request()));
@@ -177,6 +184,7 @@ describe('Provider', () => {
177
184
  const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
178
185
  const options = {
179
186
  credentials: { apiKey: 'apikey#1111' },
187
+ logger: logger,
180
188
  additionnalheaders: { 'X-Additional-Header': 'value1', 'Content-Type': 'application/json' },
181
189
  };
182
190
  const actualResponse = await rateLimitedProvider.delete('/endpoint/123', options);
@@ -197,7 +205,7 @@ describe('Provider', () => {
197
205
  },
198
206
  },
199
207
  ]);
200
- assert.deepEqual(actualResponse, { status: 204, headers: response.headers, data: undefined });
208
+ assert.deepEqual(actualResponse, { status: 204, headers: response.headers, body: undefined });
201
209
  });
202
210
  it('throws on invalid json response', async (context) => {
203
211
  const response = new Response('{invalidJSON}', {
@@ -206,6 +214,7 @@ describe('Provider', () => {
206
214
  context.mock.method(global, 'fetch', () => Promise.resolve(response));
207
215
  assert.rejects(() => provider.get('/endpoint/123', {
208
216
  credentials: { apiKey: 'apikey#1111' },
217
+ logger: logger,
209
218
  }));
210
219
  });
211
220
  it('throws on status 400', async (context) => {
@@ -215,6 +224,7 @@ describe('Provider', () => {
215
224
  context.mock.method(global, 'fetch', () => Promise.resolve(response));
216
225
  assert.rejects(() => provider.get('/endpoint/123', {
217
226
  credentials: { apiKey: 'apikey#1111' },
227
+ logger: logger,
218
228
  }));
219
229
  });
220
230
  it('throws on status 429', async (context) => {
@@ -224,6 +234,7 @@ describe('Provider', () => {
224
234
  context.mock.method(global, 'fetch', () => Promise.resolve(response));
225
235
  assert.rejects(() => provider.get('/endpoint/123', {
226
236
  credentials: { apiKey: 'apikey#1111' },
237
+ logger: logger,
227
238
  }));
228
239
  });
229
240
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unito/integration-sdk",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Integration SDK",
5
5
  "type": "module",
6
6
  "types": "dist/src/index.d.ts",
package/src/handler.ts CHANGED
@@ -128,7 +128,7 @@ function assertValidConfiguration(path: Path, pathWithIdentifier: Path, handlers
128
128
 
129
129
  if (hasIndividualHandlers && hasCollectionHandlers) {
130
130
  throw new InvalidHandler(
131
- `The provided path '${path}' doesn't differentiate between individual and collection level operation, so you cannot define both. `,
131
+ `The provided path '${path}' doesn't differentiate between individual and collection level operation, so you cannot define both.`,
132
132
  );
133
133
  }
134
134
  }
@@ -221,7 +221,7 @@ export class Handler {
221
221
  if (this.handlers.getCollection) {
222
222
  const handler = this.handlers.getCollection;
223
223
 
224
- console.debug(` Enabling GET ${this.path}`);
224
+ console.debug(` Enabling getCollection at GET ${this.path}`);
225
225
 
226
226
  router.get(this.path, async (req, res) => {
227
227
  if (!res.locals.credentials) {
@@ -244,7 +244,7 @@ export class Handler {
244
244
  if (this.handlers.createItem) {
245
245
  const handler = this.handlers.createItem;
246
246
 
247
- console.debug(` Enabling POST ${this.path}`);
247
+ console.debug(` Enabling createItem at POST ${this.path}`);
248
248
 
249
249
  router.post(this.path, async (req, res) => {
250
250
  if (!res.locals.credentials) {
@@ -268,7 +268,7 @@ export class Handler {
268
268
  if (this.handlers.getItem) {
269
269
  const handler = this.handlers.getItem;
270
270
 
271
- console.debug(` Enabling GET ${this.pathWithIdentifier}`);
271
+ console.debug(` Enabling getItem at GET ${this.pathWithIdentifier}`);
272
272
 
273
273
  router.get(this.pathWithIdentifier, async (req, res) => {
274
274
  if (!res.locals.credentials) {
@@ -289,7 +289,7 @@ export class Handler {
289
289
  if (this.handlers.updateItem) {
290
290
  const handler = this.handlers.updateItem;
291
291
 
292
- console.debug(` Enabling PATCH ${this.pathWithIdentifier}`);
292
+ console.debug(` Enabling updateItem at PATCH ${this.pathWithIdentifier}`);
293
293
 
294
294
  router.patch(this.pathWithIdentifier, async (req, res) => {
295
295
  if (!res.locals.credentials) {
@@ -313,7 +313,7 @@ export class Handler {
313
313
  if (this.handlers.deleteItem) {
314
314
  const handler = this.handlers.deleteItem;
315
315
 
316
- console.debug(` Enabling DELETE ${this.pathWithIdentifier}`);
316
+ console.debug(` Enabling deleteItem at DELETE ${this.pathWithIdentifier}`);
317
317
 
318
318
  router.delete(this.pathWithIdentifier, async (req, res) => {
319
319
  if (!res.locals.credentials) {
@@ -334,7 +334,7 @@ export class Handler {
334
334
  if (this.handlers.getCredentialAccount) {
335
335
  const handler = this.handlers.getCredentialAccount;
336
336
 
337
- console.debug(` Enabling GET ${this.pathWithIdentifier}`);
337
+ console.debug(` Enabling getCredentialAccount at GET ${this.pathWithIdentifier}`);
338
338
 
339
339
  router.get(this.pathWithIdentifier, async (req, res) => {
340
340
  if (!res.locals.credentials) {
@@ -355,7 +355,7 @@ export class Handler {
355
355
  if (this.handlers.acknowledgeWebhooks) {
356
356
  const handler = this.handlers.acknowledgeWebhooks;
357
357
 
358
- console.debug(` Enabling POST ${this.pathWithIdentifier}`);
358
+ console.debug(` Enabling acknowledgeWebhooks at POST ${this.pathWithIdentifier}`);
359
359
 
360
360
  router.post(this.pathWithIdentifier, async (req, res) => {
361
361
  assertWebhookParseRequestPayload(req.body);
@@ -374,7 +374,7 @@ export class Handler {
374
374
  if (this.handlers.parseWebhooks) {
375
375
  const handler = this.handlers.parseWebhooks;
376
376
 
377
- console.debug(` Enabling POST ${this.pathWithIdentifier}`);
377
+ console.debug(` Enabling parseWebhooks at POST ${this.pathWithIdentifier}`);
378
378
 
379
379
  router.post(this.pathWithIdentifier, async (req, res) => {
380
380
  assertWebhookParseRequestPayload(req.body);
@@ -393,7 +393,7 @@ export class Handler {
393
393
  if (this.handlers.updateWebhookSubscriptions) {
394
394
  const handler = this.handlers.updateWebhookSubscriptions;
395
395
 
396
- console.debug(` Enabling PUT ${this.pathWithIdentifier}`);
396
+ console.debug(` Enabling updateWebhookSubscriptions at PUT ${this.pathWithIdentifier}`);
397
397
 
398
398
  router.put(this.pathWithIdentifier, async (req, res) => {
399
399
  if (!res.locals.credentials) {
@@ -18,6 +18,10 @@ function printErrorMessage(message: string) {
18
18
  console.error(message);
19
19
  }
20
20
 
21
+ type Options = {
22
+ port?: number;
23
+ };
24
+
21
25
  export default class Integration {
22
26
  private handlers: Handler[];
23
27
 
@@ -25,7 +29,10 @@ export default class Integration {
25
29
 
26
30
  private cache: Cache | undefined = undefined;
27
31
 
28
- constructor() {
32
+ private port: number;
33
+
34
+ constructor(options: Options = {}) {
35
+ this.port = options.port || 9200;
29
36
  this.handlers = [];
30
37
  }
31
38
 
@@ -101,9 +108,8 @@ export default class Integration {
101
108
  // Must be the last handler.
102
109
  app.use(notFoundMiddleware);
103
110
 
104
- this.instance = app.listen(process.env.PORT || 9200, () =>
105
- console.info(`Server started on port ${process.env.PORT || 9200}.`),
106
- );
111
+ // Start the server.
112
+ this.instance = app.listen(this.port, () => console.info(`Server started on port ${this.port}.`));
107
113
 
108
114
  // Trap exit signals.
109
115
  ['SIGTERM', 'SIGINT', 'SIGUSR2'].forEach(signalType => {
@@ -1,5 +1,6 @@
1
1
  import { buildHttpError } from '../errors.js';
2
2
  import { Credentials } from '../middlewares/credentials.js';
3
+ import Logger from '../resources/logger.js';
3
4
 
4
5
  /**
5
6
  * RateLimiter is a wrapper function that you can provide to limit the rate of calls to the provider based on the
@@ -18,25 +19,26 @@ import { Credentials } from '../middlewares/credentials.js';
18
19
  * @throws HttpError when the provider returns an error.
19
20
  */
20
21
  export type RateLimiter = <T>(
21
- context: { credentials: Credentials },
22
+ context: { credentials: Credentials; logger: Logger },
22
23
  targetFunction: () => Promise<Response<T>>,
23
24
  ) => Promise<Response<T>>;
24
25
 
25
26
  export interface RequestOptions {
26
27
  credentials: Credentials;
28
+ logger: Logger;
27
29
  queryParams?: { [key: string]: string };
28
30
  additionnalheaders?: { [key: string]: string };
29
31
  }
30
32
 
31
33
  export interface Response<T> {
32
- data: T;
34
+ body: T;
33
35
  status: number;
34
36
  headers: Headers;
35
37
  }
36
38
 
37
39
  export class Provider {
38
40
  protected rateLimiter: RateLimiter | undefined = undefined;
39
- protected prepareRequest: (context: { credentials: Credentials }) => {
41
+ protected prepareRequest: (context: { credentials: Credentials; logger: Logger }) => {
40
42
  url: string;
41
43
  headers: Record<string, string>;
42
44
  };
@@ -116,7 +118,7 @@ export class Provider {
116
118
  body: Record<string, unknown> | null,
117
119
  options: RequestOptions & { defaultHeaders: { 'Content-Type': string; Accept: string }; method: string },
118
120
  ): Promise<Response<T>> {
119
- const { url: providerUrl, headers: providerHeaders } = this.prepareRequest({ credentials: options.credentials });
121
+ const { url: providerUrl, headers: providerHeaders } = this.prepareRequest(options);
120
122
 
121
123
  let absoluteUrl = [providerUrl, endpoint.charAt(0) === '/' ? endpoint.substring(1) : endpoint].join('/');
122
124
 
@@ -149,8 +151,8 @@ export class Provider {
149
151
  }
150
152
 
151
153
  try {
152
- const data: T = response.body ? await response.json() : undefined;
153
- return { status: response.status, headers: response.headers, data };
154
+ const body: T = response.body ? await response.json() : undefined;
155
+ return { status: response.status, headers: response.headers, body };
154
156
  } catch {
155
157
  throw buildHttpError(400, 'Invalid JSON response');
156
158
  }
@@ -0,0 +1,34 @@
1
+ import assert from 'node:assert/strict';
2
+ import { describe, it } from 'node:test';
3
+ import Integration from '../src/integration.js';
4
+
5
+ describe('Integration', () => {
6
+ describe('constructor', () => {
7
+ it('defaults to port 9200', () => {
8
+ const integration = new Integration();
9
+
10
+ assert.equal(integration['port'], 9200);
11
+ });
12
+
13
+ it('accepts an optional port', () => {
14
+ const integration = new Integration({ port: 1234 });
15
+
16
+ assert.equal(integration['port'], 1234);
17
+ });
18
+ });
19
+
20
+ describe('addHandler', () => {
21
+ it('works', () => {
22
+ const integration = new Integration();
23
+
24
+ integration.addHandler('/', {});
25
+
26
+ const handler = integration['handlers'][0];
27
+
28
+ assert.ok(handler);
29
+ assert.equal(handler['path'], '/');
30
+ assert.equal(handler['pathWithIdentifier'], '/');
31
+ assert.deepEqual(handler['handlers'], {});
32
+ });
33
+ });
34
+ });
@@ -26,6 +26,23 @@ describe('filters middleware', () => {
26
26
  });
27
27
  });
28
28
 
29
+ it('decodes URI components', () => {
30
+ const request = { query: { filter: 'status=foo%2Cbar!!%2C%3Fbaz%3D!%3Equx' } } as express.Request<
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ any,
33
+ object,
34
+ object,
35
+ { filter: string }
36
+ >;
37
+ const response = { locals: {} } as express.Response;
38
+
39
+ middleware(request, response, () => {});
40
+
41
+ assert.deepEqual(response.locals, {
42
+ filters: [{ field: 'status', operator: OperatorType.EQUAL, values: ['foo,bar!!,?baz=!>qux'] }],
43
+ });
44
+ });
45
+
29
46
  it('no data', () => {
30
47
  const request = { query: {} } as express.Request;
31
48
  const response = { locals: {} } as express.Response;
@@ -2,6 +2,7 @@ import assert from 'node:assert/strict';
2
2
  import { describe, it } from 'node:test';
3
3
 
4
4
  import { Provider } from '../../src/resources/provider.js';
5
+ import Logger from '../../src/resources/logger.js';
5
6
 
6
7
  describe('Provider', () => {
7
8
  const provider = new Provider({
@@ -16,6 +17,8 @@ describe('Provider', () => {
16
17
  },
17
18
  });
18
19
 
20
+ const logger = new Logger();
21
+
19
22
  it('get', async context => {
20
23
  const response = new Response('{"data": "value"}', {
21
24
  status: 200,
@@ -26,6 +29,7 @@ describe('Provider', () => {
26
29
 
27
30
  const actualResponse = await provider.get('/endpoint', {
28
31
  credentials: { apiKey: 'apikey#1111' },
32
+ logger: logger,
29
33
  additionnalheaders: { 'X-Additional-Header': 'value1' },
30
34
  });
31
35
 
@@ -44,7 +48,7 @@ describe('Provider', () => {
44
48
  },
45
49
  },
46
50
  ]);
47
- assert.deepEqual(actualResponse, { status: 200, headers: response.headers, data: { data: 'value' } });
51
+ assert.deepEqual(actualResponse, { status: 200, headers: response.headers, body: { data: 'value' } });
48
52
  });
49
53
 
50
54
  it('post with url encoded body', async context => {
@@ -62,6 +66,7 @@ describe('Provider', () => {
62
66
  },
63
67
  {
64
68
  credentials: { apiKey: 'apikey#1111' },
69
+ logger: logger,
65
70
  additionnalheaders: { 'X-Additional-Header': 'value1' },
66
71
  },
67
72
  );
@@ -81,7 +86,7 @@ describe('Provider', () => {
81
86
  },
82
87
  },
83
88
  ]);
84
- assert.deepEqual(actualResponse, { status: 201, headers: response.headers, data: { data: 'value' } });
89
+ assert.deepEqual(actualResponse, { status: 201, headers: response.headers, body: { data: 'value' } });
85
90
  });
86
91
 
87
92
  it('put with json body', async context => {
@@ -100,6 +105,7 @@ describe('Provider', () => {
100
105
  },
101
106
  {
102
107
  credentials: { apiKey: 'apikey#1111' },
108
+ logger: logger,
103
109
  additionnalheaders: { 'X-Additional-Header': 'value1', 'Content-Type': 'application/json' },
104
110
  },
105
111
  );
@@ -119,7 +125,7 @@ describe('Provider', () => {
119
125
  },
120
126
  },
121
127
  ]);
122
- assert.deepEqual(actualResponse, { status: 201, headers: response.headers, data: { data: 'value' } });
128
+ assert.deepEqual(actualResponse, { status: 201, headers: response.headers, body: { data: 'value' } });
123
129
  });
124
130
 
125
131
  it('patch with query params', async context => {
@@ -137,6 +143,7 @@ describe('Provider', () => {
137
143
  },
138
144
  {
139
145
  credentials: { apiKey: 'apikey#1111' },
146
+ logger: logger,
140
147
  queryParams: { param1: 'value1', param2: 'value2' },
141
148
  additionnalheaders: { 'X-Additional-Header': 'value1', 'Content-Type': 'application/json' },
142
149
  },
@@ -157,7 +164,7 @@ describe('Provider', () => {
157
164
  },
158
165
  },
159
166
  ]);
160
- assert.deepEqual(actualResponse, { status: 201, headers: response.headers, data: { data: 'value' } });
167
+ assert.deepEqual(actualResponse, { status: 201, headers: response.headers, body: { data: 'value' } });
161
168
  });
162
169
 
163
170
  it('delete', async context => {
@@ -170,6 +177,7 @@ describe('Provider', () => {
170
177
 
171
178
  const actualResponse = await provider.delete('/endpoint/123', {
172
179
  credentials: { apiKey: 'apikey#1111' },
180
+ logger: logger,
173
181
  additionnalheaders: { 'X-Additional-Header': 'value1', 'Content-Type': 'application/json' },
174
182
  });
175
183
 
@@ -188,7 +196,7 @@ describe('Provider', () => {
188
196
  },
189
197
  },
190
198
  ]);
191
- assert.deepEqual(actualResponse, { status: 204, headers: response.headers, data: undefined });
199
+ assert.deepEqual(actualResponse, { status: 204, headers: response.headers, body: undefined });
192
200
  });
193
201
 
194
202
  it('uses rate limiter if provided', async context => {
@@ -216,6 +224,7 @@ describe('Provider', () => {
216
224
 
217
225
  const options = {
218
226
  credentials: { apiKey: 'apikey#1111' },
227
+ logger: logger,
219
228
  additionnalheaders: { 'X-Additional-Header': 'value1', 'Content-Type': 'application/json' },
220
229
  };
221
230
 
@@ -238,7 +247,7 @@ describe('Provider', () => {
238
247
  },
239
248
  },
240
249
  ]);
241
- assert.deepEqual(actualResponse, { status: 204, headers: response.headers, data: undefined });
250
+ assert.deepEqual(actualResponse, { status: 204, headers: response.headers, body: undefined });
242
251
  });
243
252
 
244
253
  it('throws on invalid json response', async context => {
@@ -251,6 +260,7 @@ describe('Provider', () => {
251
260
  assert.rejects(() =>
252
261
  provider.get('/endpoint/123', {
253
262
  credentials: { apiKey: 'apikey#1111' },
263
+ logger: logger,
254
264
  }),
255
265
  );
256
266
  });
@@ -265,6 +275,7 @@ describe('Provider', () => {
265
275
  assert.rejects(() =>
266
276
  provider.get('/endpoint/123', {
267
277
  credentials: { apiKey: 'apikey#1111' },
278
+ logger: logger,
268
279
  }),
269
280
  );
270
281
  });
@@ -279,6 +290,7 @@ describe('Provider', () => {
279
290
  assert.rejects(() =>
280
291
  provider.get('/endpoint/123', {
281
292
  credentials: { apiKey: 'apikey#1111' },
293
+ logger: logger,
282
294
  }),
283
295
  );
284
296
  });