@unito/integration-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.cjs +27 -0
- package/.nvmrc +1 -0
- package/.prettierignore +2 -0
- package/.prettierrc +7 -0
- package/LICENSE +3 -0
- package/README.md +16 -0
- package/dist/src/api/index.d.ts +2 -0
- package/dist/src/api/index.d.ts.map +1 -0
- package/dist/src/api/index.js +2 -0
- package/dist/src/api/index.js.map +1 -0
- package/dist/src/app/errors/HTTPError.d.ts +5 -0
- package/dist/src/app/errors/HTTPError.d.ts.map +1 -0
- package/dist/src/app/errors/HTTPError.js +8 -0
- package/dist/src/app/errors/HTTPError.js.map +1 -0
- package/dist/src/app/errors/HTTPNotFoundError.d.ts +5 -0
- package/dist/src/app/errors/HTTPNotFoundError.d.ts.map +1 -0
- package/dist/src/app/errors/HTTPNotFoundError.js +7 -0
- package/dist/src/app/errors/HTTPNotFoundError.js.map +1 -0
- package/dist/src/app/errors/HTTPUnprocessableEntityError.d.ts +5 -0
- package/dist/src/app/errors/HTTPUnprocessableEntityError.d.ts.map +1 -0
- package/dist/src/app/errors/HTTPUnprocessableEntityError.js +7 -0
- package/dist/src/app/errors/HTTPUnprocessableEntityError.js.map +1 -0
- package/dist/src/app/errors/index.d.ts +4 -0
- package/dist/src/app/errors/index.d.ts.map +1 -0
- package/dist/src/app/errors/index.js +4 -0
- package/dist/src/app/errors/index.js.map +1 -0
- package/dist/src/app/index.d.ts +6 -0
- package/dist/src/app/index.d.ts.map +1 -0
- package/dist/src/app/index.js +80 -0
- package/dist/src/app/index.js.map +1 -0
- package/dist/src/app/integration.d.ts +5 -0
- package/dist/src/app/integration.d.ts.map +1 -0
- package/dist/src/app/integration.js +86 -0
- package/dist/src/app/integration.js.map +1 -0
- package/dist/src/app/itemNode.d.ts +8 -0
- package/dist/src/app/itemNode.d.ts.map +1 -0
- package/dist/src/app/itemNode.js +13 -0
- package/dist/src/app/itemNode.js.map +1 -0
- package/dist/src/app/middlewares/withCorrelationId.d.ts +11 -0
- package/dist/src/app/middlewares/withCorrelationId.d.ts.map +1 -0
- package/dist/src/app/middlewares/withCorrelationId.js +8 -0
- package/dist/src/app/middlewares/withCorrelationId.js.map +1 -0
- package/dist/src/app/middlewares/withLogger.d.ts +12 -0
- package/dist/src/app/middlewares/withLogger.d.ts.map +1 -0
- package/dist/src/app/middlewares/withLogger.js +18 -0
- package/dist/src/app/middlewares/withLogger.js.map +1 -0
- package/dist/src/errors.d.ts +15 -0
- package/dist/src/errors.js +39 -0
- package/dist/src/handler.d.ts +67 -0
- package/dist/src/handler.js +224 -0
- package/dist/src/httpErrors.d.ts +22 -0
- package/dist/src/httpErrors.js +37 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/integration.d.ts +9 -0
- package/dist/src/integration.js +108 -0
- package/dist/src/middlewares/correlationId.d.ts +10 -0
- package/dist/src/middlewares/correlationId.js +6 -0
- package/dist/src/middlewares/credentials.d.ts +11 -0
- package/dist/src/middlewares/credentials.js +17 -0
- package/dist/src/middlewares/errors.d.ts +3 -0
- package/dist/src/middlewares/errors.js +22 -0
- package/dist/src/middlewares/filters.d.ts +16 -0
- package/dist/src/middlewares/filters.js +25 -0
- package/dist/src/middlewares/finish.d.ts +11 -0
- package/dist/src/middlewares/finish.js +10 -0
- package/dist/src/middlewares/logger.d.ts +12 -0
- package/dist/src/middlewares/logger.js +18 -0
- package/dist/src/middlewares/notFound.d.ts +3 -0
- package/dist/src/middlewares/notFound.js +8 -0
- package/dist/src/middlewares/selects.d.ts +10 -0
- package/dist/src/middlewares/selects.js +11 -0
- package/dist/src/resources/cache.d.ts +4 -0
- package/dist/src/resources/cache.d.ts.map +1 -0
- package/dist/src/resources/cache.js +25 -0
- package/dist/src/resources/cache.js.map +1 -0
- package/dist/src/resources/context.d.ts +78 -0
- package/dist/src/resources/context.js +2 -0
- package/dist/src/resources/logger.d.ts +14 -0
- package/dist/src/resources/logger.d.ts.map +1 -0
- package/dist/src/resources/logger.js +46 -0
- package/dist/src/resources/logger.js.map +1 -0
- package/dist/src/resources/provider.d.ts +36 -0
- package/dist/src/resources/provider.js +76 -0
- package/dist/test/errors.test.d.ts +1 -0
- package/dist/test/errors.test.js +16 -0
- package/dist/test/handler.test.d.ts +1 -0
- package/dist/test/handler.test.js +146 -0
- package/dist/test/middlewares/correlationId.test.d.ts +1 -0
- package/dist/test/middlewares/correlationId.test.js +19 -0
- package/dist/test/middlewares/credentials.test.d.ts +1 -0
- package/dist/test/middlewares/credentials.test.js +37 -0
- package/dist/test/middlewares/errors.test.d.ts +1 -0
- package/dist/test/middlewares/errors.test.js +64 -0
- package/dist/test/middlewares/filters.test.d.ts +1 -0
- package/dist/test/middlewares/filters.test.js +26 -0
- package/dist/test/middlewares/finish.test.d.ts +1 -0
- package/dist/test/middlewares/finish.test.js +68 -0
- package/dist/test/middlewares/logger.test.d.ts +1 -0
- package/dist/test/middlewares/logger.test.js +41 -0
- package/dist/test/middlewares/notFound.test.d.ts +1 -0
- package/dist/test/middlewares/notFound.test.js +27 -0
- package/dist/test/middlewares/selects.test.d.ts +1 -0
- package/dist/test/middlewares/selects.test.js +21 -0
- package/dist/test/resources/cache.test.d.ts +1 -0
- package/dist/test/resources/cache.test.js +25 -0
- package/dist/test/resources/logger.test.d.ts +1 -0
- package/dist/test/resources/logger.test.js +67 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +59 -0
- package/src/errors.ts +34 -0
- package/src/handler.ts +404 -0
- package/src/httpErrors.ts +44 -0
- package/src/index.ts +10 -0
- package/src/integration.ts +129 -0
- package/src/middlewares/correlationId.ts +19 -0
- package/src/middlewares/credentials.ts +35 -0
- package/src/middlewares/errors.ts +30 -0
- package/src/middlewares/filters.ts +51 -0
- package/src/middlewares/finish.ts +24 -0
- package/src/middlewares/logger.ts +36 -0
- package/src/middlewares/notFound.ts +13 -0
- package/src/middlewares/selects.ts +31 -0
- package/src/resources/cache.ts +34 -0
- package/src/resources/context.ts +113 -0
- package/src/resources/logger.ts +57 -0
- package/src/resources/provider.ts +120 -0
- package/test/errors.test.ts +17 -0
- package/test/handler.test.ts +178 -0
- package/test/middlewares/correlationId.test.ts +26 -0
- package/test/middlewares/credentials.test.ts +52 -0
- package/test/middlewares/errors.test.ts +78 -0
- package/test/middlewares/filters.test.ts +39 -0
- package/test/middlewares/finish.test.ts +81 -0
- package/test/middlewares/logger.test.ts +57 -0
- package/test/middlewares/notFound.test.ts +32 -0
- package/test/middlewares/selects.test.ts +34 -0
- package/test/resources/cache.test.ts +31 -0
- package/test/resources/logger.test.ts +80 -0
- package/tsconfig.json +29 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { WriteThroughCache, LocalCache } from 'cachette';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import * as uuid from 'uuid';
|
|
4
|
+
import Logger from './logger.js';
|
|
5
|
+
export function initializeCache() {
|
|
6
|
+
const cacheInstance = process.env.REDIS_URL ? new WriteThroughCache(process.env.REDIS_URL) : new LocalCache();
|
|
7
|
+
// Intended: the correlation id will be the same for all logs of Cachette.
|
|
8
|
+
const correlationId = uuid.v4();
|
|
9
|
+
const logger = new Logger({ correlation_id: correlationId });
|
|
10
|
+
cacheInstance
|
|
11
|
+
.on('info', message => {
|
|
12
|
+
logger.info(message);
|
|
13
|
+
})
|
|
14
|
+
.on('warn', message => {
|
|
15
|
+
logger.warn(message);
|
|
16
|
+
})
|
|
17
|
+
.on('error', message => {
|
|
18
|
+
logger.error(message);
|
|
19
|
+
});
|
|
20
|
+
return cacheInstance;
|
|
21
|
+
}
|
|
22
|
+
const shake256 = createHash('shake256');
|
|
23
|
+
export function generateCacheKey(value) {
|
|
24
|
+
return shake256.update(value).digest('hex');
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../../src/resources/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAiB,MAAM,UAAU,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,GAAG,MAAM,KAAK,CAAC;AAC3B,OAAO,MAAM,MAAM,aAAa,CAAC;AAEjC,MAAM,CAAC,IAAI,QAAuB,CAAC;AAInC,MAAM,UAAU,eAAe;IAC7B,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC;QAC1B,QAAQ,GAAG,IAAI,iBAAiB,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAC1D,CAAC;SAAM,CAAC;QACN,QAAQ,GAAG,IAAI,UAAU,EAAE,CAAC;IAC9B,CAAC;IAED,0EAA0E;IAC1E,MAAM,aAAa,GAAG,IAAI,CAAC,EAAE,EAAE,CAAC;IAEhC,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,cAAc,EAAE,aAAa,EAAE,CAAC,CAAC;IAE7D,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE;QAC5B,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE;QAC5B,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE;QAC7B,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAkB,EAAE,YAA+B,OAAO;IACzF,IAAI,QAAgB,CAAC;IAErB,QAAQ,SAAS,EAAE,CAAC;QAClB,KAAK,UAAU;YACb,QAAQ,GAAG,GAAG,UAAU,CAAC,UAAU,CAAC;iBACjC,MAAM,CAAC,KAAe,CAAC;iBACvB,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YACnB,MAAM;QACR,KAAK,MAAM;YACT,QAAQ,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YACxC,MAAM;QACR,KAAK,OAAO;YACV,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YACzC,MAAM;QACR,KAAK,OAAO;YACV,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YACzC,MAAM;QACR,KAAK,OAAO,CAAC;QACb;YACE,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as API from '@unito/integration-api';
|
|
2
|
+
import Logger from './logger.js';
|
|
3
|
+
import { Credentials } from '../middlewares/credentials.js';
|
|
4
|
+
import { Filter } from '../middlewares/filters.js';
|
|
5
|
+
type Context<P extends Record<string, string>, Q extends ParsedQueryString> = {
|
|
6
|
+
/**
|
|
7
|
+
* The parsed credentials associated with the request through the X-Unito-Credentials header.
|
|
8
|
+
*
|
|
9
|
+
* Will contain the keys for the variables defined in the corresponding configuration's authorization.
|
|
10
|
+
*/
|
|
11
|
+
credentials: Credentials;
|
|
12
|
+
/**
|
|
13
|
+
* The logger pre decorated with the correlation ID and the additionnal metadata provided through the request headers.
|
|
14
|
+
*/
|
|
15
|
+
logger: Logger;
|
|
16
|
+
/**
|
|
17
|
+
* The request params.
|
|
18
|
+
*
|
|
19
|
+
* A call to `/customers/:customerId/subscriptions/:subscriptionId` => `/customers/123/subscriptions/2` will yield:
|
|
20
|
+
*
|
|
21
|
+
* {
|
|
22
|
+
* customerId: '123',
|
|
23
|
+
* subscriptionId: '2'
|
|
24
|
+
* }
|
|
25
|
+
*/
|
|
26
|
+
params: P;
|
|
27
|
+
/**
|
|
28
|
+
* The raw query string parsed as an object.
|
|
29
|
+
*/
|
|
30
|
+
query: Q;
|
|
31
|
+
};
|
|
32
|
+
export type GetItemContext<P extends Record<string, string> = Record<string, never>, Q extends Record<string, string> = Record<string, never>> = Context<P, Q>;
|
|
33
|
+
export type GetCollectionContext<P extends Record<string, string> = Record<string, never>, Q extends Record<string, string> = Record<string, never>> = Context<P, Q> & {
|
|
34
|
+
/**
|
|
35
|
+
* Parsed filter query param yielding a list of filters.
|
|
36
|
+
*
|
|
37
|
+
* Given a filter query param:
|
|
38
|
+
* `filter=name=John,department.name=Engineering`
|
|
39
|
+
*
|
|
40
|
+
* Context.filters will be:
|
|
41
|
+
* [
|
|
42
|
+
* { field: 'name', operator: 'EQUAL', values: ['John'] },
|
|
43
|
+
* { field: 'department.name', operator: 'EQUAL', values: ['Engineering'] }
|
|
44
|
+
* ]
|
|
45
|
+
*/
|
|
46
|
+
filters: Filter[];
|
|
47
|
+
/**
|
|
48
|
+
* Parsed select query param yielding a list of fields to select.
|
|
49
|
+
*
|
|
50
|
+
* Given a select query param:
|
|
51
|
+
* `select=name,department.name`
|
|
52
|
+
*
|
|
53
|
+
* Context.selects will be:
|
|
54
|
+
* ['name', 'department.name']
|
|
55
|
+
*/
|
|
56
|
+
selects: string[];
|
|
57
|
+
};
|
|
58
|
+
export type CreateItemContext<P extends Record<string, string> = Record<string, never>, Q extends Record<string, string> = Record<string, never>, B extends API.CreateItemRequestPayload = API.CreateItemRequestPayload> = Context<P, Q> & {
|
|
59
|
+
body: B;
|
|
60
|
+
};
|
|
61
|
+
export type UpdateItemContext<P extends Record<string, string> = Record<string, never>, Q extends Record<string, string> = Record<string, never>, B extends API.UpdateItemRequestPayload = API.UpdateItemRequestPayload> = Context<P, Q> & {
|
|
62
|
+
body: B;
|
|
63
|
+
};
|
|
64
|
+
export type DeleteItemContext<P extends Record<string, string> = Record<string, never>, Q extends Record<string, string> = Record<string, never>> = Context<P, Q>;
|
|
65
|
+
export type GetCredentialAccountContext<P extends Record<string, string> = Record<string, never>, Q extends Record<string, string> = Record<string, never>> = Context<P, Q>;
|
|
66
|
+
export type ParseWebhooksContext<P extends Record<string, string> = Record<string, never>, Q extends Record<string, string> = Record<string, never>, B extends API.WebhookParseRequestPayload = API.WebhookParseRequestPayload> = Omit<Context<P, Q>, 'credentials'> & {
|
|
67
|
+
body: B;
|
|
68
|
+
};
|
|
69
|
+
export type UpdateWebhookSubscriptionsContext<P extends Record<string, string> = Record<string, never>, Q extends Record<string, string> = Record<string, never>, B extends API.WebhookSubscriptionRequestPayload = API.WebhookSubscriptionRequestPayload> = Context<P, Q> & {
|
|
70
|
+
body: B;
|
|
71
|
+
};
|
|
72
|
+
export type AckknowledgeWebhooksContext<P extends Record<string, string> = Record<string, never>, Q extends Record<string, string> = Record<string, never>, B extends API.WebhookParseRequestPayload = API.WebhookParseRequestPayload> = Omit<Context<P, Q>, 'credentials'> & {
|
|
73
|
+
body: B;
|
|
74
|
+
};
|
|
75
|
+
interface ParsedQueryString {
|
|
76
|
+
[key: string]: undefined | string | string[] | ParsedQueryString | ParsedQueryString[];
|
|
77
|
+
}
|
|
78
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export default class Logger {
|
|
2
|
+
private metadata;
|
|
3
|
+
constructor(metadata?: Record<string, unknown>);
|
|
4
|
+
private send;
|
|
5
|
+
log(data: unknown): void;
|
|
6
|
+
error(data: unknown): void;
|
|
7
|
+
warn(data: unknown): void;
|
|
8
|
+
info(data: unknown): void;
|
|
9
|
+
debug(data: unknown): void;
|
|
10
|
+
decorate(metadata: Record<string, unknown>): void;
|
|
11
|
+
getMetadata(): Record<string, unknown>;
|
|
12
|
+
setMetadata(key: string, value: unknown): void;
|
|
13
|
+
clearMetadata(): void;
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../src/resources/logger.ts"],"names":[],"mappings":"AAQA,MAAM,CAAC,OAAO,OAAO,MAAM;IACzB,OAAO,CAAC,QAAQ,CAA+B;gBAEnC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAM9C,OAAO,CAAC,IAAI;IAKL,GAAG,CAAC,IAAI,EAAE,OAAO;IAIjB,KAAK,CAAC,IAAI,EAAE,OAAO;IAInB,IAAI,CAAC,IAAI,EAAE,OAAO;IAIlB,IAAI,CAAC,IAAI,EAAE,OAAO;IAIlB,KAAK,CAAC,IAAI,EAAE,OAAO;IAInB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAI1C,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;IAInC,SAAS;CAGjB"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
var LogLevel;
|
|
2
|
+
(function (LogLevel) {
|
|
3
|
+
LogLevel["ERROR"] = "error";
|
|
4
|
+
LogLevel["WARN"] = "warn";
|
|
5
|
+
LogLevel["INFO"] = "info";
|
|
6
|
+
LogLevel["LOG"] = "log";
|
|
7
|
+
LogLevel["DEBUG"] = "debug";
|
|
8
|
+
})(LogLevel || (LogLevel = {}));
|
|
9
|
+
export default class Logger {
|
|
10
|
+
metadata = {};
|
|
11
|
+
constructor(metadata) {
|
|
12
|
+
if (metadata) {
|
|
13
|
+
this.metadata = structuredClone(metadata);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
send(logLevel, message) {
|
|
17
|
+
console[logLevel](JSON.stringify({ message, ...this.metadata }));
|
|
18
|
+
}
|
|
19
|
+
log(data) {
|
|
20
|
+
this.send(LogLevel.LOG, data);
|
|
21
|
+
}
|
|
22
|
+
error(data) {
|
|
23
|
+
this.send(LogLevel.ERROR, data);
|
|
24
|
+
}
|
|
25
|
+
warn(data) {
|
|
26
|
+
this.send(LogLevel.WARN, data);
|
|
27
|
+
}
|
|
28
|
+
info(data) {
|
|
29
|
+
this.send(LogLevel.INFO, data);
|
|
30
|
+
}
|
|
31
|
+
debug(data) {
|
|
32
|
+
this.send(LogLevel.DEBUG, data);
|
|
33
|
+
}
|
|
34
|
+
decorate(metadata) {
|
|
35
|
+
this.metadata = { ...this.metadata, ...metadata };
|
|
36
|
+
}
|
|
37
|
+
getMetadata() {
|
|
38
|
+
return structuredClone(this.metadata);
|
|
39
|
+
}
|
|
40
|
+
setMetadata(key, value) {
|
|
41
|
+
this.metadata[key] = value;
|
|
42
|
+
}
|
|
43
|
+
clearMetadata() {
|
|
44
|
+
this.metadata = {};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../../src/resources/logger.ts"],"names":[],"mappings":"AAAA,IAAK,QAMJ;AAND,WAAK,QAAQ;IACX,2BAAe,CAAA;IACf,yBAAa,CAAA;IACb,yBAAa,CAAA;IACb,uBAAW,CAAA;IACX,2BAAe,CAAA;AACjB,CAAC,EANI,QAAQ,KAAR,QAAQ,QAMZ;AAED,MAAM,CAAC,OAAO,OAAO,MAAM;IACjB,QAAQ,GAA4B,EAAE,CAAC;IAE/C,YAAY,QAAkC;QAC5C,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,CAAC,QAAQ,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IAEO,IAAI,CAAC,QAAkB,EAAE,OAAgB;QAC/C,sCAAsC;QACtC,OAAO,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IACnE,CAAC;IAEM,GAAG,CAAC,IAAa;QACtB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAChC,CAAC;IAEM,KAAK,CAAC,IAAa;QACxB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAClC,CAAC;IAEM,IAAI,CAAC,IAAa;QACvB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACjC,CAAC;IAEM,IAAI,CAAC,IAAa;QACvB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACjC,CAAC;IAEM,KAAK,CAAC,IAAa;QACxB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAClC,CAAC;IAEM,QAAQ,CAAC,QAAiC;QAC/C,IAAI,CAAC,QAAQ,GAAG,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,QAAQ,EAAE,CAAC;IACpD,CAAC;IAEM,OAAO,CAAC,GAAW,EAAE,KAAc;QACxC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IAC7B,CAAC;IAEM,SAAS;QACd,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC;IACrB,CAAC;CACF"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Credentials } from '../middlewares/credentials.js';
|
|
2
|
+
export interface RequestOptions {
|
|
3
|
+
credentials: Credentials;
|
|
4
|
+
queryParams?: {
|
|
5
|
+
[key: string]: string;
|
|
6
|
+
};
|
|
7
|
+
body?: Record<string, unknown>;
|
|
8
|
+
additionnalheaders?: {
|
|
9
|
+
[key: string]: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export interface Response<T> {
|
|
13
|
+
data: T;
|
|
14
|
+
headers: Headers;
|
|
15
|
+
}
|
|
16
|
+
export declare class Provider {
|
|
17
|
+
protected prepareRequest: ((context: {
|
|
18
|
+
credentials: Credentials;
|
|
19
|
+
}) => {
|
|
20
|
+
/**
|
|
21
|
+
* The base URL of the provider.
|
|
22
|
+
*/
|
|
23
|
+
url: string;
|
|
24
|
+
/**
|
|
25
|
+
* The additional headers to add to the request.
|
|
26
|
+
*/
|
|
27
|
+
headers: Record<string, string>;
|
|
28
|
+
}) | undefined;
|
|
29
|
+
constructor(options: {
|
|
30
|
+
prepareRequest: typeof Provider.prototype.prepareRequest;
|
|
31
|
+
});
|
|
32
|
+
get<T>(endpoint: string, options: RequestOptions): Promise<Response<T>>;
|
|
33
|
+
post<T>(endpoint: string, options: RequestOptions): Promise<Response<T>>;
|
|
34
|
+
patch<T>(endpoint: string, options: RequestOptions): Promise<Response<T>>;
|
|
35
|
+
private fetchWrapper;
|
|
36
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { handleErrorResponse as throwHttpError } from '../errors.js';
|
|
2
|
+
export class Provider {
|
|
3
|
+
prepareRequest = undefined;
|
|
4
|
+
constructor(options) {
|
|
5
|
+
this.prepareRequest = options.prepareRequest;
|
|
6
|
+
}
|
|
7
|
+
async get(endpoint, options) {
|
|
8
|
+
return this.fetchWrapper(endpoint, {
|
|
9
|
+
...options,
|
|
10
|
+
method: 'GET',
|
|
11
|
+
defaultHeaders: {
|
|
12
|
+
'Content-Type': 'application/json',
|
|
13
|
+
Accept: 'application/json',
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
async post(endpoint, options) {
|
|
18
|
+
return this.fetchWrapper(endpoint, {
|
|
19
|
+
...options,
|
|
20
|
+
method: 'POST',
|
|
21
|
+
defaultHeaders: {
|
|
22
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
23
|
+
Accept: 'application/json',
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
async patch(endpoint, options) {
|
|
28
|
+
return this.fetchWrapper(endpoint, {
|
|
29
|
+
...options,
|
|
30
|
+
method: 'PATCH',
|
|
31
|
+
defaultHeaders: {
|
|
32
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
33
|
+
Accept: 'application/json',
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async fetchWrapper(endpoint, options) {
|
|
38
|
+
if (!this.prepareRequest) {
|
|
39
|
+
throw new Error('Provider not initialized');
|
|
40
|
+
}
|
|
41
|
+
const { url: providerUrl, headers: providerHeaders } = this.prepareRequest({ credentials: options.credentials });
|
|
42
|
+
let absoluteUrl = [providerUrl, endpoint.charAt(0) === '/' ? endpoint.substring(1) : endpoint].join('/');
|
|
43
|
+
if (options.queryParams) {
|
|
44
|
+
absoluteUrl = `${absoluteUrl}?${new URLSearchParams(options.queryParams)}`;
|
|
45
|
+
}
|
|
46
|
+
const headers = { ...options.defaultHeaders, ...providerHeaders, ...options.additionnalheaders };
|
|
47
|
+
let body = null;
|
|
48
|
+
if (options.body) {
|
|
49
|
+
if (headers['Content-Type'] === 'application/x-www-form-urlencoded') {
|
|
50
|
+
body = new URLSearchParams(options.body).toString(); // this doesn't support objects!
|
|
51
|
+
}
|
|
52
|
+
else if (headers['Content-Type'] === 'application/json') {
|
|
53
|
+
body = JSON.stringify(options.body);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const callToProvider = async () => await fetch(absoluteUrl, {
|
|
57
|
+
method: options.method,
|
|
58
|
+
headers,
|
|
59
|
+
body: body,
|
|
60
|
+
});
|
|
61
|
+
// TODO: add back rate limiter
|
|
62
|
+
const response = await callToProvider();
|
|
63
|
+
if (response.status >= 400) {
|
|
64
|
+
const textResult = await response.text();
|
|
65
|
+
throwHttpError(response.status, textResult);
|
|
66
|
+
}
|
|
67
|
+
let data;
|
|
68
|
+
try {
|
|
69
|
+
data = await response.json();
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
throwHttpError(400, 'Invalid JSON response');
|
|
73
|
+
}
|
|
74
|
+
return { headers: response.headers, data };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
import * as errors from '../src/errors.js';
|
|
4
|
+
import * as httpErrors from '../src/httpErrors.js';
|
|
5
|
+
describe('handleErrorResponse', () => {
|
|
6
|
+
it('returns correct httpError given status code', () => {
|
|
7
|
+
assert.throws(() => errors.handleErrorResponse(400, 'bad request'), httpErrors.BadRequestError);
|
|
8
|
+
assert.throws(() => errors.handleErrorResponse(401, 'unauthorized'), httpErrors.UnauthorizedError);
|
|
9
|
+
assert.throws(() => errors.handleErrorResponse(403, 'forbidden'), httpErrors.UnauthorizedError);
|
|
10
|
+
assert.throws(() => errors.handleErrorResponse(404, 'not found'), httpErrors.NotFoundError);
|
|
11
|
+
assert.throws(() => errors.handleErrorResponse(408, 'timeout'), httpErrors.TimeoutError);
|
|
12
|
+
assert.throws(() => errors.handleErrorResponse(422, 'unprocessable entity'), httpErrors.UnprocessableEntityError);
|
|
13
|
+
assert.throws(() => errors.handleErrorResponse(429, 'rate limit exceeded'), httpErrors.RateLimitExceededError);
|
|
14
|
+
assert.throws(() => errors.handleErrorResponse(500, 'internal server error'), httpErrors.HttpError);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { afterEach, beforeEach, describe, it, mock } from 'node:test';
|
|
3
|
+
import { Handler } from '../src/handler.js';
|
|
4
|
+
import { BadRequestError } from '../src/httpErrors.js';
|
|
5
|
+
describe('Handler', () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
// Disable debug logging to keep the test output clean.
|
|
8
|
+
mock.method(global.console, 'debug', () => { });
|
|
9
|
+
});
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
mock.reset();
|
|
12
|
+
});
|
|
13
|
+
describe('constructor', () => {
|
|
14
|
+
it('returns a Handler', () => {
|
|
15
|
+
const itemHandler = new Handler('/', {});
|
|
16
|
+
assert.equal(itemHandler instanceof Handler, true);
|
|
17
|
+
});
|
|
18
|
+
it('validates path', () => {
|
|
19
|
+
const paths = [
|
|
20
|
+
['', false],
|
|
21
|
+
['invalid', false],
|
|
22
|
+
['/', true],
|
|
23
|
+
['/bar', true],
|
|
24
|
+
['/bar/', false],
|
|
25
|
+
['/foo/:bar', true],
|
|
26
|
+
['/foo/:bar/', false],
|
|
27
|
+
['/foo/:bar/spam/:baz', true],
|
|
28
|
+
['/foo/:bar/:spam', true],
|
|
29
|
+
];
|
|
30
|
+
for (const [path, valid] of paths) {
|
|
31
|
+
if (valid) {
|
|
32
|
+
assert.doesNotThrow(() => new Handler(path, {}));
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
assert.throws(() => new Handler(path, {}));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
describe('generate', () => {
|
|
41
|
+
async function executeHandler(handler, request = {}) {
|
|
42
|
+
let body = {};
|
|
43
|
+
let statusCode = 0;
|
|
44
|
+
const response = {
|
|
45
|
+
status: (receivedStatusCode) => ({
|
|
46
|
+
send: (receivedBody) => {
|
|
47
|
+
body = receivedBody;
|
|
48
|
+
statusCode = receivedStatusCode;
|
|
49
|
+
},
|
|
50
|
+
}),
|
|
51
|
+
statusCode: 0,
|
|
52
|
+
locals: {
|
|
53
|
+
credentials: { foo: 'bar' },
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
await handler(request, response);
|
|
57
|
+
return { body, statusCode };
|
|
58
|
+
}
|
|
59
|
+
it('returns a router', async () => {
|
|
60
|
+
const handler = new Handler('/foo/:bar', {
|
|
61
|
+
getCollection: () => Promise.resolve({ info: {}, data: [] }),
|
|
62
|
+
getItem: () => Promise.resolve({ fields: {}, relations: [] }),
|
|
63
|
+
createItem: () => Promise.resolve({ fields: {}, path: '/' }),
|
|
64
|
+
updateItem: () => Promise.resolve({ fields: {}, relations: [] }),
|
|
65
|
+
deleteItem: () => Promise.resolve(),
|
|
66
|
+
});
|
|
67
|
+
const router = handler.generate();
|
|
68
|
+
const routes = router.stack;
|
|
69
|
+
assert.equal(routes.length, 5);
|
|
70
|
+
assert.equal(routes[0].route.path, '/foo');
|
|
71
|
+
assert.equal(routes[0].route.methods.get, true);
|
|
72
|
+
assert.equal(routes[1].route.path, '/foo');
|
|
73
|
+
assert.equal(routes[1].route.methods.post, true);
|
|
74
|
+
assert.equal(routes[2].route.path, '/foo/:bar');
|
|
75
|
+
assert.equal(routes[2].route.methods.get, true);
|
|
76
|
+
assert.equal(routes[3].route.path, '/foo/:bar');
|
|
77
|
+
assert.equal(routes[3].route.methods.patch, true);
|
|
78
|
+
assert.equal(routes[4].route.path, '/foo/:bar');
|
|
79
|
+
assert.equal(routes[4].route.methods.delete, true);
|
|
80
|
+
// GetCollection.
|
|
81
|
+
assert.deepEqual(await executeHandler(routes[0].route.stack[0].handle), {
|
|
82
|
+
body: { info: {}, data: [] },
|
|
83
|
+
statusCode: 200,
|
|
84
|
+
});
|
|
85
|
+
// CreateItem.
|
|
86
|
+
assert.deepEqual(await executeHandler(routes[1].route.stack[0].handle, { body: { foo: 'bar' } }), {
|
|
87
|
+
body: { fields: {}, path: '/' },
|
|
88
|
+
statusCode: 201,
|
|
89
|
+
});
|
|
90
|
+
// GetItem.
|
|
91
|
+
assert.deepEqual(await executeHandler(routes[2].route.stack[0].handle), {
|
|
92
|
+
body: { fields: {}, relations: [] },
|
|
93
|
+
statusCode: 200,
|
|
94
|
+
});
|
|
95
|
+
// UpdateItem.
|
|
96
|
+
assert.deepEqual(await executeHandler(routes[3].route.stack[0].handle, { body: { foo: 'bar' } }), {
|
|
97
|
+
body: { fields: {}, relations: [] },
|
|
98
|
+
statusCode: 200,
|
|
99
|
+
});
|
|
100
|
+
// DeleteItem.
|
|
101
|
+
assert.deepEqual(await executeHandler(routes[4].route.stack[0].handle), {
|
|
102
|
+
body: null,
|
|
103
|
+
statusCode: 204,
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
it('uses pathWithIdentifier as is when appropriate', async () => {
|
|
107
|
+
const handler = new Handler('/foo/:bar/baz', {
|
|
108
|
+
getCollection: () => Promise.resolve({ info: {}, data: [] }),
|
|
109
|
+
});
|
|
110
|
+
const router = handler.generate();
|
|
111
|
+
const routes = router.stack;
|
|
112
|
+
assert.equal(routes.length, 1);
|
|
113
|
+
assert.equal(routes[0].route.path, '/foo/:bar/baz');
|
|
114
|
+
assert.equal(routes[0].route.methods.get, true);
|
|
115
|
+
// GetCollection.
|
|
116
|
+
assert.deepEqual(await executeHandler(routes[0].route.stack[0].handle), {
|
|
117
|
+
body: { info: {}, data: [] },
|
|
118
|
+
statusCode: 200,
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
it('undefined handlers', async () => {
|
|
122
|
+
const handler = new Handler('/foo/:bar', {});
|
|
123
|
+
const router = handler.generate();
|
|
124
|
+
const routes = router.stack;
|
|
125
|
+
assert.equal(routes.length, 0);
|
|
126
|
+
});
|
|
127
|
+
it('returns a 400 for invalid request payloads', async () => {
|
|
128
|
+
const handler = new Handler('/foo/:bar', {
|
|
129
|
+
createItem: () => Promise.resolve({ fields: {}, path: '/' }),
|
|
130
|
+
updateItem: () => Promise.resolve({ fields: {}, relations: [] }),
|
|
131
|
+
});
|
|
132
|
+
const router = handler.generate();
|
|
133
|
+
const routes = router.stack;
|
|
134
|
+
// CreateItemRequestPayload.
|
|
135
|
+
const createHandler = routes[0].route.stack[0].handle;
|
|
136
|
+
await assert.doesNotReject(async () => await executeHandler(createHandler, { body: { foo: 'bar' } }));
|
|
137
|
+
await assert.rejects(async () => await executeHandler(createHandler, { body: null }), BadRequestError);
|
|
138
|
+
await assert.rejects(async () => await executeHandler(createHandler, { body: 'not json' }), BadRequestError);
|
|
139
|
+
// UpdateItemRequestPayload.
|
|
140
|
+
const updateHandler = routes[0].route.stack[0].handle;
|
|
141
|
+
await assert.doesNotReject(async () => await executeHandler(updateHandler, { body: { foo: 'bar' } }));
|
|
142
|
+
await assert.rejects(async () => await executeHandler(updateHandler, { body: null }), BadRequestError);
|
|
143
|
+
await assert.rejects(async () => await executeHandler(updateHandler, { body: 'not json' }), BadRequestError);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
import middleware from '../../src/middlewares/correlationId.js';
|
|
4
|
+
describe('correlationId middleware', () => {
|
|
5
|
+
it('uses header', () => {
|
|
6
|
+
const request = { header: (_key) => '123' };
|
|
7
|
+
const response = { locals: {} };
|
|
8
|
+
middleware(request, response, () => { });
|
|
9
|
+
assert.deepEqual(response.locals, {
|
|
10
|
+
correlationId: '123',
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
it('fallback', () => {
|
|
14
|
+
const request = { header: (_key) => undefined };
|
|
15
|
+
const response = { locals: {} };
|
|
16
|
+
middleware(request, response, () => { });
|
|
17
|
+
assert(response.locals.correlationId);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
import middleware from '../../src/middlewares/credentials.js';
|
|
4
|
+
import { BadRequestError } from '../../src/httpErrors.js';
|
|
5
|
+
describe('credentials middleware', () => {
|
|
6
|
+
it('generates', async () => {
|
|
7
|
+
const credentials = Buffer.from(JSON.stringify({
|
|
8
|
+
accessToken: 'abc',
|
|
9
|
+
})).toString('base64');
|
|
10
|
+
const request = { header: (_key) => credentials };
|
|
11
|
+
const response = { locals: {} };
|
|
12
|
+
middleware(request, response, () => { });
|
|
13
|
+
assert.deepEqual(response.locals, {
|
|
14
|
+
credentials: {
|
|
15
|
+
accessToken: 'abc',
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
it('malformed header', async () => {
|
|
20
|
+
const request = { header: (_key) => 'nope' };
|
|
21
|
+
const response = { locals: {} };
|
|
22
|
+
assert.throws(() => middleware(request, response, () => { }), BadRequestError);
|
|
23
|
+
});
|
|
24
|
+
it('variables', async () => {
|
|
25
|
+
const credentials = Buffer.from(JSON.stringify({
|
|
26
|
+
apiKey: 'abc',
|
|
27
|
+
})).toString('base64');
|
|
28
|
+
const request = { header: (_key) => credentials };
|
|
29
|
+
const response = { locals: {} };
|
|
30
|
+
middleware(request, response, () => { });
|
|
31
|
+
assert.deepEqual(response.locals, {
|
|
32
|
+
credentials: {
|
|
33
|
+
apiKey: 'abc',
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
import middleware from '../../src/middlewares/errors.js';
|
|
4
|
+
import { HttpError } from '../../src/httpErrors.js';
|
|
5
|
+
describe('errors middleware', () => {
|
|
6
|
+
it('headers sent, do nothing', () => {
|
|
7
|
+
let actualStatus;
|
|
8
|
+
let actualJson;
|
|
9
|
+
const response = {
|
|
10
|
+
headersSent: true,
|
|
11
|
+
status: (code) => {
|
|
12
|
+
actualStatus = code;
|
|
13
|
+
return {
|
|
14
|
+
json: (json) => {
|
|
15
|
+
actualJson = json;
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
},
|
|
19
|
+
locals: { logger: { error: () => undefined } },
|
|
20
|
+
};
|
|
21
|
+
middleware(new Error(), {}, response, () => { });
|
|
22
|
+
assert.strictEqual(actualStatus, undefined);
|
|
23
|
+
assert.strictEqual(actualJson, undefined);
|
|
24
|
+
});
|
|
25
|
+
it('handles httpError', () => {
|
|
26
|
+
let actualStatus;
|
|
27
|
+
let actualJson;
|
|
28
|
+
const response = {
|
|
29
|
+
headersSent: false,
|
|
30
|
+
status: (code) => {
|
|
31
|
+
actualStatus = code;
|
|
32
|
+
return {
|
|
33
|
+
json: (json) => {
|
|
34
|
+
actualJson = json;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
locals: { logger: { error: () => undefined } },
|
|
39
|
+
};
|
|
40
|
+
middleware(new HttpError('httpError', 429), {}, response, () => { });
|
|
41
|
+
assert.strictEqual(actualStatus, 429);
|
|
42
|
+
assert.strictEqual(actualJson, 'httpError');
|
|
43
|
+
});
|
|
44
|
+
it('handles other error', () => {
|
|
45
|
+
let actualStatus;
|
|
46
|
+
let actualJson;
|
|
47
|
+
const response = {
|
|
48
|
+
headersSent: false,
|
|
49
|
+
status: (code) => {
|
|
50
|
+
actualStatus = code;
|
|
51
|
+
return {
|
|
52
|
+
json: (json) => {
|
|
53
|
+
actualJson = json;
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
locals: { logger: { error: () => undefined } },
|
|
58
|
+
};
|
|
59
|
+
middleware(new Error('error'), {}, response, () => { });
|
|
60
|
+
assert.strictEqual(actualStatus, 500);
|
|
61
|
+
assert.strictEqual(actualJson?.code, '500');
|
|
62
|
+
assert.deepEqual(actualJson?.originalError, { code: 'Error', message: 'error' });
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { OperatorType } from '@unito/integration-api';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { describe, it } from 'node:test';
|
|
4
|
+
import middleware from '../../src/middlewares/filters.js';
|
|
5
|
+
describe('filters middleware', () => {
|
|
6
|
+
it('data', () => {
|
|
7
|
+
const request = { query: { filter: 'status=active|pending,createdAt>2022-01-01,email!!' } };
|
|
8
|
+
const response = { locals: {} };
|
|
9
|
+
middleware(request, response, () => { });
|
|
10
|
+
assert.deepEqual(response.locals, {
|
|
11
|
+
filters: [
|
|
12
|
+
{ field: 'status', operator: OperatorType.EQUAL, values: ['active', 'pending'] },
|
|
13
|
+
{ field: 'createdAt', operator: OperatorType.GREATER_THAN, values: ['2022-01-01'] },
|
|
14
|
+
{ field: 'email', operator: OperatorType.IS_NOT_NULL, values: [] },
|
|
15
|
+
],
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
it('no data', () => {
|
|
19
|
+
const request = { query: {} };
|
|
20
|
+
const response = { locals: {} };
|
|
21
|
+
middleware(request, response, () => { });
|
|
22
|
+
assert.deepEqual(response.locals, {
|
|
23
|
+
filters: [],
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|