@vida-global/core 1.3.0 → 1.3.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.
- package/.github/workflows/npm-publish.yml +18 -8
- package/.github/workflows/npm-test.yml +5 -3
- package/index.js +8 -13
- package/lib/activeRecord/db/schema.js +1 -1
- package/lib/redis/index.js +1 -1
- package/lib/server/README.md +1 -1
- package/lib/server/apiDocsGenerator.js +86 -0
- package/lib/server/controllerImporter.js +64 -0
- package/lib/server/index.js +2 -0
- package/lib/server/server.js +20 -38
- package/lib/server/serverController.js +135 -45
- package/package.json +1 -1
- package/test/server/server.test.js +5 -3
- package/test/server/serverController.test.js +175 -39
|
@@ -16,13 +16,23 @@ jobs:
|
|
|
16
16
|
publish:
|
|
17
17
|
runs-on: ubuntu-latest
|
|
18
18
|
steps:
|
|
19
|
-
-
|
|
20
|
-
|
|
19
|
+
- name: Checkout code
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- name: Setup Node
|
|
23
|
+
uses: actions/setup-node@v4
|
|
21
24
|
with:
|
|
22
|
-
node-version: '
|
|
25
|
+
node-version: '20'
|
|
23
26
|
registry-url: 'https://registry.npmjs.org'
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
-
|
|
27
|
+
|
|
28
|
+
- name: Update npm
|
|
29
|
+
run: npm install -g npm@latest
|
|
30
|
+
|
|
31
|
+
- name: Install dependencies
|
|
32
|
+
run: npm ci
|
|
33
|
+
|
|
34
|
+
- name: Run tests
|
|
35
|
+
run: npm test
|
|
36
|
+
|
|
37
|
+
- name: Publish
|
|
38
|
+
run: npm publish
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
# This workflow will run tests using node
|
|
2
|
+
|
|
3
|
+
|
|
1
4
|
name: npm test
|
|
2
5
|
|
|
3
6
|
on:
|
|
@@ -6,7 +9,6 @@ on:
|
|
|
6
9
|
jobs:
|
|
7
10
|
test:
|
|
8
11
|
runs-on: ubuntu-latest
|
|
9
|
-
|
|
10
12
|
steps:
|
|
11
13
|
- name: Checkout code
|
|
12
14
|
uses: actions/checkout@v4
|
|
@@ -14,8 +16,8 @@ jobs:
|
|
|
14
16
|
- name: Setup Node
|
|
15
17
|
uses: actions/setup-node@v4
|
|
16
18
|
with:
|
|
17
|
-
node-version: 20
|
|
18
|
-
|
|
19
|
+
node-version: '20'
|
|
20
|
+
registry-url: 'https://registry.npmjs.org'
|
|
19
21
|
|
|
20
22
|
- name: Install dependencies
|
|
21
23
|
run: npm ci
|
package/index.js
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const ActiveRecord = require('./lib/activeRecord');
|
|
7
|
-
const { redisClientFactory } = require('./lib/redis');
|
|
1
|
+
const httpLibs = require('./lib/http/client');
|
|
2
|
+
const serverLibs = require('./lib/server');
|
|
3
|
+
const { logger } = require('./lib/logger');
|
|
4
|
+
const ActiveRecord = require('./lib/activeRecord');
|
|
5
|
+
const redisLibs = require('./lib/redis');
|
|
8
6
|
|
|
9
7
|
|
|
10
8
|
module.exports = {
|
|
11
9
|
ActiveRecord,
|
|
12
|
-
|
|
13
|
-
HttpClient,
|
|
14
|
-
HttpError,
|
|
10
|
+
...httpLibs,
|
|
15
11
|
logger,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
VidaServerController,
|
|
12
|
+
...redisLibs,
|
|
13
|
+
...serverLibs
|
|
19
14
|
};
|
|
@@ -59,7 +59,7 @@ function resolveAutoIncrementColumns(details) {
|
|
|
59
59
|
if (!details.defaultValue) return;
|
|
60
60
|
|
|
61
61
|
const regExp = /^nextval\(\w+_seq::regclass\)$/;
|
|
62
|
-
if (details.defaultValue
|
|
62
|
+
if (regExp.test(details.defaultValue)) {
|
|
63
63
|
delete details.defaultValue;
|
|
64
64
|
delete details.allowNull;
|
|
65
65
|
details.autoIncrement = true;
|
package/lib/redis/index.js
CHANGED
package/lib/server/README.md
CHANGED
|
@@ -194,5 +194,5 @@ titleUniqueness(title) {
|
|
|
194
194
|
- `{isInteger: true}`, `{isInteger: {gte: 0, lte: 100}}`
|
|
195
195
|
- `{isDateTime}`
|
|
196
196
|
- `{isEnum: {enums: [a, b, c]}}`
|
|
197
|
-
- `{regex: /foo/}
|
|
197
|
+
- `{isString: true}`, `{isString: {length: {gte: 10, lte: 100}, regex: /foo/}}
|
|
198
198
|
- `{function: someFunction}` If the function returns a string, that is considered an error and returned as the validation message
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const { ControllerImporter } = require('./controllerImporter');
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ApiDocsGenerator {
|
|
5
|
+
#controllerDirectories
|
|
6
|
+
#controllerClasses;
|
|
7
|
+
#docs = {};
|
|
8
|
+
|
|
9
|
+
constructor(server) {
|
|
10
|
+
this.#controllerDirectories = server.controllerDirectories;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
generateDocs() {
|
|
15
|
+
console.log("\n\n");
|
|
16
|
+
this.controllerClasses.forEach(controllerClass => {
|
|
17
|
+
controllerClass.actions.forEach(action => {
|
|
18
|
+
this.generateDocsForAction(action, controllerClass);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
console.log("\n");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
generateDocsForAction(action, controllerClass) {
|
|
26
|
+
const documentationDetails = controllerClass.documentationForAction(action.action);
|
|
27
|
+
if (!documentationDetails?.description) return;
|
|
28
|
+
|
|
29
|
+
const components = this.collectComponents(controllerClass, documentationDetails, action);
|
|
30
|
+
|
|
31
|
+
const endpoint = components.endpoint;
|
|
32
|
+
this.#docs[endpoint] = this.#docs[endpoint] || {};
|
|
33
|
+
const actionDocs = this.#docs[endpoint][action.method.toLowerCase()] = {};
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
collectComponents(controllerClass, documentationDetails, action) {
|
|
39
|
+
const components = {...documentationDetails};
|
|
40
|
+
components.endpoint = this.formatEndpoint(action);
|
|
41
|
+
components.method = action.method;
|
|
42
|
+
|
|
43
|
+
const pathParamNames = components.endpoint.match(/{[^}]+}/g).map(p => {
|
|
44
|
+
return p.replace(/[{}]/g, '')
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const parameters = Object.entries(controllerClass.parametersForAction(action.action));
|
|
48
|
+
const pathParams = parameters.filter(([param, _]) => pathParamNames.includes(param));
|
|
49
|
+
const otherParams = parameters.filter(([param, _]) => !pathParamNames.includes(param));
|
|
50
|
+
console.log(components.endpoint);
|
|
51
|
+
console.log(pathParams);
|
|
52
|
+
console.log(components.method);
|
|
53
|
+
console.log(parameters);
|
|
54
|
+
console.log(components.description);
|
|
55
|
+
console.log(components.summary);
|
|
56
|
+
console.log("\n");
|
|
57
|
+
//const auth = controllerClass.authenticationDocumentation(action.action);
|
|
58
|
+
//const method = action.method;
|
|
59
|
+
|
|
60
|
+
return components;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
get controllerClasses() {
|
|
65
|
+
if (!this.#controllerClasses) {
|
|
66
|
+
const controllerImporter = new ControllerImporter(this.#controllerDirectories);
|
|
67
|
+
this.#controllerClasses = controllerImporter.controllerClasses;
|
|
68
|
+
}
|
|
69
|
+
return [...this.#controllerClasses];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
/***********************************************************************************************
|
|
74
|
+
* OPENAPI COMPONENTS
|
|
75
|
+
***********************************************************************************************/
|
|
76
|
+
formatEndpoint(action) {
|
|
77
|
+
const endpoint = action.path.replace(/:([^/]+)/g, '{$1}');
|
|
78
|
+
return endpoint;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
module.exports = {
|
|
85
|
+
ApiDocsGenerator
|
|
86
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const { VidaServerController } = require('./serverController');
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ControllerImporter {
|
|
6
|
+
#controllerDirectories;
|
|
7
|
+
#controllerClasses;
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
constructor(controllerDirectories) {
|
|
11
|
+
if (!Array.isArray(controllerDirectories)) {
|
|
12
|
+
controllerDirectories = [controllerDirectories];
|
|
13
|
+
}
|
|
14
|
+
this.#controllerDirectories = controllerDirectories;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
get controllerClasses() {
|
|
19
|
+
if (!this.#controllerClasses) {
|
|
20
|
+
this.#importControllerClasses();
|
|
21
|
+
}
|
|
22
|
+
return this.#controllerClasses;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
#importControllerClasses() {
|
|
27
|
+
this.#controllerClasses = [];
|
|
28
|
+
this.#controllerDirectories.forEach(dir => {
|
|
29
|
+
this.#importDirectory(dir, dir);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
#importDirectory(dir, topLevelDirectory) {
|
|
35
|
+
fs.readdirSync(dir).forEach((f => {
|
|
36
|
+
const filePath = `${dir}/${f}`;
|
|
37
|
+
if (fs.lstatSync(filePath).isDirectory()) {
|
|
38
|
+
this.#importDirectory(filePath, topLevelDirectory);
|
|
39
|
+
} else if (f.endsWith('.js')) {
|
|
40
|
+
this.#importFromFile(filePath, dir, topLevelDirectory);
|
|
41
|
+
}
|
|
42
|
+
}).bind(this));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
#importFromFile(filePath, dir, topLevelDirectory) {
|
|
47
|
+
const exports = Object.values(require(filePath));
|
|
48
|
+
exports.forEach(_export => {
|
|
49
|
+
if (_export.prototype instanceof VidaServerController) {
|
|
50
|
+
const directoryPrefix = dir.replace(topLevelDirectory, '');
|
|
51
|
+
_export.directoryPrefix = directoryPrefix;
|
|
52
|
+
_export.autoLoadHelpers();
|
|
53
|
+
_export.autoLoadDocumentation();
|
|
54
|
+
this.#controllerClasses.push(_export);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
module.exports = {
|
|
63
|
+
ControllerImporter
|
|
64
|
+
}
|
package/lib/server/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
const { VidaServer } = require('./server');
|
|
2
2
|
const { VidaServerController } = require('./serverController');
|
|
3
3
|
const ERRORS = require('./errors');
|
|
4
|
+
const { ApiDocsGenerator } = require('./apiDocsGenerator');
|
|
4
5
|
|
|
5
6
|
module.exports = {
|
|
7
|
+
ApiDocsGenerator,
|
|
6
8
|
...ERRORS,
|
|
7
9
|
VidaServer,
|
|
8
10
|
VidaServerController,
|
package/lib/server/server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
const { ControllerImporter } = require('./controllerImporter');
|
|
1
2
|
const express = require('express');
|
|
2
|
-
const fs = require('fs');
|
|
3
3
|
const httpLogger = require('pino-http')
|
|
4
4
|
const { logger } = require('../logger');
|
|
5
5
|
const mustacheExpress = require('mustache-express');
|
|
@@ -8,11 +8,6 @@ const responseTime = require('response-time');
|
|
|
8
8
|
const IoServer = require("socket.io")
|
|
9
9
|
const { Server } = require('http');
|
|
10
10
|
const { SystemController } = require('./systemController');
|
|
11
|
-
const { AuthorizationError,
|
|
12
|
-
ForbiddenError,
|
|
13
|
-
NotFoundError,
|
|
14
|
-
ValidationError,
|
|
15
|
-
VidaServerController } = require('./serverController');
|
|
16
11
|
|
|
17
12
|
|
|
18
13
|
class VidaServer {
|
|
@@ -32,11 +27,12 @@ class VidaServer {
|
|
|
32
27
|
}
|
|
33
28
|
|
|
34
29
|
|
|
35
|
-
async listen() {
|
|
30
|
+
async listen(callback) {
|
|
36
31
|
await this.registerControllers();
|
|
37
32
|
|
|
38
33
|
this.#httpServer.listen(this.#port, this.#host, () => {
|
|
39
34
|
this.logger.info(`Server is running on port ${this.#port}`);
|
|
35
|
+
if (callback) callback();
|
|
40
36
|
});
|
|
41
37
|
}
|
|
42
38
|
|
|
@@ -75,6 +71,7 @@ class VidaServer {
|
|
|
75
71
|
***********************************************************************************************/
|
|
76
72
|
setupMiddleware() {
|
|
77
73
|
this.use(this.jsonParsingMiddleware);
|
|
74
|
+
this.use(this.octetStreamParsingMiddleware);
|
|
78
75
|
this.use(responseTime())
|
|
79
76
|
this.use(express.static('public'))
|
|
80
77
|
this.use(this.loggingMiddleware);
|
|
@@ -97,6 +94,16 @@ class VidaServer {
|
|
|
97
94
|
}
|
|
98
95
|
|
|
99
96
|
|
|
97
|
+
get octetStreamParsingMiddleware() {
|
|
98
|
+
return express.raw({ type: 'application/octet-stream', limit: this.octetStreamLimit });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
get octetStreamLimit() {
|
|
103
|
+
return '128mb';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
100
107
|
get loggingMiddleware() {
|
|
101
108
|
return httpLogger({
|
|
102
109
|
logger: this.middlewareLogger,
|
|
@@ -193,7 +200,6 @@ class VidaServer {
|
|
|
193
200
|
|
|
194
201
|
|
|
195
202
|
#registerController(controllerCls) {
|
|
196
|
-
controllerCls.autoLoadHelpers();
|
|
197
203
|
controllerCls.actions.forEach((action => {
|
|
198
204
|
this.#registerAction(action, controllerCls)
|
|
199
205
|
}).bind(this));
|
|
@@ -215,6 +221,7 @@ class VidaServer {
|
|
|
215
221
|
_put() { this.#expressServer.put(...arguments); }
|
|
216
222
|
_delete() { this.#expressServer.delete(...arguments); }
|
|
217
223
|
_patch() { this.#expressServer.patch(...arguments); }
|
|
224
|
+
_head() { this.#expressServer.head(...arguments); }
|
|
218
225
|
|
|
219
226
|
|
|
220
227
|
requestHandler(action, controllerCls) {
|
|
@@ -232,41 +239,15 @@ class VidaServer {
|
|
|
232
239
|
|
|
233
240
|
|
|
234
241
|
get controllerClasses() {
|
|
235
|
-
if (this.#controllerClasses)
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
this.#importControllers(dir, dir);
|
|
240
|
-
});
|
|
242
|
+
if (!this.#controllerClasses) {
|
|
243
|
+
const controllerImporter = new ControllerImporter(this.controllerDirectories);
|
|
244
|
+
this.#controllerClasses = controllerImporter.controllerClasses;
|
|
245
|
+
}
|
|
241
246
|
|
|
242
247
|
return this.#controllerClasses;
|
|
243
248
|
}
|
|
244
249
|
|
|
245
250
|
|
|
246
|
-
#importControllers(dir, topLevelDirectory) {
|
|
247
|
-
fs.readdirSync(dir).forEach((f => {
|
|
248
|
-
const filePath = `${dir}/${f}`;
|
|
249
|
-
if (fs.lstatSync(filePath).isDirectory()) {
|
|
250
|
-
this.#importControllers(filePath, topLevelDirectory);
|
|
251
|
-
} else if (f.endsWith('.js')) {
|
|
252
|
-
this.#importControllersFromFile(filePath, dir, topLevelDirectory);
|
|
253
|
-
}
|
|
254
|
-
}).bind(this));
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
#importControllersFromFile(filePath, dir, topLevelDirectory) {
|
|
259
|
-
const exports = Object.values(require(filePath));
|
|
260
|
-
exports.forEach(_export => {
|
|
261
|
-
if (_export.prototype instanceof VidaServerController) {
|
|
262
|
-
const directoryPrefix = dir.replace(topLevelDirectory, '');
|
|
263
|
-
_export.directoryPrefix = directoryPrefix;
|
|
264
|
-
this.#controllerClasses.push(_export);
|
|
265
|
-
}
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
|
|
270
251
|
get controllerDirectories() {
|
|
271
252
|
return [`${process.cwd()}/controllers`];
|
|
272
253
|
}
|
|
@@ -291,6 +272,7 @@ class VidaServer {
|
|
|
291
272
|
post() { this.handleDeprecatedRoutingCall('post', arguments); }
|
|
292
273
|
put() { this.handleDeprecatedRoutingCall('put', arguments); }
|
|
293
274
|
patch() { this.handleDeprecatedRoutingCall('patch', arguments); }
|
|
275
|
+
head() { this.handleDeprecatedRoutingCall('head', arguments); }
|
|
294
276
|
delete() { this.handleDeprecatedRoutingCall('delete', arguments); }
|
|
295
277
|
|
|
296
278
|
|
|
@@ -7,14 +7,16 @@ const{ AuthorizationError,
|
|
|
7
7
|
ValidationError } = require('./errors');
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
const AUTH_CALLBACK_NAME = 'authenticateRequest';
|
|
11
|
+
|
|
12
|
+
|
|
10
13
|
class VidaServerController {
|
|
11
14
|
#afterCallbacks = [];
|
|
12
15
|
#beforeCallbacks = [];
|
|
16
|
+
#callbacksSetUp = false;
|
|
13
17
|
#params;
|
|
14
18
|
#rendered = false;
|
|
15
19
|
#request;
|
|
16
|
-
#requestBody;
|
|
17
|
-
#requestQuery;
|
|
18
20
|
#response;
|
|
19
21
|
|
|
20
22
|
|
|
@@ -24,6 +26,8 @@ class VidaServerController {
|
|
|
24
26
|
}
|
|
25
27
|
this.#request = request;
|
|
26
28
|
this.#response = response;
|
|
29
|
+
|
|
30
|
+
this.#applyCallbacks();
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
|
|
@@ -32,15 +36,15 @@ class VidaServerController {
|
|
|
32
36
|
|
|
33
37
|
get params() {
|
|
34
38
|
if (!this.#params) {
|
|
35
|
-
const body = this.#processRequestData('body');
|
|
36
|
-
|
|
37
|
-
this.#params = {...this._request.params, ...body, ...query};
|
|
39
|
+
const body = Buffer.isBuffer(this._request.body) ? {} : this.#processRequestData('body');
|
|
40
|
+
this.#params = {...this._request.params, ...this._request.query, ...body};
|
|
38
41
|
}
|
|
39
42
|
return structuredClone(this.#params);
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
get requestHeaders() { return structuredClone(this._request.headers || {}); }
|
|
43
46
|
get responseHeaders() { return this._response.headers; };
|
|
47
|
+
get requestBody() { return this._request.body; }
|
|
44
48
|
get contentType() { return this.requestHeaders['content-type']; }
|
|
45
49
|
get logger() { return logger; }
|
|
46
50
|
get rendered() { return this.#rendered }
|
|
@@ -51,23 +55,20 @@ class VidaServerController {
|
|
|
51
55
|
|
|
52
56
|
|
|
53
57
|
#processRequestData(key) {
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const processedData = {};
|
|
59
|
-
this[instanceVarName] = processedData;
|
|
60
|
-
for (let [paramName, paramValue] of Object.entries(data)) {
|
|
61
|
-
processedData[paramName] = this.#processedRequestDataValue(paramValue)
|
|
62
|
-
}
|
|
58
|
+
const data = this._request[key] || {};
|
|
59
|
+
const processedData = {};
|
|
60
|
+
for (let [paramName, paramValue] of Object.entries(data)) {
|
|
61
|
+
processedData[paramName] = this.#processedRequestDataValue(paramValue)
|
|
63
62
|
}
|
|
64
63
|
|
|
65
|
-
return
|
|
64
|
+
return processedData;
|
|
66
65
|
}
|
|
67
66
|
|
|
68
67
|
|
|
69
68
|
#processedRequestDataValue(value) {
|
|
69
|
+
return value;
|
|
70
70
|
try {
|
|
71
|
+
if (!/^\[|{/.test(value)) return value;
|
|
71
72
|
return JSON.parse(value);
|
|
72
73
|
} catch(err) {
|
|
73
74
|
return value;
|
|
@@ -95,8 +96,6 @@ class VidaServerController {
|
|
|
95
96
|
return;
|
|
96
97
|
}
|
|
97
98
|
|
|
98
|
-
this.setupCallbacks();
|
|
99
|
-
|
|
100
99
|
const responseBody = await this.#performAction(action);
|
|
101
100
|
|
|
102
101
|
if (!this.rendered) {
|
|
@@ -109,6 +108,10 @@ class VidaServerController {
|
|
|
109
108
|
|
|
110
109
|
async #performAction(action) {
|
|
111
110
|
if (await this.runBeforeCallbacks(action) === false) return false;
|
|
111
|
+
|
|
112
|
+
const parameters = this.constructor.parametersForAction(action);
|
|
113
|
+
if (parameters) await this.validateParameters(parameters);
|
|
114
|
+
|
|
112
115
|
const responseBody = await this[action]();
|
|
113
116
|
if (await this.runAfterCallbacks(action) === false) return false;
|
|
114
117
|
|
|
@@ -127,7 +130,7 @@ class VidaServerController {
|
|
|
127
130
|
await this.renderNotFoundResponse(err.message);
|
|
128
131
|
|
|
129
132
|
} else if (err instanceof ValidationError) {
|
|
130
|
-
await this.renderErrors(err.message, err.
|
|
133
|
+
await this.renderErrors(err.message, err.fields);
|
|
131
134
|
|
|
132
135
|
} else {
|
|
133
136
|
this.statusCode = 500;
|
|
@@ -144,7 +147,8 @@ class VidaServerController {
|
|
|
144
147
|
if (typeof body == 'string') {
|
|
145
148
|
this._response.send(body);
|
|
146
149
|
} else {
|
|
147
|
-
body = await this.
|
|
150
|
+
body = await this.processJSONBody(body, options);
|
|
151
|
+
body = this.formatJSONBody(body, options);
|
|
148
152
|
this._response.json(body);
|
|
149
153
|
}
|
|
150
154
|
this.#rendered = true;
|
|
@@ -191,24 +195,23 @@ class VidaServerController {
|
|
|
191
195
|
}
|
|
192
196
|
|
|
193
197
|
|
|
194
|
-
|
|
195
|
-
body = await this._formatJSONBody(body, options);
|
|
198
|
+
formatJSONBody(body, options) {
|
|
196
199
|
if (options.standardize === false) return body;
|
|
197
200
|
return {data: body, status: this.statusText};
|
|
198
201
|
}
|
|
199
202
|
|
|
200
203
|
|
|
201
|
-
async
|
|
204
|
+
async processJSONBody(body, options) {
|
|
202
205
|
if (body === undefined) return null;
|
|
203
206
|
if (!body || typeof body != 'object') return body;
|
|
204
207
|
|
|
205
208
|
if (body.toApiResponse) {
|
|
206
209
|
const formattedBody = await body.toApiResponse(options);
|
|
207
|
-
return await this.
|
|
210
|
+
return await this.processJSONBody(formattedBody, options);
|
|
208
211
|
|
|
209
212
|
} else if (Array.isArray(body)) {
|
|
210
213
|
for (const idx in body) {
|
|
211
|
-
body[idx] = await this.
|
|
214
|
+
body[idx] = await this.processJSONBody(body[idx], options);
|
|
212
215
|
}
|
|
213
216
|
|
|
214
217
|
} else if (body.constructor == Date) {
|
|
@@ -216,7 +219,7 @@ class VidaServerController {
|
|
|
216
219
|
|
|
217
220
|
} else {
|
|
218
221
|
for (let [key, val] of Object.entries(body)) {
|
|
219
|
-
body[key] = await this.
|
|
222
|
+
body[key] = await this.processJSONBody(val, options);
|
|
220
223
|
}
|
|
221
224
|
}
|
|
222
225
|
|
|
@@ -246,17 +249,7 @@ class VidaServerController {
|
|
|
246
249
|
* HELPERS
|
|
247
250
|
***********************************************************************************************/
|
|
248
251
|
static autoLoadHelpers() {
|
|
249
|
-
|
|
250
|
-
this._autoLoadHelpers()
|
|
251
|
-
} catch(err) {
|
|
252
|
-
if (err.message.includes(`Cannot find module '${this.autoLoadHelperPath}'`)) return;
|
|
253
|
-
throw err;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
static _autoLoadHelpers() {
|
|
259
|
-
const { InstanceMethods, Accessors } = require(this.autoLoadHelperPath);
|
|
252
|
+
const { InstanceMethods, Accessors } = this._autoLoadModule(this.autoLoadHelperPath);
|
|
260
253
|
|
|
261
254
|
if (InstanceMethods) Object.assign(this.prototype, InstanceMethods);
|
|
262
255
|
|
|
@@ -268,11 +261,34 @@ class VidaServerController {
|
|
|
268
261
|
}
|
|
269
262
|
|
|
270
263
|
|
|
264
|
+
static autoLoadDocumentation() {
|
|
265
|
+
const docs = this._autoLoadModule(this.autoLoadDocumentationPath);
|
|
266
|
+
Object.assign(this, docs);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
static _autoLoadModule(path) {
|
|
271
|
+
try {
|
|
272
|
+
return require(path);
|
|
273
|
+
} catch(err) {
|
|
274
|
+
const errorText = `Cannot find module '${path}'`;
|
|
275
|
+
if (!err.message.includes(errorText)) throw err;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return {};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
|
|
271
282
|
static get autoLoadHelperPath() {
|
|
272
283
|
return `${process.cwd()}/lib/controllers${this.routePrefix}Controller.js`;
|
|
273
284
|
}
|
|
274
285
|
|
|
275
286
|
|
|
287
|
+
static get autoLoadDocumentationPath() {
|
|
288
|
+
return `${process.cwd()}/lib/controllers/_docs${this.routePrefix}Controller.js`;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
|
|
276
292
|
/***********************************************************************************************
|
|
277
293
|
* CALLBACKS
|
|
278
294
|
***********************************************************************************************/
|
|
@@ -280,6 +296,13 @@ class VidaServerController {
|
|
|
280
296
|
setupCallbacks() {}
|
|
281
297
|
|
|
282
298
|
|
|
299
|
+
#applyCallbacks() {
|
|
300
|
+
// set up the callbacks when the instance is constructed and prevent it from being called again
|
|
301
|
+
this.setupCallbacks();
|
|
302
|
+
this.setupCallbacks = undefined;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
|
|
283
306
|
beforeCallback(callback, options={}) {
|
|
284
307
|
this.#addCallback(callback, options, this.#beforeCallbacks);
|
|
285
308
|
}
|
|
@@ -340,7 +363,7 @@ class VidaServerController {
|
|
|
340
363
|
/***********************************************************************************************
|
|
341
364
|
* VALIDATIONS
|
|
342
365
|
***********************************************************************************************/
|
|
343
|
-
|
|
366
|
+
validateParameters(validations) {
|
|
344
367
|
const errors = {};
|
|
345
368
|
for (const [parameterName, parameterValidations] of Object.entries(validations)) {
|
|
346
369
|
const parameterErrors = this.validateParameter(this.params[parameterName], parameterValidations);
|
|
@@ -353,8 +376,12 @@ class VidaServerController {
|
|
|
353
376
|
|
|
354
377
|
validateParameter(value, parameterValidations) {
|
|
355
378
|
const errors = [];
|
|
379
|
+
|
|
380
|
+
if (parameterValidations?.optional && value === undefined) return errors;
|
|
381
|
+
|
|
356
382
|
for (const [validationType, validationOptions] of Object.entries(parameterValidations)) {
|
|
357
383
|
const validationMethod = `validate${camelize(validationType)}`;
|
|
384
|
+
if (!this[validationMethod]) continue;
|
|
358
385
|
const error = this[validationMethod](value, validationOptions);
|
|
359
386
|
if (error) errors.push(error);
|
|
360
387
|
}
|
|
@@ -370,10 +397,29 @@ class VidaServerController {
|
|
|
370
397
|
|
|
371
398
|
validateIsInteger(value, options) {
|
|
372
399
|
const num = parseFloat(value);
|
|
373
|
-
if (
|
|
374
|
-
|
|
400
|
+
if (!/^-?\d+$/.test(value) || isNaN(num) || !Number.isInteger(num)) return 'must be an integer';
|
|
401
|
+
|
|
402
|
+
if (options?.gte !== undefined) {
|
|
375
403
|
if (num < options.gte) return `must be greater than or equal to ${options.gte}`;
|
|
376
404
|
}
|
|
405
|
+
if (options?.lte !== undefined) {
|
|
406
|
+
if (num > options.lte) return `must be less than or equal to ${options.lte}`;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
validateIsString(value, options) {
|
|
412
|
+
if (typeof value !== 'string') return 'must be a string';
|
|
413
|
+
|
|
414
|
+
if (options?.length?.gte !== undefined) {
|
|
415
|
+
if (value.length < options.length.gte) return `must be greater than or equal to ${options.length.gte} characters`;
|
|
416
|
+
}
|
|
417
|
+
if (options?.length?.lte !== undefined) {
|
|
418
|
+
if (value.length > options.length.lte) return `must be fewer than or equal to ${options.length.lte} characters`;
|
|
419
|
+
}
|
|
420
|
+
if (options?.regex !== undefined) {
|
|
421
|
+
if (!options.regex.test(value)) return `must match ${options.regex}`;
|
|
422
|
+
}
|
|
377
423
|
}
|
|
378
424
|
|
|
379
425
|
|
|
@@ -389,11 +435,6 @@ class VidaServerController {
|
|
|
389
435
|
}
|
|
390
436
|
|
|
391
437
|
|
|
392
|
-
validateRegex(value, regex) {
|
|
393
|
-
if (!regex.test(value)) return `must match the pattern ${regex}`;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
|
|
397
438
|
validateFunction(value, fnc) {
|
|
398
439
|
const error = fnc(value);
|
|
399
440
|
if (error) return error;
|
|
@@ -482,7 +523,56 @@ class VidaServerController {
|
|
|
482
523
|
|
|
483
524
|
|
|
484
525
|
static get actionRegExp() {
|
|
485
|
-
return new RegExp(/^(?<method>get|post|put|delete|patch)(?<path>[A-Z][a-zA-Z]+)$/);
|
|
526
|
+
return new RegExp(/^(?<method>get|post|put|delete|patch|head)(?<path>[A-Z][a-zA-Z]+)$/);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
/***********************************************************************************************
|
|
531
|
+
* DOCUMENTATION
|
|
532
|
+
***********************************************************************************************/
|
|
533
|
+
static parametersForAction(actionName) {
|
|
534
|
+
const methodName = this.parametersMethodForAction(actionName);
|
|
535
|
+
if (this[methodName]) return this[methodName]();
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
static parametersMethodForAction(actionName) {
|
|
541
|
+
return `parametersFor${camelize(actionName)}`;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
static documentationForAction(actionName) {
|
|
546
|
+
const methodName = this.documentationMethodForAction(actionName);
|
|
547
|
+
if (this[methodName]) return this[methodName]();
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
static documentationMethodForAction(actionName) {
|
|
553
|
+
return `document${camelize(actionName)}`;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
isAuthenticatedAction(actionName) {
|
|
558
|
+
const { options } = this.#beforeCallbacks.find(({ callback, options }) => callback == AUTH_CALLBACK_NAME)
|
|
559
|
+
return Boolean(options) && this.#shouldRunCallback(actionName, options);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
static isAuthenticatedAction(actionName) {
|
|
564
|
+
const controller = new this();
|
|
565
|
+
return controller.isAuthenticatedAction(actionName);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
static _authenticationDocumentation(actionName) {
|
|
570
|
+
if (!this.isAuthenticatedAction(actionName)) return undefined;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
static authenticationDocumentation(actionName) {
|
|
575
|
+
return {apiKeyAuth: []};
|
|
486
576
|
}
|
|
487
577
|
}
|
|
488
578
|
|
package/package.json
CHANGED
|
@@ -8,12 +8,14 @@ const { VidaServer } = require('../../lib/server');
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
jest.mock('express', () => {
|
|
11
|
+
const express = require('jest-express');
|
|
11
12
|
const { Response } = require('jest-express/lib/response');
|
|
12
13
|
|
|
13
|
-
// missing functions in mock
|
|
14
|
-
Response.prototype.on
|
|
14
|
+
// missing functions in mock express
|
|
15
|
+
Response.prototype.on = jest.fn();
|
|
16
|
+
express.raw = jest.fn();
|
|
15
17
|
|
|
16
|
-
return
|
|
18
|
+
return express;
|
|
17
19
|
});
|
|
18
20
|
|
|
19
21
|
|
|
@@ -165,20 +165,20 @@ describe('VidaServerController', () => {
|
|
|
165
165
|
expect(validator2).toHaveBeenCalledWith(value1, options2);
|
|
166
166
|
});
|
|
167
167
|
|
|
168
|
-
it ('throws a `ValidationError` when there\'s an error',
|
|
168
|
+
it ('throws a `ValidationError` when there\'s an error', () => {
|
|
169
169
|
validator1.mockImplementation(() => error1);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}).
|
|
170
|
+
expect(() => {
|
|
171
|
+
controller.validateParameters(validations);
|
|
172
|
+
}).toThrow(Errors.ValidationError);
|
|
173
173
|
});
|
|
174
174
|
|
|
175
|
-
it ('throws a `ValidationError` when there are multiple errors',
|
|
175
|
+
it ('throws a `ValidationError` when there are multiple errors', () => {
|
|
176
176
|
validator1.mockImplementation(() => error1);
|
|
177
177
|
validator2.mockImplementation(() => error2);
|
|
178
178
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}).
|
|
179
|
+
expect(() => {
|
|
180
|
+
controller.validateParameters(validations);
|
|
181
|
+
}).toThrow(Errors.ValidationError);
|
|
182
182
|
});
|
|
183
183
|
|
|
184
184
|
it ('sets nothing when there is no error', () => {
|
|
@@ -186,6 +186,34 @@ describe('VidaServerController', () => {
|
|
|
186
186
|
expect(controller.render).toHaveBeenCalledTimes(0);
|
|
187
187
|
expect(response.statusCode).toEqual(200);
|
|
188
188
|
});
|
|
189
|
+
|
|
190
|
+
it ('ignores details where there is no validator', () => {
|
|
191
|
+
const validations = {
|
|
192
|
+
[paramName2]: {[validationType1]: options2, 'foo': true}
|
|
193
|
+
};
|
|
194
|
+
controller.validateParameters(validations);
|
|
195
|
+
|
|
196
|
+
expect(validator1).toHaveBeenCalledTimes(1);
|
|
197
|
+
expect(validator1).toHaveBeenCalledWith(value2, options2);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it ('does not error when optional parameters are not included', () => {
|
|
201
|
+
const validations = {foo: {isString: true, optional: true}};
|
|
202
|
+
expect(() => {
|
|
203
|
+
controller.validateParameters(validations);
|
|
204
|
+
}).not.toThrow();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it ('does error when optional parameters are invalid', () => {
|
|
208
|
+
const validations = {foo: {isString: {length: {gte: 2}}, optional: true}};
|
|
209
|
+
params.foo = '';
|
|
210
|
+
|
|
211
|
+
expect(() => {
|
|
212
|
+
controller.validateParameters(validations);
|
|
213
|
+
}).toThrow(Errors.ValidationError);
|
|
214
|
+
|
|
215
|
+
delete params.foo;
|
|
216
|
+
});
|
|
189
217
|
});
|
|
190
218
|
|
|
191
219
|
|
|
@@ -230,6 +258,21 @@ describe('VidaServerController', () => {
|
|
|
230
258
|
expect(result).toEqual('must be an integer');
|
|
231
259
|
});
|
|
232
260
|
|
|
261
|
+
it ('returns an error when the value is null', () => {
|
|
262
|
+
const result = controller.validateIsInteger(null)
|
|
263
|
+
expect(result).toEqual('must be an integer');
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it ('returns an error when the value is undefined', () => {
|
|
267
|
+
const result = controller.validateIsInteger(undefined)
|
|
268
|
+
expect(result).toEqual('must be an integer');
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it ('returns an error when the value is an object', () => {
|
|
272
|
+
const result = controller.validateIsInteger({})
|
|
273
|
+
expect(result).toEqual('must be an integer');
|
|
274
|
+
});
|
|
275
|
+
|
|
233
276
|
it ('returns an error when the number is less than the gte option', () => {
|
|
234
277
|
const result = controller.validateIsInteger(1, {gte: 2});
|
|
235
278
|
expect(result).toEqual('must be greater than or equal to 2');
|
|
@@ -244,6 +287,112 @@ describe('VidaServerController', () => {
|
|
|
244
287
|
const result = controller.validateIsInteger(2, {gte: 2});
|
|
245
288
|
expect(result).toEqual(undefined);
|
|
246
289
|
});
|
|
290
|
+
|
|
291
|
+
it ('returns an error when the number is greater than the lte option', () => {
|
|
292
|
+
const result = controller.validateIsInteger(3, {lte: 2});
|
|
293
|
+
expect(result).toEqual('must be less than or equal to 2');
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it ('returns undefined when the number is less than the lte option', () => {
|
|
297
|
+
const result = controller.validateIsInteger(1, {lte: 2});
|
|
298
|
+
expect(result).toEqual(undefined);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it ('returns undefined when the number is equal to the lte option', () => {
|
|
302
|
+
const result = controller.validateIsInteger(2, {lte: 2});
|
|
303
|
+
expect(result).toEqual(undefined);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it ('supports both the lte and gte options', () => {
|
|
307
|
+
let result = controller.validateIsInteger(1, {lte: 2, gte: 0});
|
|
308
|
+
expect(result).toEqual(undefined);
|
|
309
|
+
|
|
310
|
+
result = controller.validateIsInteger(-1, {lte: 2, gte: 0});
|
|
311
|
+
expect(result).toEqual('must be greater than or equal to 0');
|
|
312
|
+
|
|
313
|
+
result = controller.validateIsInteger(3, {lte: 2, gte: 0});
|
|
314
|
+
expect(result).toEqual('must be less than or equal to 2');
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
describe('VidaServerController.validateIsString', () => {
|
|
320
|
+
const controller = new FooController({}, {});
|
|
321
|
+
it ('returns undefined when there is an string', () => {
|
|
322
|
+
const result = controller.validateIsString('')
|
|
323
|
+
expect(result).toEqual(undefined);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it ('returns an error when there is a number', () => {
|
|
327
|
+
const result = controller.validateIsString(Math.random());
|
|
328
|
+
expect(result).toEqual('must be a string');
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it ('returns an error when the value is null', () => {
|
|
332
|
+
const result = controller.validateIsString(null)
|
|
333
|
+
expect(result).toEqual('must be a string');
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it ('returns an error when the value is undefined', () => {
|
|
337
|
+
const result = controller.validateIsString(undefined)
|
|
338
|
+
expect(result).toEqual('must be a string');
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it ('returns an error when the value is an object', () => {
|
|
342
|
+
const result = controller.validateIsString({})
|
|
343
|
+
expect(result).toEqual('must be a string');
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it ('returns an error when the length is fewer than the gte option', () => {
|
|
347
|
+
const result = controller.validateIsString('f', {length: {gte: 2}});
|
|
348
|
+
expect(result).toEqual('must be greater than or equal to 2 characters');
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it ('returns undefined when the length is greater than the gte option', () => {
|
|
352
|
+
const result = controller.validateIsString('foo', {length: {gte: 2}});
|
|
353
|
+
expect(result).toEqual(undefined);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it ('returns undefined when the length is equal to the gte option', () => {
|
|
357
|
+
const result = controller.validateIsString('fo', {length: {gte: 2}});
|
|
358
|
+
expect(result).toEqual(undefined);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it ('returns an error when the length is greater than the lte option', () => {
|
|
362
|
+
const result = controller.validateIsString('foo', {length: {lte: 2}});
|
|
363
|
+
expect(result).toEqual('must be fewer than or equal to 2 characters');
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it ('returns undefined when the length is fewer than the lte option', () => {
|
|
367
|
+
const result = controller.validateIsString('f', {length: {lte: 2}});
|
|
368
|
+
expect(result).toEqual(undefined);
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it ('returns undefined when the length is equal to the lte option', () => {
|
|
372
|
+
const result = controller.validateIsString('fo', {length: {lte: 2}});
|
|
373
|
+
expect(result).toEqual(undefined);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it ('supports both the lte and gte options', () => {
|
|
377
|
+
let result = controller.validateIsString('fo', {length: {lte: 3, gte: 1}});
|
|
378
|
+
expect(result).toEqual(undefined);
|
|
379
|
+
|
|
380
|
+
result = controller.validateIsString('', {length: {lte: 3, gte: 1}});
|
|
381
|
+
expect(result).toEqual('must be greater than or equal to 1 characters');
|
|
382
|
+
|
|
383
|
+
result = controller.validateIsString('foobar', {length: {lte: 3, gte: 1}});
|
|
384
|
+
expect(result).toEqual('must be fewer than or equal to 3 characters');
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it ('returns undefined when the value matches the pattern', () => {
|
|
388
|
+
const result = controller.validateIsString('foo', {regex: /oo/})
|
|
389
|
+
expect(result).toBe(undefined);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it ('returns an error when the value does not match the pattern', () => {
|
|
393
|
+
const result = controller.validateIsString('bar', {regex: /oo/})
|
|
394
|
+
expect(result).toBe(`must match /oo/`);
|
|
395
|
+
});
|
|
247
396
|
});
|
|
248
397
|
|
|
249
398
|
|
|
@@ -321,36 +470,6 @@ describe('VidaServerController', () => {
|
|
|
321
470
|
});
|
|
322
471
|
|
|
323
472
|
|
|
324
|
-
describe('VidaServerController.validateRegex', () => {
|
|
325
|
-
const controller = new FooController({}, {});
|
|
326
|
-
|
|
327
|
-
it ('returns undefined when the value matches the pattern', () => {
|
|
328
|
-
const result = controller.validateRegex('foo', /oo/)
|
|
329
|
-
expect(result).toBe(undefined);
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
it ('returns an error when the value does not match the pattern', () => {
|
|
333
|
-
const result = controller.validateRegex('bar', /oo/)
|
|
334
|
-
expect(result).toBe(`must match the pattern /oo/`);
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
it ('returns an error when the value is null', () => {
|
|
338
|
-
const result = controller.validateRegex(null, /oo/)
|
|
339
|
-
expect(result).toBe(`must match the pattern /oo/`);
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
it ('returns an error when the value is undefined', () => {
|
|
343
|
-
const result = controller.validateRegex(undefined, /oo/)
|
|
344
|
-
expect(result).toBe(`must match the pattern /oo/`);
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
it ('returns an error when the value is an object', () => {
|
|
348
|
-
const result = controller.validateRegex({}, /oo/)
|
|
349
|
-
expect(result).toBe(`must match the pattern /oo/`);
|
|
350
|
-
});
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
|
|
354
473
|
describe('VidaServerController#performRequest', () => {
|
|
355
474
|
const errorHandlers = [
|
|
356
475
|
['renderUnauthorizedResponse', 'AuthorizationError'],
|
|
@@ -371,7 +490,7 @@ describe('VidaServerController', () => {
|
|
|
371
490
|
|
|
372
491
|
expect(controller[handlerName]).toHaveBeenCalledTimes(1);
|
|
373
492
|
if (handlerName == 'renderErrors') {
|
|
374
|
-
expect(controller[handlerName]).toHaveBeenCalledWith(message, error.
|
|
493
|
+
expect(controller[handlerName]).toHaveBeenCalledWith(message, error.fields);
|
|
375
494
|
} else {
|
|
376
495
|
expect(controller[handlerName]).toHaveBeenCalledWith(message);
|
|
377
496
|
}
|
|
@@ -594,4 +713,21 @@ describe('VidaServerController', () => {
|
|
|
594
713
|
});
|
|
595
714
|
});
|
|
596
715
|
});
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
describe('ServerController#setupCallbacks', () => {
|
|
719
|
+
it ('is only called once', () => {
|
|
720
|
+
class CallbacksTestController extends VidaServerController {}
|
|
721
|
+
CallbacksTestController.prototype.setupCallbacks = jest.fn();
|
|
722
|
+
const controller = new CallbacksTestController({}, {});
|
|
723
|
+
|
|
724
|
+
expect(CallbacksTestController.prototype.setupCallbacks).toHaveBeenCalledTimes(1);
|
|
725
|
+
expect(controller.setupCallbacks).toBe(undefined);
|
|
726
|
+
|
|
727
|
+
});
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
describe('Request Properties', () => {
|
|
732
|
+
});
|
|
597
733
|
});
|