@vida-global/core 2.0.10 → 2.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/index.js +2 -0
- package/lib/activeRecord/README.md +1 -1
- package/lib/activeRecord/baseRecord.js +70 -4
- package/lib/cache/index.js +4 -0
- package/lib/cache/memoryCache.js +155 -0
- package/lib/server/README.md +8 -219
- package/lib/server/controllerImporter.js +0 -1
- package/lib/server/controllerMixins/callbacks.js +16 -61
- package/lib/server/controllerMixins/classScopedRegistry.js +48 -0
- package/lib/server/controllerMixins/documentation.js +41 -9
- package/lib/server/controllerMixins/renderer.js +140 -99
- package/lib/server/controllerMixins/validations.js +57 -0
- package/lib/server/doc/callbacks.md +23 -0
- package/lib/server/doc/documentation.md +135 -0
- package/lib/server/doc/renderer.md +115 -0
- package/lib/server/doc/requestDetails.md +52 -0
- package/lib/server/doc/validations.md +40 -0
- package/lib/server/index.js +1 -1
- package/lib/server/openApi/apiDocGenerator.js +426 -0
- package/lib/server/openApi/apiDocsGenerator.js +147 -0
- package/lib/server/openApi/schemaImporter.js +41 -0
- package/lib/server/openApi/schemaRegistry.js +43 -0
- package/lib/server/openApi/schemas.js +97 -0
- package/lib/server/openApi/tagRegistry.js +27 -0
- package/lib/server/server.js +14 -0
- package/lib/server/serverController.js +4 -9
- package/lib/server/statusTexts.js +38 -0
- package/lib/utils/yamlLoader.js +17 -0
- package/package.json +4 -2
- package/test/activeRecord/baseRecord.test.js +101 -0
- package/test/cache/memoryCache.test.js +233 -0
- package/test/server/apiDocGenerator.test.js +743 -0
- package/test/server/controllerMixins/callbacks.test.js +326 -0
- package/test/server/controllerMixins/classScopedRegistry.test.js +95 -0
- package/test/server/controllerMixins/documentation.test.js +168 -0
- package/test/server/controllerMixins/renderer.test.js +734 -0
- package/test/server/controllerMixins/requestDetails.test.js +225 -0
- package/test/server/controllerMixins/routing.test.js +82 -0
- package/test/server/controllerMixins/validations.test.js +509 -0
- package/test/server/openApi/apiDocsGenerator.test.js +306 -0
- package/test/server/openApi/helpers/apiDocsPackageFixture/package.json +5 -0
- package/test/server/openApi/schemaImporter.test.js +54 -0
- package/test/server/openApi/schemaRegistry.test.js +93 -0
- package/test/server/openApi/schemas.test.js +109 -0
- package/test/server/serverController.test.js +8 -867
- package/test/server/statusTexts.test.js +30 -0
- package/test/utils/yamlLoader.test.js +43 -0
- package/lib/server/apiDocsGenerator.js +0 -86
- package/test/server/apiDocsGenerator.test.js +0 -38
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
const { FooController } = require('../helpers/controllers/fooController');
|
|
2
|
+
const { randomPayload } = require('../helpers/serverController');
|
|
3
|
+
const TestHelpers = require('@vida-global/test-helpers');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
describe('VidaServerController', () => {
|
|
7
|
+
describe('Request Properties', () => {
|
|
8
|
+
let request;
|
|
9
|
+
let response;
|
|
10
|
+
let controller;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
request = {headers: randomPayload()};
|
|
14
|
+
response = {headers: randomPayload(), statusCode: TestHelpers.Faker.Math.randomNumber(), json: jest.fn()};
|
|
15
|
+
request.headers['content-type'] = TestHelpers.Faker.Text.randomString();
|
|
16
|
+
controller = new FooController(request, response);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('#requestHeaders', () => {
|
|
20
|
+
it ('returns a copy of the request headers', () => {
|
|
21
|
+
expect(controller.requestHeaders).not.toBe(request.headers);
|
|
22
|
+
expect(JSON.stringify(controller.requestHeaders)).toEqual(JSON.stringify(request.headers));
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
describe('#responseHeaders', () => {
|
|
28
|
+
it ('returns the request headers', () => {
|
|
29
|
+
expect(controller.responseHeaders).toBe(response.headers);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
describe('#contentType', () => {
|
|
35
|
+
it ('returns the content-type request header', () => {
|
|
36
|
+
expect(controller.contentType).toEqual(request.headers['content-type']);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
describe('#userAgent', () => {
|
|
42
|
+
it ('returns the user-agent request header', () => {
|
|
43
|
+
const ua = TestHelpers.Faker.Text.randomString();
|
|
44
|
+
request.headers['user-agent'] = ua;
|
|
45
|
+
expect(controller.userAgent).toEqual(ua);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
describe('#requestBody', () => {
|
|
51
|
+
it ('returns the underlying request body', () => {
|
|
52
|
+
const body = {foo: TestHelpers.Faker.Text.randomString()};
|
|
53
|
+
request.body = body;
|
|
54
|
+
expect(controller.requestBody).toBe(body);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
describe('#requestId', () => {
|
|
60
|
+
it ('returns the request id', () => {
|
|
61
|
+
request.id = TestHelpers.Faker.Text.randomString();
|
|
62
|
+
expect(controller.requestId).toEqual(request.id);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
describe('#requestIp', () => {
|
|
68
|
+
it ('returns the request ip', () => {
|
|
69
|
+
request.ip = TestHelpers.Faker.Text.randomString();
|
|
70
|
+
expect(controller.requestIp).toEqual(request.ip);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
describe('#requestMethod', () => {
|
|
76
|
+
it ('returns the request method', () => {
|
|
77
|
+
request.method = 'POST';
|
|
78
|
+
expect(controller.requestMethod).toEqual('POST');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
describe('#url', () => {
|
|
84
|
+
it ('returns the request originalUrl', () => {
|
|
85
|
+
request.originalUrl = `/${TestHelpers.Faker.Text.randomString()}`;
|
|
86
|
+
expect(controller.url).toEqual(request.originalUrl);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
describe('#statusCode', () => {
|
|
92
|
+
it ('returns the response status code', () => {
|
|
93
|
+
expect(controller.statusCode).toEqual(response.statusCode);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it ('sets the response status code', () => {
|
|
97
|
+
const code = TestHelpers.Faker.Math.randomNumber();
|
|
98
|
+
controller.statusCode = code;
|
|
99
|
+
expect(response.statusCode).toEqual(code);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
describe('#bearerToken', () => {
|
|
105
|
+
beforeEach(() => {
|
|
106
|
+
delete request.headers.authorization;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it ('returns null when there is no authorization header', () => {
|
|
110
|
+
expect(controller.bearerToken).toBe(null);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it ('returns null when the authorization header is empty', () => {
|
|
114
|
+
request.headers.authorization = '';
|
|
115
|
+
expect(controller.bearerToken).toBe(null);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it ('returns null when the authorization scheme is not bearer', () => {
|
|
119
|
+
request.headers.authorization = 'Basic dXNlcjpwYXNz';
|
|
120
|
+
expect(controller.bearerToken).toBe(null);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it ('extracts the token after a lowercase bearer scheme', () => {
|
|
124
|
+
request.headers.authorization = 'bearer abc123';
|
|
125
|
+
expect(controller.bearerToken).toBe('abc123');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it ('extracts the token regardless of bearer scheme casing', () => {
|
|
129
|
+
request.headers.authorization = 'Bearer abc123';
|
|
130
|
+
expect(controller.bearerToken).toBe('abc123');
|
|
131
|
+
|
|
132
|
+
const c2 = new FooController({headers: {authorization: 'BEARER xyz'}}, response);
|
|
133
|
+
expect(c2.bearerToken).toBe('xyz');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it ('tolerates leading whitespace and trims trailing whitespace from the token', () => {
|
|
137
|
+
request.headers.authorization = ' Bearer tokenValue ';
|
|
138
|
+
expect(controller.bearerToken).toBe('tokenValue');
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
describe('cookies / getCookie / setCookie', () => {
|
|
145
|
+
let request;
|
|
146
|
+
let response;
|
|
147
|
+
let controller;
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
beforeEach(() => {
|
|
151
|
+
request = { cookies: { sessionId: 'abc', theme: 'dark' } };
|
|
152
|
+
response = { cookie: jest.fn() };
|
|
153
|
+
controller = new FooController(request, response);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
describe('#cookies', () => {
|
|
158
|
+
it ('returns the cookies object from the request', () => {
|
|
159
|
+
expect(controller.cookies).toEqual({ sessionId: 'abc', theme: 'dark' });
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
it ('returns an empty object when request.cookies is undefined', () => {
|
|
164
|
+
controller = new FooController({}, response);
|
|
165
|
+
expect(controller.cookies).toEqual({});
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
describe('#getCookie', () => {
|
|
171
|
+
it ('returns the value for an existing cookie', () => {
|
|
172
|
+
expect(controller.getCookie('sessionId')).toBe('abc');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
it ('returns null for a missing cookie', () => {
|
|
177
|
+
expect(controller.getCookie('missing')).toBeNull();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
it ('returns null when name is empty', () => {
|
|
182
|
+
expect(controller.getCookie('')).toBeNull();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
it ('returns null when name is null', () => {
|
|
187
|
+
expect(controller.getCookie(null)).toBeNull();
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
describe('#setCookie', () => {
|
|
193
|
+
it ('invokes response.cookie with the given name, value, and options', () => {
|
|
194
|
+
const options = { path: '/foo', maxAge: 60000, httpOnly: true, secure: true, sameSite: 'None' };
|
|
195
|
+
controller.setCookie('sessionId', 'xyz', options);
|
|
196
|
+
expect(response.cookie).toHaveBeenCalledTimes(1);
|
|
197
|
+
expect(response.cookie).toHaveBeenCalledWith('sessionId', 'xyz', options);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
it ('defaults options to an empty object', () => {
|
|
202
|
+
controller.setCookie('sessionId', 'xyz');
|
|
203
|
+
expect(response.cookie).toHaveBeenCalledWith('sessionId', 'xyz', {});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
it ('coerces null/undefined value to an empty string', () => {
|
|
208
|
+
controller.setCookie('sessionId', null);
|
|
209
|
+
expect(response.cookie).toHaveBeenCalledWith('sessionId', '', {});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
it ('is a no-op when name is empty', () => {
|
|
214
|
+
controller.setCookie('', 'xyz');
|
|
215
|
+
expect(response.cookie).not.toHaveBeenCalled();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
it ('is a no-op when name is null', () => {
|
|
220
|
+
controller.setCookie(null, 'xyz');
|
|
221
|
+
expect(response.cookie).not.toHaveBeenCalled();
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const { FooController } = require('../helpers/controllers/fooController');
|
|
2
|
+
const { VidaServerController } = require('../../../lib/server/serverController');
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
describe('VidaServerController', () => {
|
|
6
|
+
describe('.actionNames', () => {
|
|
7
|
+
it ('returns actions based on method names', () => {
|
|
8
|
+
const names = FooController.actionNames;
|
|
9
|
+
const expected = ['postIndex',
|
|
10
|
+
'getFooBar',
|
|
11
|
+
'getBazBan',
|
|
12
|
+
'postFooBar',
|
|
13
|
+
'putFooBar',
|
|
14
|
+
'deleteFooBar',
|
|
15
|
+
'patchFooBar'];
|
|
16
|
+
expect(names).toEqual(expected);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
describe('._constructAction', () => {
|
|
22
|
+
class UsersController extends VidaServerController {
|
|
23
|
+
static get routes() {
|
|
24
|
+
return {getBazBan: '/baz/:id/ban'};
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
it ('returns an object with path, method, and action', () => {
|
|
29
|
+
const expected = {
|
|
30
|
+
method: 'GET',
|
|
31
|
+
path: '/users/fooBar',
|
|
32
|
+
action: 'getFooBar'
|
|
33
|
+
}
|
|
34
|
+
expect(UsersController._constructAction('getFooBar', '')).toEqual(expected);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it ('uses a blank path for index actions', () => {
|
|
38
|
+
const expected = {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
path: '/users',
|
|
41
|
+
action: 'postIndex'
|
|
42
|
+
}
|
|
43
|
+
expect(UsersController._constructAction('postIndex', '')).toEqual(expected);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it ('creates a single resource route for Record', () => {
|
|
47
|
+
const expected = {
|
|
48
|
+
method: 'GET',
|
|
49
|
+
path: '/user/:id',
|
|
50
|
+
action: 'getRecord'
|
|
51
|
+
}
|
|
52
|
+
expect(UsersController._constructAction('getRecord', '')).toEqual(expected);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it ('only uses Record when checking for single resource routes', () => {
|
|
56
|
+
const expected = {
|
|
57
|
+
method: 'GET',
|
|
58
|
+
path: '/users/recording',
|
|
59
|
+
action: 'getRecording'
|
|
60
|
+
}
|
|
61
|
+
expect(UsersController._constructAction('getRecording', '')).toEqual(expected);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it ('creates a single sub resource route for RecordFoo', () => {
|
|
65
|
+
const expected = {
|
|
66
|
+
method: 'GET',
|
|
67
|
+
path: '/user/:id/foo',
|
|
68
|
+
action: 'getRecordFoo'
|
|
69
|
+
}
|
|
70
|
+
expect(UsersController._constructAction('getRecordFoo', '')).toEqual(expected);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it ('uses a custom path when defined', () => {
|
|
74
|
+
const expected = {
|
|
75
|
+
method: 'GET',
|
|
76
|
+
path: '/baz/:id/ban',
|
|
77
|
+
action: 'getBazBan'
|
|
78
|
+
}
|
|
79
|
+
expect(UsersController._constructAction('getBazBan', '')).toEqual(expected);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|