@unito/integration-sdk 2.0.0 → 2.0.2

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.
@@ -92,6 +92,7 @@ export class Handler {
92
92
  const collection = await handler({
93
93
  credentials: res.locals.credentials,
94
94
  secrets: res.locals.secrets,
95
+ search: res.locals.search,
95
96
  selects: res.locals.selects,
96
97
  filters: res.locals.filters,
97
98
  logger: res.locals.logger,
@@ -523,6 +523,7 @@ class Handler {
523
523
  const collection = await handler({
524
524
  credentials: res.locals.credentials,
525
525
  secrets: res.locals.secrets,
526
+ search: res.locals.search,
526
527
  selects: res.locals.selects,
527
528
  filters: res.locals.filters,
528
529
  logger: res.locals.logger,
@@ -828,7 +829,7 @@ function onError(err, _req, res, next) {
828
829
  // a subset of the symbol of another operator.
829
830
  //
830
831
  // For example, the symbol "=" (EQUAL) is a subset of the symbol "!=" (NOT_EQUAL).
831
- const ORDERED_OPERATORS = Object.values(integrationApi.OperatorType).sort((o1, o2) => o2.length - o1.length);
832
+ const ORDERED_OPERATORS = Object.values(integrationApi.OperatorTypes).sort((o1, o2) => o2.length - o1.length);
832
833
  function extractFilters(req, res, next) {
833
834
  const rawFilters = req.query.filter;
834
835
  res.locals.filters = [];
@@ -929,6 +930,17 @@ function extractSecrets(req, res, next) {
929
930
  next();
930
931
  }
931
932
 
933
+ function extractSearch(req, res, next) {
934
+ const rawSearch = req.query.search;
935
+ if (typeof rawSearch === 'string') {
936
+ res.locals.search = rawSearch;
937
+ }
938
+ else {
939
+ res.locals.search = null;
940
+ }
941
+ next();
942
+ }
943
+
932
944
  function extractSelects(req, res, next) {
933
945
  const rawSelect = req.query.select;
934
946
  if (typeof rawSelect === 'string') {
@@ -1061,7 +1073,7 @@ class Integration {
1061
1073
  // Express Server initialization
1062
1074
  const app = express();
1063
1075
  // Parse query strings with https://github.com/ljharb/qs.
1064
- app.set('query parser', 'extended');
1076
+ app.set('query parser', 'simple');
1065
1077
  app.use(express.json());
1066
1078
  // Must be one of the first handlers (to catch all the errors).
1067
1079
  app.use(onFinish);
@@ -1081,6 +1093,7 @@ class Integration {
1081
1093
  app.use(extractCredentials);
1082
1094
  app.use(extractSecrets);
1083
1095
  app.use(extractFilters);
1096
+ app.use(extractSearch);
1084
1097
  app.use(extractSelects);
1085
1098
  app.use(extractRelations);
1086
1099
  app.use(extractOperationDeadline);
@@ -1393,11 +1406,11 @@ class Provider {
1393
1406
  // When we expect octet-stream, we accept any Content-Type the provider sends us, we just want to stream it
1394
1407
  body = response.body;
1395
1408
  }
1396
- else if (headers.Accept === 'application/json') {
1409
+ else if (headers.Accept?.match(/application\/.*json/)) {
1397
1410
  // Validate that the response content type is at least similar to what we expect
1398
1411
  // (Provider's response Content-Type might be more specific, e.g. application/json;charset=utf-8)
1399
1412
  // Default to application/json if no Content-Type header is provided
1400
- if (responseContentType && !responseContentType.includes('application/json')) {
1413
+ if (responseContentType && !responseContentType.match(/application\/.*json/)) {
1401
1414
  const textResult = await response.text();
1402
1415
  throw this.handleError(500, `Unsupported content-type, expected 'application/json' but got '${responseContentType}'.
1403
1416
  Original response (${response.status}): "${textResult}"`, options);
@@ -10,6 +10,7 @@ import notFoundMiddleware from './middlewares/notFound.js';
10
10
  import loggerMiddleware from './middlewares/logger.js';
11
11
  import startMiddleware from './middlewares/start.js';
12
12
  import secretsMiddleware from './middlewares/secrets.js';
13
+ import searchMiddleware from './middlewares/search.js';
13
14
  import selectsMiddleware from './middlewares/selects.js';
14
15
  import relationsMiddleware from './middlewares/relations.js';
15
16
  import signalMiddleware from './middlewares/signal.js';
@@ -111,7 +112,7 @@ export default class Integration {
111
112
  // Express Server initialization
112
113
  const app = express();
113
114
  // Parse query strings with https://github.com/ljharb/qs.
114
- app.set('query parser', 'extended');
115
+ app.set('query parser', 'simple');
115
116
  app.use(express.json());
116
117
  // Must be one of the first handlers (to catch all the errors).
117
118
  app.use(finishMiddleware);
@@ -131,6 +132,7 @@ export default class Integration {
131
132
  app.use(credentialsMiddleware);
132
133
  app.use(secretsMiddleware);
133
134
  app.use(filtersMiddleware);
135
+ app.use(searchMiddleware);
134
136
  app.use(selectsMiddleware);
135
137
  app.use(relationsMiddleware);
136
138
  app.use(signalMiddleware);
@@ -1,10 +1,10 @@
1
- import { OperatorType } from '@unito/integration-api';
1
+ import { OperatorTypes } from '@unito/integration-api';
2
2
  // The operators are ordered by their symbol length, in descending order.
3
3
  // This is necessary because the symbol of an operator can be
4
4
  // a subset of the symbol of another operator.
5
5
  //
6
6
  // For example, the symbol "=" (EQUAL) is a subset of the symbol "!=" (NOT_EQUAL).
7
- const ORDERED_OPERATORS = Object.values(OperatorType).sort((o1, o2) => o2.length - o1.length);
7
+ const ORDERED_OPERATORS = Object.values(OperatorTypes).sort((o1, o2) => o2.length - o1.length);
8
8
  function extractFilters(req, res, next) {
9
9
  const rawFilters = req.query.filter;
10
10
  res.locals.filters = [];
@@ -0,0 +1,10 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ declare global {
3
+ namespace Express {
4
+ interface Locals {
5
+ search: string | null;
6
+ }
7
+ }
8
+ }
9
+ declare function extractSearch(req: Request, res: Response, next: NextFunction): void;
10
+ export default extractSearch;
@@ -0,0 +1,11 @@
1
+ function extractSearch(req, res, next) {
2
+ const rawSearch = req.query.search;
3
+ if (typeof rawSearch === 'string') {
4
+ res.locals.search = rawSearch;
5
+ }
6
+ else {
7
+ res.locals.search = null;
8
+ }
9
+ next();
10
+ }
11
+ export default extractSearch;
@@ -90,6 +90,16 @@ export type GetCollectionContext<P extends Maybe<Params> = Empty, Q extends Quer
90
90
  * @see {@link Filter}
91
91
  */
92
92
  filters: Filter[];
93
+ /**
94
+ * Parsed search query param yielding a free-text search query.
95
+ *
96
+ * Given a search query param:
97
+ * `search=John`
98
+ *
99
+ * Context.search will be:
100
+ * 'John'
101
+ */
102
+ search: string | null;
93
103
  /**
94
104
  * Parsed select query param yielding a list of fields to select.
95
105
  *
@@ -286,11 +286,11 @@ export class Provider {
286
286
  // When we expect octet-stream, we accept any Content-Type the provider sends us, we just want to stream it
287
287
  body = response.body;
288
288
  }
289
- else if (headers.Accept === 'application/json') {
289
+ else if (headers.Accept?.match(/application\/.*json/)) {
290
290
  // Validate that the response content type is at least similar to what we expect
291
291
  // (Provider's response Content-Type might be more specific, e.g. application/json;charset=utf-8)
292
292
  // Default to application/json if no Content-Type header is provided
293
- if (responseContentType && !responseContentType.includes('application/json')) {
293
+ if (responseContentType && !responseContentType.match(/application\/.*json/)) {
294
294
  const textResult = await response.text();
295
295
  throw this.handleError(500, `Unsupported content-type, expected 'application/json' but got '${responseContentType}'.
296
296
  Original response (${response.status}): "${textResult}"`, options);
@@ -1,4 +1,4 @@
1
- import { FieldValueType, OperatorType, Semantic } from '@unito/integration-api';
1
+ import { FieldValueTypes, OperatorTypes, Semantics } from '@unito/integration-api';
2
2
  import assert from 'node:assert/strict';
3
3
  import { describe, it } from 'node:test';
4
4
  import { getApplicableFilters } from '../src/helpers.js';
@@ -7,48 +7,48 @@ describe('Helpers', () => {
7
7
  it('returns only filters for defined fields', () => {
8
8
  const actual = getApplicableFilters({
9
9
  filters: [
10
- { field: 'status', operator: OperatorType.EQUAL, values: ['active', 'pending'] },
11
- { field: 'email', operator: OperatorType.IS_NOT_NULL, values: [] },
10
+ { field: 'status', operator: OperatorTypes.EQUAL, values: ['active', 'pending'] },
11
+ { field: 'email', operator: OperatorTypes.IS_NOT_NULL, values: [] },
12
12
  ],
13
13
  }, [
14
14
  {
15
15
  name: 'status',
16
16
  label: 'Status',
17
- type: FieldValueType.STRING,
17
+ type: FieldValueTypes.STRING,
18
18
  },
19
19
  ]);
20
- const expected = [{ field: 'status', operator: OperatorType.EQUAL, values: ['active', 'pending'] }];
20
+ const expected = [{ field: 'status', operator: OperatorTypes.EQUAL, values: ['active', 'pending'] }];
21
21
  assert.deepEqual(actual, expected);
22
22
  });
23
23
  it('translates semantics into field names', () => {
24
24
  const actual = getApplicableFilters({
25
25
  filters: [
26
- { field: 'semantic:displayName', operator: OperatorType.START_WITH, values: ['Bob'] },
27
- { field: 'semantic:createdAt', operator: OperatorType.EQUAL, values: ['2021-01-01'] },
26
+ { field: 'semantic:displayName', operator: OperatorTypes.START_WITH, values: ['Bob'] },
27
+ { field: 'semantic:createdAt', operator: OperatorTypes.EQUAL, values: ['2021-01-01'] },
28
28
  ],
29
29
  }, [
30
30
  {
31
31
  name: 'name',
32
32
  label: 'Name',
33
- type: FieldValueType.STRING,
34
- semantic: Semantic.DISPLAY_NAME,
33
+ type: FieldValueTypes.STRING,
34
+ semantic: Semantics.DISPLAY_NAME,
35
35
  },
36
36
  ]);
37
- const expected = [{ field: 'name', operator: OperatorType.START_WITH, values: ['Bob'] }];
37
+ const expected = [{ field: 'name', operator: OperatorTypes.START_WITH, values: ['Bob'] }];
38
38
  assert.deepEqual(actual, expected);
39
39
  });
40
40
  it('gracefully handle garbage', () => {
41
41
  const actual = getApplicableFilters({
42
42
  filters: [
43
- { field: '...', operator: OperatorType.EQUAL, values: [] },
44
- { field: ':', operator: OperatorType.EQUAL, values: [] },
45
- { field: '', operator: OperatorType.EQUAL, values: [] },
43
+ { field: '...', operator: OperatorTypes.EQUAL, values: [] },
44
+ { field: ':', operator: OperatorTypes.EQUAL, values: [] },
45
+ { field: '', operator: OperatorTypes.EQUAL, values: [] },
46
46
  ],
47
47
  }, [
48
48
  {
49
49
  name: 'status',
50
50
  label: 'Status',
51
- type: FieldValueType.STRING,
51
+ type: FieldValueTypes.STRING,
52
52
  },
53
53
  ]);
54
54
  assert.deepEqual(actual, []);
@@ -1,10 +1,10 @@
1
- import { OperatorType } from '@unito/integration-api';
1
+ import { OperatorTypes } from '@unito/integration-api';
2
2
  import assert from 'node:assert/strict';
3
3
  import { describe, it } from 'node:test';
4
4
  import extractFilters from '../../src/middlewares/filters.js';
5
5
  describe('filters middleware', () => {
6
6
  it('properly parse operators', () => {
7
- Object.values(OperatorType).forEach(operator => {
7
+ Object.values(OperatorTypes).forEach(operator => {
8
8
  const request = {
9
9
  query: { filter: `aKey${operator}value` },
10
10
  };
@@ -20,7 +20,7 @@ describe('filters middleware', () => {
20
20
  const response = { locals: {} };
21
21
  extractFilters(request, response, () => { });
22
22
  assert.deepEqual(response.locals, {
23
- filters: [{ field: 'aKey', operator: OperatorType.IS_NULL, values: [] }],
23
+ filters: [{ field: 'aKey', operator: OperatorTypes.IS_NULL, values: [] }],
24
24
  });
25
25
  });
26
26
  it('decodes URI components', () => {
@@ -28,7 +28,7 @@ describe('filters middleware', () => {
28
28
  const response = { locals: {} };
29
29
  extractFilters(request, response, () => { });
30
30
  assert.deepEqual(response.locals, {
31
- filters: [{ field: 'status', operator: OperatorType.EQUAL, values: ['foo,bar!!,?baz=!>qux'] }],
31
+ filters: [{ field: 'status', operator: OperatorTypes.EQUAL, values: ['foo,bar!!,?baz=!>qux'] }],
32
32
  });
33
33
  });
34
34
  it('no data', () => {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,21 @@
1
+ import assert from 'node:assert/strict';
2
+ import { describe, it } from 'node:test';
3
+ import extractSearch from '../../src/middlewares/search.js';
4
+ describe('search middleware', () => {
5
+ it('data', () => {
6
+ const request = { query: { search: 'foo bar spam baz' } };
7
+ const response = { locals: {} };
8
+ extractSearch(request, response, () => { });
9
+ assert.deepEqual(response.locals, {
10
+ search: 'foo bar spam baz',
11
+ });
12
+ });
13
+ it('no data', () => {
14
+ const request = { query: {} };
15
+ const response = { locals: {} };
16
+ extractSearch(request, response, () => { });
17
+ assert.deepEqual(response.locals, {
18
+ search: null,
19
+ });
20
+ });
21
+ });
@@ -79,6 +79,93 @@ describe('Provider', () => {
79
79
  ]);
80
80
  assert.deepEqual(actualResponse, { status: 200, headers: response.headers, body: '' });
81
81
  });
82
+ it('should accept application/schema+json type response', async (context) => {
83
+ const response = new Response('{"data": "value"}', {
84
+ status: 200,
85
+ headers: { 'Content-Type': 'application/schema+json; charset=UTF-8' },
86
+ });
87
+ const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
88
+ const actualResponse = await provider.get('/endpoint', {
89
+ credentials: { apiKey: 'apikey#1111' },
90
+ logger: logger,
91
+ signal: new AbortController().signal,
92
+ additionnalheaders: { 'X-Additional-Header': 'value1', Accept: 'application/schema+json; charset=UTF-8' },
93
+ });
94
+ assert.equal(fetchMock.mock.calls.length, 1);
95
+ assert.deepEqual(fetchMock.mock.calls[0]?.arguments, [
96
+ 'www.myApi.com/endpoint',
97
+ {
98
+ method: 'GET',
99
+ body: null,
100
+ signal: new AbortController().signal,
101
+ headers: {
102
+ Accept: 'application/schema+json; charset=UTF-8',
103
+ 'X-Custom-Provider-Header': 'value',
104
+ 'X-Provider-Credential-Header': 'apikey#1111',
105
+ 'X-Additional-Header': 'value1',
106
+ },
107
+ },
108
+ ]);
109
+ assert.deepEqual(actualResponse, { status: 200, headers: response.headers, body: { data: 'value' } });
110
+ });
111
+ it('should accept application/swagger+json type response', async (context) => {
112
+ const response = new Response('{"data": "value"}', {
113
+ status: 200,
114
+ headers: { 'Content-Type': 'application/swagger+json; charset=UTF-8' },
115
+ });
116
+ const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
117
+ const actualResponse = await provider.get('/endpoint', {
118
+ credentials: { apiKey: 'apikey#1111' },
119
+ logger: logger,
120
+ signal: new AbortController().signal,
121
+ additionnalheaders: { 'X-Additional-Header': 'value1', Accept: 'application/swagger+json; charset=UTF-8' },
122
+ });
123
+ assert.equal(fetchMock.mock.calls.length, 1);
124
+ assert.deepEqual(fetchMock.mock.calls[0]?.arguments, [
125
+ 'www.myApi.com/endpoint',
126
+ {
127
+ method: 'GET',
128
+ body: null,
129
+ signal: new AbortController().signal,
130
+ headers: {
131
+ Accept: 'application/swagger+json; charset=UTF-8',
132
+ 'X-Custom-Provider-Header': 'value',
133
+ 'X-Provider-Credential-Header': 'apikey#1111',
134
+ 'X-Additional-Header': 'value1',
135
+ },
136
+ },
137
+ ]);
138
+ assert.deepEqual(actualResponse, { status: 200, headers: response.headers, body: { data: 'value' } });
139
+ });
140
+ it('should accept application/vnd.oracle.resource+json type response', async (context) => {
141
+ const response = new Response('{"data": "value"}', {
142
+ status: 200,
143
+ headers: { 'Content-Type': 'application/vnd.oracle.resource+json; type=collection; charset=UTF-8' },
144
+ });
145
+ const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
146
+ const actualResponse = await provider.get('/endpoint', {
147
+ credentials: { apiKey: 'apikey#1111' },
148
+ logger: logger,
149
+ signal: new AbortController().signal,
150
+ additionnalheaders: { 'X-Additional-Header': 'value1' },
151
+ });
152
+ assert.equal(fetchMock.mock.calls.length, 1);
153
+ assert.deepEqual(fetchMock.mock.calls[0]?.arguments, [
154
+ 'www.myApi.com/endpoint',
155
+ {
156
+ method: 'GET',
157
+ body: null,
158
+ signal: new AbortController().signal,
159
+ headers: {
160
+ Accept: 'application/json',
161
+ 'X-Custom-Provider-Header': 'value',
162
+ 'X-Provider-Credential-Header': 'apikey#1111',
163
+ 'X-Additional-Header': 'value1',
164
+ },
165
+ },
166
+ ]);
167
+ assert.deepEqual(actualResponse, { status: 200, headers: response.headers, body: { data: 'value' } });
168
+ });
82
169
  it('should return the raw response body if specified', async (context) => {
83
170
  const response = new Response(`IMAGINE A HUGE PAYLOAD`, {
84
171
  status: 200,
package/package.json CHANGED
@@ -1,14 +1,20 @@
1
1
  {
2
2
  "name": "@unito/integration-sdk",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Integration SDK",
5
5
  "type": "module",
6
6
  "types": "dist/src/index.d.ts",
7
7
  "exports": {
8
8
  "./package.json": "./package.json",
9
9
  ".": {
10
- "import": "./dist/src/index.js",
11
- "require": "./dist/src/index.cjs"
10
+ "import": {
11
+ "types": "./dist/src/index.d.ts",
12
+ "default": "./dist/src/index.js"
13
+ },
14
+ "require": {
15
+ "types": "./dist/src/index.d.ts",
16
+ "default": "./dist/src/index.cjs"
17
+ }
12
18
  }
13
19
  },
14
20
  "license": "LicenseRef-LICENSE",
@@ -46,7 +52,7 @@
46
52
  "typescript": "5.x"
47
53
  },
48
54
  "dependencies": {
49
- "@unito/integration-api": "1.x",
55
+ "@unito/integration-api": "4.x",
50
56
  "busboy": "^1.6.0",
51
57
  "cachette": "2.x",
52
58
  "express": "^5.x",
package/src/handler.ts CHANGED
@@ -297,6 +297,7 @@ export class Handler {
297
297
  const collection = await handler({
298
298
  credentials: res.locals.credentials,
299
299
  secrets: res.locals.secrets,
300
+ search: res.locals.search,
300
301
  selects: res.locals.selects,
301
302
  filters: res.locals.filters,
302
303
  logger: res.locals.logger,
@@ -12,6 +12,7 @@ import notFoundMiddleware from './middlewares/notFound.js';
12
12
  import loggerMiddleware from './middlewares/logger.js';
13
13
  import startMiddleware from './middlewares/start.js';
14
14
  import secretsMiddleware from './middlewares/secrets.js';
15
+ import searchMiddleware from './middlewares/search.js';
15
16
  import selectsMiddleware from './middlewares/selects.js';
16
17
  import relationsMiddleware from './middlewares/relations.js';
17
18
  import signalMiddleware from './middlewares/signal.js';
@@ -124,7 +125,7 @@ export default class Integration {
124
125
  const app: express.Application = express();
125
126
 
126
127
  // Parse query strings with https://github.com/ljharb/qs.
127
- app.set('query parser', 'extended');
128
+ app.set('query parser', 'simple');
128
129
 
129
130
  app.use(express.json());
130
131
 
@@ -149,6 +150,7 @@ export default class Integration {
149
150
  app.use(credentialsMiddleware);
150
151
  app.use(secretsMiddleware);
151
152
  app.use(filtersMiddleware);
153
+ app.use(searchMiddleware);
152
154
  app.use(selectsMiddleware);
153
155
  app.use(relationsMiddleware);
154
156
  app.use(signalMiddleware);
@@ -1,4 +1,4 @@
1
- import { OperatorType } from '@unito/integration-api';
1
+ import { OperatorType, OperatorTypes } from '@unito/integration-api';
2
2
  import { Request, Response, NextFunction } from 'express';
3
3
 
4
4
  declare global {
@@ -32,7 +32,7 @@ export type Filter = {
32
32
  // a subset of the symbol of another operator.
33
33
  //
34
34
  // For example, the symbol "=" (EQUAL) is a subset of the symbol "!=" (NOT_EQUAL).
35
- const ORDERED_OPERATORS = Object.values(OperatorType).sort((o1, o2) => o2.length - o1.length);
35
+ const ORDERED_OPERATORS = Object.values(OperatorTypes).sort((o1, o2) => o2.length - o1.length);
36
36
 
37
37
  function extractFilters(req: Request, res: Response, next: NextFunction) {
38
38
  const rawFilters = req.query.filter;
@@ -0,0 +1,31 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+
3
+ declare global {
4
+ // eslint-disable-next-line @typescript-eslint/no-namespace
5
+ namespace Express {
6
+ interface Locals {
7
+ // When the query params contains...
8
+ //
9
+ // search=foo
10
+ //
11
+ // ... it becomes available as follow in handlers:
12
+ //
13
+ // 'foo'
14
+ search: string | null;
15
+ }
16
+ }
17
+ }
18
+
19
+ function extractSearch(req: Request, res: Response, next: NextFunction) {
20
+ const rawSearch = req.query.search;
21
+
22
+ if (typeof rawSearch === 'string') {
23
+ res.locals.search = rawSearch;
24
+ } else {
25
+ res.locals.search = null;
26
+ }
27
+
28
+ next();
29
+ }
30
+
31
+ export default extractSearch;
@@ -96,6 +96,16 @@ export type GetCollectionContext<P extends Maybe<Params> = Empty, Q extends Quer
96
96
  * @see {@link Filter}
97
97
  */
98
98
  filters: Filter[];
99
+ /**
100
+ * Parsed search query param yielding a free-text search query.
101
+ *
102
+ * Given a search query param:
103
+ * `search=John`
104
+ *
105
+ * Context.search will be:
106
+ * 'John'
107
+ */
108
+ search: string | null;
99
109
  /**
100
110
  * Parsed select query param yielding a list of fields to select.
101
111
  *
@@ -402,11 +402,11 @@ export class Provider {
402
402
  if (options.rawBody || headers.Accept === 'application/octet-stream') {
403
403
  // When we expect octet-stream, we accept any Content-Type the provider sends us, we just want to stream it
404
404
  body = response.body as T;
405
- } else if (headers.Accept === 'application/json') {
405
+ } else if (headers.Accept?.match(/application\/.*json/)) {
406
406
  // Validate that the response content type is at least similar to what we expect
407
407
  // (Provider's response Content-Type might be more specific, e.g. application/json;charset=utf-8)
408
408
  // Default to application/json if no Content-Type header is provided
409
- if (responseContentType && !responseContentType.includes('application/json')) {
409
+ if (responseContentType && !responseContentType.match(/application\/.*json/)) {
410
410
  const textResult = await response.text();
411
411
  throw this.handleError(
412
412
  500,
@@ -1,4 +1,4 @@
1
- import { FieldValueType, OperatorType, Semantic } from '@unito/integration-api';
1
+ import { FieldValueTypes, OperatorTypes, Semantics } from '@unito/integration-api';
2
2
 
3
3
  import assert from 'node:assert/strict';
4
4
  import { describe, it } from 'node:test';
@@ -10,20 +10,20 @@ describe('Helpers', () => {
10
10
  const actual = getApplicableFilters(
11
11
  {
12
12
  filters: [
13
- { field: 'status', operator: OperatorType.EQUAL, values: ['active', 'pending'] },
14
- { field: 'email', operator: OperatorType.IS_NOT_NULL, values: [] },
13
+ { field: 'status', operator: OperatorTypes.EQUAL, values: ['active', 'pending'] },
14
+ { field: 'email', operator: OperatorTypes.IS_NOT_NULL, values: [] },
15
15
  ],
16
16
  },
17
17
  [
18
18
  {
19
19
  name: 'status',
20
20
  label: 'Status',
21
- type: FieldValueType.STRING,
21
+ type: FieldValueTypes.STRING,
22
22
  },
23
23
  ],
24
24
  );
25
25
 
26
- const expected = [{ field: 'status', operator: OperatorType.EQUAL, values: ['active', 'pending'] }];
26
+ const expected = [{ field: 'status', operator: OperatorTypes.EQUAL, values: ['active', 'pending'] }];
27
27
 
28
28
  assert.deepEqual(actual, expected);
29
29
  });
@@ -32,21 +32,21 @@ describe('Helpers', () => {
32
32
  const actual = getApplicableFilters(
33
33
  {
34
34
  filters: [
35
- { field: 'semantic:displayName', operator: OperatorType.START_WITH, values: ['Bob'] },
36
- { field: 'semantic:createdAt', operator: OperatorType.EQUAL, values: ['2021-01-01'] },
35
+ { field: 'semantic:displayName', operator: OperatorTypes.START_WITH, values: ['Bob'] },
36
+ { field: 'semantic:createdAt', operator: OperatorTypes.EQUAL, values: ['2021-01-01'] },
37
37
  ],
38
38
  },
39
39
  [
40
40
  {
41
41
  name: 'name',
42
42
  label: 'Name',
43
- type: FieldValueType.STRING,
44
- semantic: Semantic.DISPLAY_NAME,
43
+ type: FieldValueTypes.STRING,
44
+ semantic: Semantics.DISPLAY_NAME,
45
45
  },
46
46
  ],
47
47
  );
48
48
 
49
- const expected = [{ field: 'name', operator: OperatorType.START_WITH, values: ['Bob'] }];
49
+ const expected = [{ field: 'name', operator: OperatorTypes.START_WITH, values: ['Bob'] }];
50
50
 
51
51
  assert.deepEqual(actual, expected);
52
52
  });
@@ -55,16 +55,16 @@ describe('Helpers', () => {
55
55
  const actual = getApplicableFilters(
56
56
  {
57
57
  filters: [
58
- { field: '...', operator: OperatorType.EQUAL, values: [] },
59
- { field: ':', operator: OperatorType.EQUAL, values: [] },
60
- { field: '', operator: OperatorType.EQUAL, values: [] },
58
+ { field: '...', operator: OperatorTypes.EQUAL, values: [] },
59
+ { field: ':', operator: OperatorTypes.EQUAL, values: [] },
60
+ { field: '', operator: OperatorTypes.EQUAL, values: [] },
61
61
  ],
62
62
  },
63
63
  [
64
64
  {
65
65
  name: 'status',
66
66
  label: 'Status',
67
- type: FieldValueType.STRING,
67
+ type: FieldValueTypes.STRING,
68
68
  },
69
69
  ],
70
70
  );
@@ -1,4 +1,4 @@
1
- import { OperatorType } from '@unito/integration-api';
1
+ import { OperatorTypes } from '@unito/integration-api';
2
2
  import express from 'express';
3
3
  import assert from 'node:assert/strict';
4
4
  import { describe, it } from 'node:test';
@@ -6,7 +6,7 @@ import extractFilters from '../../src/middlewares/filters.js';
6
6
 
7
7
  describe('filters middleware', () => {
8
8
  it('properly parse operators', () => {
9
- Object.values(OperatorType).forEach(operator => {
9
+ Object.values(OperatorTypes).forEach(operator => {
10
10
  const request = {
11
11
  query: { filter: `aKey${operator}value` },
12
12
  } as express.Request<
@@ -41,7 +41,7 @@ describe('filters middleware', () => {
41
41
  extractFilters(request, response, () => {});
42
42
 
43
43
  assert.deepEqual(response.locals, {
44
- filters: [{ field: 'aKey', operator: OperatorType.IS_NULL, values: [] }],
44
+ filters: [{ field: 'aKey', operator: OperatorTypes.IS_NULL, values: [] }],
45
45
  });
46
46
  });
47
47
 
@@ -58,7 +58,7 @@ describe('filters middleware', () => {
58
58
  extractFilters(request, response, () => {});
59
59
 
60
60
  assert.deepEqual(response.locals, {
61
- filters: [{ field: 'status', operator: OperatorType.EQUAL, values: ['foo,bar!!,?baz=!>qux'] }],
61
+ filters: [{ field: 'status', operator: OperatorTypes.EQUAL, values: ['foo,bar!!,?baz=!>qux'] }],
62
62
  });
63
63
  });
64
64
 
@@ -0,0 +1,34 @@
1
+ import express from 'express';
2
+ import assert from 'node:assert/strict';
3
+ import { describe, it } from 'node:test';
4
+ import extractSearch from '../../src/middlewares/search.js';
5
+
6
+ describe('search middleware', () => {
7
+ it('data', () => {
8
+ const request = { query: { search: 'foo bar spam baz' } } as express.Request<
9
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
+ any,
11
+ object,
12
+ object,
13
+ { search: string }
14
+ >;
15
+ const response = { locals: {} } as express.Response;
16
+
17
+ extractSearch(request, response, () => {});
18
+
19
+ assert.deepEqual(response.locals, {
20
+ search: 'foo bar spam baz',
21
+ });
22
+ });
23
+
24
+ it('no data', () => {
25
+ const request = { query: {} } as express.Request;
26
+ const response = { locals: {} } as express.Response;
27
+
28
+ extractSearch(request, response, () => {});
29
+
30
+ assert.deepEqual(response.locals, {
31
+ search: null,
32
+ });
33
+ });
34
+ });
@@ -94,6 +94,111 @@ describe('Provider', () => {
94
94
  assert.deepEqual(actualResponse, { status: 200, headers: response.headers, body: '' });
95
95
  });
96
96
 
97
+ it('should accept application/schema+json type response', async context => {
98
+ const response = new Response('{"data": "value"}', {
99
+ status: 200,
100
+ headers: { 'Content-Type': 'application/schema+json; charset=UTF-8' },
101
+ });
102
+
103
+ const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
104
+
105
+ const actualResponse = await provider.get('/endpoint', {
106
+ credentials: { apiKey: 'apikey#1111' },
107
+ logger: logger,
108
+ signal: new AbortController().signal,
109
+ additionnalheaders: { 'X-Additional-Header': 'value1', Accept: 'application/schema+json; charset=UTF-8' },
110
+ });
111
+
112
+ assert.equal(fetchMock.mock.calls.length, 1);
113
+
114
+ assert.deepEqual(fetchMock.mock.calls[0]?.arguments, [
115
+ 'www.myApi.com/endpoint',
116
+ {
117
+ method: 'GET',
118
+ body: null,
119
+ signal: new AbortController().signal,
120
+ headers: {
121
+ Accept: 'application/schema+json; charset=UTF-8',
122
+ 'X-Custom-Provider-Header': 'value',
123
+ 'X-Provider-Credential-Header': 'apikey#1111',
124
+ 'X-Additional-Header': 'value1',
125
+ },
126
+ },
127
+ ]);
128
+
129
+ assert.deepEqual(actualResponse, { status: 200, headers: response.headers, body: { data: 'value' } });
130
+ });
131
+
132
+ it('should accept application/swagger+json type response', async context => {
133
+ const response = new Response('{"data": "value"}', {
134
+ status: 200,
135
+ headers: { 'Content-Type': 'application/swagger+json; charset=UTF-8' },
136
+ });
137
+
138
+ const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
139
+
140
+ const actualResponse = await provider.get('/endpoint', {
141
+ credentials: { apiKey: 'apikey#1111' },
142
+ logger: logger,
143
+ signal: new AbortController().signal,
144
+ additionnalheaders: { 'X-Additional-Header': 'value1', Accept: 'application/swagger+json; charset=UTF-8' },
145
+ });
146
+
147
+ assert.equal(fetchMock.mock.calls.length, 1);
148
+
149
+ assert.deepEqual(fetchMock.mock.calls[0]?.arguments, [
150
+ 'www.myApi.com/endpoint',
151
+ {
152
+ method: 'GET',
153
+ body: null,
154
+ signal: new AbortController().signal,
155
+ headers: {
156
+ Accept: 'application/swagger+json; charset=UTF-8',
157
+ 'X-Custom-Provider-Header': 'value',
158
+ 'X-Provider-Credential-Header': 'apikey#1111',
159
+ 'X-Additional-Header': 'value1',
160
+ },
161
+ },
162
+ ]);
163
+
164
+ assert.deepEqual(actualResponse, { status: 200, headers: response.headers, body: { data: 'value' } });
165
+ });
166
+
167
+ it('should accept application/vnd.oracle.resource+json type response', async context => {
168
+ const response = new Response('{"data": "value"}', {
169
+ status: 200,
170
+ headers: { 'Content-Type': 'application/vnd.oracle.resource+json; type=collection; charset=UTF-8' },
171
+ });
172
+
173
+ const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
174
+
175
+ const actualResponse = await provider.get('/endpoint', {
176
+ credentials: { apiKey: 'apikey#1111' },
177
+ logger: logger,
178
+ signal: new AbortController().signal,
179
+ additionnalheaders: { 'X-Additional-Header': 'value1' },
180
+ });
181
+
182
+ assert.equal(fetchMock.mock.calls.length, 1);
183
+
184
+ assert.deepEqual(fetchMock.mock.calls[0]?.arguments, [
185
+ 'www.myApi.com/endpoint',
186
+ {
187
+ method: 'GET',
188
+ body: null,
189
+ signal: new AbortController().signal,
190
+ headers: {
191
+ Accept: 'application/json',
192
+ 'X-Custom-Provider-Header': 'value',
193
+ 'X-Provider-Credential-Header': 'apikey#1111',
194
+ 'X-Additional-Header': 'value1',
195
+ },
196
+ },
197
+ ]);
198
+
199
+ assert.deepEqual(actualResponse, { status: 200, headers: response.headers, body: { data: 'value' } });
200
+ });
201
+
97
202
  it('should return the raw response body if specified', async context => {
98
203
  const response = new Response(`IMAGINE A HUGE PAYLOAD`, {
99
204
  status: 200,