@vida-global/core 2.0.11 → 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/lib/activeRecord/README.md +1 -1
- package/lib/activeRecord/baseRecord.js +70 -4
- 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/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,509 @@
|
|
|
1
|
+
const { FooController } = require('../helpers/controllers/fooController');
|
|
2
|
+
const { camelize } = require('inflection');
|
|
3
|
+
const Errors = require('../../../lib/server/errors');
|
|
4
|
+
const TestHelpers = require('@vida-global/test-helpers');
|
|
5
|
+
const { VidaServerController } = require('../../../lib/server/serverController');
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
describe('VidaServerController', () => {
|
|
9
|
+
describe('.validateParameters', () => {
|
|
10
|
+
const paramName1 = TestHelpers.Faker.Text.randomString();
|
|
11
|
+
const paramName2 = TestHelpers.Faker.Text.randomString();
|
|
12
|
+
const value1 = TestHelpers.Faker.Text.randomString();
|
|
13
|
+
const value2 = TestHelpers.Faker.Text.randomString();
|
|
14
|
+
const validationType1 = TestHelpers.Faker.Text.randomString();
|
|
15
|
+
const validationType2 = TestHelpers.Faker.Text.randomString();
|
|
16
|
+
const options1 = [TestHelpers.Faker.Text.randomString()];
|
|
17
|
+
const options2 = [TestHelpers.Faker.Text.randomString()];
|
|
18
|
+
const error1 = TestHelpers.Faker.Text.randomString()
|
|
19
|
+
const error2 = TestHelpers.Faker.Text.randomString()
|
|
20
|
+
|
|
21
|
+
let params;
|
|
22
|
+
let controller;
|
|
23
|
+
let response;
|
|
24
|
+
let validator1;
|
|
25
|
+
let validator2;
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
params = {[paramName1]: value1, [paramName2]: value2};
|
|
28
|
+
response = {json: jest.fn(), statusCode: 200};
|
|
29
|
+
controller = new FooController({params}, response);
|
|
30
|
+
controller.render = jest.fn();
|
|
31
|
+
validator1 = jest.fn();
|
|
32
|
+
validator2 = jest.fn();
|
|
33
|
+
controller[`validate${camelize(validationType1)}`] = validator1;
|
|
34
|
+
controller[`validate${camelize(validationType2)}`] = validator2;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const validations = {
|
|
38
|
+
[paramName1]: {[validationType1]: options1, [validationType2]: options2},
|
|
39
|
+
[paramName2]: {[validationType1]: options2}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
it ('runs validation checkers based on inputs', async () => {
|
|
43
|
+
await controller.validateParameters(validations);
|
|
44
|
+
expect(validator1).toHaveBeenCalledTimes(2);
|
|
45
|
+
expect(validator1).toHaveBeenCalledWith(value1, options1);
|
|
46
|
+
expect(validator1).toHaveBeenCalledWith(value2, options2);
|
|
47
|
+
|
|
48
|
+
expect(validator2).toHaveBeenCalledTimes(1);
|
|
49
|
+
expect(validator2).toHaveBeenCalledWith(value1, options2);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it ('throws a `ValidationError` when there\'s an error', async () => {
|
|
53
|
+
validator1.mockImplementation(() => error1);
|
|
54
|
+
await expect(controller.validateParameters(validations)).rejects.toThrow(Errors.ValidationError);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it ('attaches the failing parameter and error message to the ValidationError fields', async () => {
|
|
58
|
+
validator1.mockImplementation(() => error1);
|
|
59
|
+
await expect(controller.validateParameters(validations)).rejects.toMatchObject({
|
|
60
|
+
fields: {[paramName1]: [error1], [paramName2]: [error1]}
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it ('throws a `ValidationError` when there are multiple errors', async () => {
|
|
65
|
+
validator1.mockImplementation(() => error1);
|
|
66
|
+
validator2.mockImplementation(() => error2);
|
|
67
|
+
|
|
68
|
+
await expect(controller.validateParameters(validations)).rejects.toThrow(Errors.ValidationError);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it ('sets nothing when there is no error', async () => {
|
|
72
|
+
await controller.validateParameters(validations);
|
|
73
|
+
expect(controller.render).toHaveBeenCalledTimes(0);
|
|
74
|
+
expect(response.statusCode).toEqual(200);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it ('ignores details where there is no validator', async () => {
|
|
78
|
+
const validations = {
|
|
79
|
+
[paramName2]: {[validationType1]: options2, 'foo': true}
|
|
80
|
+
};
|
|
81
|
+
await controller.validateParameters(validations);
|
|
82
|
+
|
|
83
|
+
expect(validator1).toHaveBeenCalledTimes(1);
|
|
84
|
+
expect(validator1).toHaveBeenCalledWith(value2, options2);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it ('does not error when optional parameters are not included', async () => {
|
|
88
|
+
const validations = {foo: {isString: true, optional: true}};
|
|
89
|
+
await expect(controller.validateParameters(validations)).resolves.not.toThrow();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it ('does error when optional parameters are invalid', async () => {
|
|
93
|
+
const validations = {foo: {isString: {length: {gte: 2}}, optional: true}};
|
|
94
|
+
params.foo = '';
|
|
95
|
+
|
|
96
|
+
await expect(controller.validateParameters(validations)).rejects.toThrow(Errors.ValidationError);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
describe('.validateAction', () => {
|
|
102
|
+
it ('is a no-op when the controller has no parametersFor<Action> method', async () => {
|
|
103
|
+
class C extends VidaServerController {}
|
|
104
|
+
const controller = new C({}, {});
|
|
105
|
+
controller.validateParameters = jest.fn();
|
|
106
|
+
|
|
107
|
+
await controller.validateAction('getThing');
|
|
108
|
+
expect(controller.validateParameters).not.toHaveBeenCalled();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it ('calls validateParameters with the value returned by parametersFor<Action>', async () => {
|
|
112
|
+
const params = {foo: {isString: true}};
|
|
113
|
+
class C extends VidaServerController {
|
|
114
|
+
static parametersForGetThing() { return params; }
|
|
115
|
+
}
|
|
116
|
+
const controller = new C({}, {});
|
|
117
|
+
controller.validateParameters = jest.fn();
|
|
118
|
+
|
|
119
|
+
await controller.validateAction('getThing');
|
|
120
|
+
expect(controller.validateParameters).toHaveBeenCalledTimes(1);
|
|
121
|
+
expect(controller.validateParameters).toHaveBeenCalledWith(params);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it ('camelizes the action name when looking up the parametersFor method', async () => {
|
|
125
|
+
const params = {foo: {isString: true}};
|
|
126
|
+
class C extends VidaServerController {
|
|
127
|
+
static parametersForGetFooBar() { return params; }
|
|
128
|
+
}
|
|
129
|
+
const controller = new C({}, {});
|
|
130
|
+
controller.validateParameters = jest.fn();
|
|
131
|
+
|
|
132
|
+
await controller.validateAction('getFooBar');
|
|
133
|
+
expect(controller.validateParameters).toHaveBeenCalledWith(params);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it ('propagates ValidationError thrown by validateParameters', async () => {
|
|
137
|
+
class C extends VidaServerController {
|
|
138
|
+
static parametersForGetThing() { return {foo: {isString: true}}; }
|
|
139
|
+
}
|
|
140
|
+
const request = { params: {}, query: {}, body: {foo: 42} };
|
|
141
|
+
const controller = new C(request, {});
|
|
142
|
+
|
|
143
|
+
await expect(controller.validateAction('getThing')).rejects.toThrow(Errors.ValidationError);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
describe('.parametersForAction', () => {
|
|
149
|
+
it ('returns null when no parametersFor method is defined', () => {
|
|
150
|
+
class C extends VidaServerController {}
|
|
151
|
+
expect(C.parametersForAction('getThing')).toBe(null);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it ('returns the value of the matching parametersFor<Action> method', () => {
|
|
155
|
+
const params = {foo: {isString: true}};
|
|
156
|
+
class C extends VidaServerController {
|
|
157
|
+
static parametersForGetThing() { return params; }
|
|
158
|
+
}
|
|
159
|
+
expect(C.parametersForAction('getThing')).toBe(params);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
describe('.parametersMethodForAction', () => {
|
|
165
|
+
it ('returns the camelized parametersFor<Action> method name', () => {
|
|
166
|
+
class C extends VidaServerController {}
|
|
167
|
+
expect(C.parametersMethodForAction('getFooBar')).toBe('parametersForGetFooBar');
|
|
168
|
+
expect(C.parametersMethodForAction('postIndex')).toBe('parametersForPostIndex');
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
describe('.validatePresence', () => {
|
|
174
|
+
const controller = new FooController({}, {});
|
|
175
|
+
it ('returns undefined when there is a value', () => {
|
|
176
|
+
const result = controller.validatePresence(TestHelpers.Faker.Text.randomString());
|
|
177
|
+
expect(result).toBe(undefined);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it ('returns an error when there is no value', () => {
|
|
181
|
+
const result = controller.validatePresence();
|
|
182
|
+
expect(result).toEqual('required');
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
describe('.validateIsInteger', () => {
|
|
188
|
+
const controller = new FooController({}, {});
|
|
189
|
+
it ('returns undefined when there is an integer', () => {
|
|
190
|
+
const val = Math.ceil(Math.random() * 100);
|
|
191
|
+
const result = controller.validateIsInteger(val)
|
|
192
|
+
expect(result).toEqual(undefined);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it ('returns undefined when there is an integer string', () => {
|
|
196
|
+
const val = Math.ceil(Math.random() * 100).toString();
|
|
197
|
+
const result = controller.validateIsInteger(val)
|
|
198
|
+
expect(result).toEqual(undefined);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it ('returns an error when there is a non integer number', () => {
|
|
202
|
+
const val = Math.random() * 100;
|
|
203
|
+
const result = controller.validateIsInteger(val)
|
|
204
|
+
expect(result).toEqual('must be an integer');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it ('returns an error when there is a non integer string', () => {
|
|
208
|
+
// starts with a number because parseFloat converts a string to a float if it starts with a number
|
|
209
|
+
const val = '1' + TestHelpers.Faker.Text.randomString();
|
|
210
|
+
const result = controller.validateIsInteger(val)
|
|
211
|
+
expect(result).toEqual('must be an integer');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it ('returns an error when the value is null', () => {
|
|
215
|
+
const result = controller.validateIsInteger(null)
|
|
216
|
+
expect(result).toEqual('must be an integer');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it ('returns an error when the value is undefined', () => {
|
|
220
|
+
const result = controller.validateIsInteger(undefined)
|
|
221
|
+
expect(result).toEqual('must be an integer');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it ('returns an error when the value is an object', () => {
|
|
225
|
+
const result = controller.validateIsInteger({})
|
|
226
|
+
expect(result).toEqual('must be an integer');
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it ('returns an error when the number is less than the gte option', () => {
|
|
230
|
+
const result = controller.validateIsInteger(1, {gte: 2});
|
|
231
|
+
expect(result).toEqual('must be greater than or equal to 2');
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it ('returns undefined when the number is greater than the gte option', () => {
|
|
235
|
+
const result = controller.validateIsInteger(3, {gte: 2});
|
|
236
|
+
expect(result).toEqual(undefined);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it ('returns undefined when the number is equal to the gte option', () => {
|
|
240
|
+
const result = controller.validateIsInteger(2, {gte: 2});
|
|
241
|
+
expect(result).toEqual(undefined);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it ('returns an error when the number is greater than the lte option', () => {
|
|
245
|
+
const result = controller.validateIsInteger(3, {lte: 2});
|
|
246
|
+
expect(result).toEqual('must be less than or equal to 2');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it ('returns undefined when the number is less than the lte option', () => {
|
|
250
|
+
const result = controller.validateIsInteger(1, {lte: 2});
|
|
251
|
+
expect(result).toEqual(undefined);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it ('returns undefined when the number is equal to the lte option', () => {
|
|
255
|
+
const result = controller.validateIsInteger(2, {lte: 2});
|
|
256
|
+
expect(result).toEqual(undefined);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it ('supports both the lte and gte options', () => {
|
|
260
|
+
let result = controller.validateIsInteger(1, {lte: 2, gte: 0});
|
|
261
|
+
expect(result).toEqual(undefined);
|
|
262
|
+
|
|
263
|
+
result = controller.validateIsInteger(-1, {lte: 2, gte: 0});
|
|
264
|
+
expect(result).toEqual('must be greater than or equal to 0');
|
|
265
|
+
|
|
266
|
+
result = controller.validateIsInteger(3, {lte: 2, gte: 0});
|
|
267
|
+
expect(result).toEqual('must be less than or equal to 2');
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
describe('.validateIsString', () => {
|
|
273
|
+
const controller = new FooController({}, {});
|
|
274
|
+
it ('returns undefined when there is an string', () => {
|
|
275
|
+
const result = controller.validateIsString('')
|
|
276
|
+
expect(result).toEqual(undefined);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it ('returns an error when there is a number', () => {
|
|
280
|
+
const result = controller.validateIsString(Math.random());
|
|
281
|
+
expect(result).toEqual('must be a string');
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it ('returns an error when the value is null', () => {
|
|
285
|
+
const result = controller.validateIsString(null)
|
|
286
|
+
expect(result).toEqual('must be a string');
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it ('returns an error when the value is undefined', () => {
|
|
290
|
+
const result = controller.validateIsString(undefined)
|
|
291
|
+
expect(result).toEqual('must be a string');
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it ('returns an error when the value is an object', () => {
|
|
295
|
+
const result = controller.validateIsString({})
|
|
296
|
+
expect(result).toEqual('must be a string');
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it ('returns an error when the length is fewer than the gte option', () => {
|
|
300
|
+
const result = controller.validateIsString('f', {length: {gte: 2}});
|
|
301
|
+
expect(result).toEqual('must be greater than or equal to 2 characters');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it ('returns undefined when the length is greater than the gte option', () => {
|
|
305
|
+
const result = controller.validateIsString('foo', {length: {gte: 2}});
|
|
306
|
+
expect(result).toEqual(undefined);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it ('returns undefined when the length is equal to the gte option', () => {
|
|
310
|
+
const result = controller.validateIsString('fo', {length: {gte: 2}});
|
|
311
|
+
expect(result).toEqual(undefined);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it ('returns an error when the length is greater than the lte option', () => {
|
|
315
|
+
const result = controller.validateIsString('foo', {length: {lte: 2}});
|
|
316
|
+
expect(result).toEqual('must be fewer than or equal to 2 characters');
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it ('returns undefined when the length is fewer than the lte option', () => {
|
|
320
|
+
const result = controller.validateIsString('f', {length: {lte: 2}});
|
|
321
|
+
expect(result).toEqual(undefined);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it ('returns undefined when the length is equal to the lte option', () => {
|
|
325
|
+
const result = controller.validateIsString('fo', {length: {lte: 2}});
|
|
326
|
+
expect(result).toEqual(undefined);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it ('supports both the lte and gte options', () => {
|
|
330
|
+
let result = controller.validateIsString('fo', {length: {lte: 3, gte: 1}});
|
|
331
|
+
expect(result).toEqual(undefined);
|
|
332
|
+
|
|
333
|
+
result = controller.validateIsString('', {length: {lte: 3, gte: 1}});
|
|
334
|
+
expect(result).toEqual('must be greater than or equal to 1 characters');
|
|
335
|
+
|
|
336
|
+
result = controller.validateIsString('foobar', {length: {lte: 3, gte: 1}});
|
|
337
|
+
expect(result).toEqual('must be fewer than or equal to 3 characters');
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it ('returns undefined when the value matches the pattern', () => {
|
|
341
|
+
const result = controller.validateIsString('foo', {regex: /oo/})
|
|
342
|
+
expect(result).toBe(undefined);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it ('returns an error when the value does not match the pattern', () => {
|
|
346
|
+
const result = controller.validateIsString('bar', {regex: /oo/})
|
|
347
|
+
expect(result).toBe(`must match /oo/`);
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
describe('.validateIsDateTime', () => {
|
|
353
|
+
const controller = new FooController({}, {});
|
|
354
|
+
it ('returns undefined when passed a date string of format YYYY-MM-DD', () => {
|
|
355
|
+
const result = controller.validateIsDateTime('2025-12-17');
|
|
356
|
+
expect(result).toBe(undefined);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it ('returns undefined when passed a date string of format MM/DD/YY', () => {
|
|
360
|
+
const result = controller.validateIsDateTime('12/17/25');
|
|
361
|
+
expect(result).toBe(undefined);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it ('returns undefined when passed a date string of format Month name DD, YYYY', () => {
|
|
365
|
+
const result = controller.validateIsDateTime('December 17, 2025');
|
|
366
|
+
expect(result).toBe(undefined);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it ('returns undefined when passed a datetime string of format YYYY-MM-DD HH:MM:SS', () => {
|
|
370
|
+
const result = controller.validateIsDateTime('2025-12-17 01:05:00');
|
|
371
|
+
expect(result).toBe(undefined);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it ('returns undefined when passed a datetime string of format MM/DD/YY HH:MM:SS', () => {
|
|
375
|
+
const result = controller.validateIsDateTime('12/17/25 01:05:00');
|
|
376
|
+
expect(result).toBe(undefined);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it ('returns undefined when passed a datetime string of format Month name DD, YYYY HH:MM:SS', () => {
|
|
380
|
+
const result = controller.validateIsDateTime('December 17, 2025 01:05:00');
|
|
381
|
+
expect(result).toBe(undefined);
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it ('returns an error when passed an invalid date', () => {
|
|
385
|
+
const result = controller.validateIsDateTime('foo');
|
|
386
|
+
expect(result).toEqual('invalid date');
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
describe('.validateIsEnum', () => {
|
|
392
|
+
const controller = new FooController({}, {});
|
|
393
|
+
const enums = [Math.random(), Math.random(), Math.random()];
|
|
394
|
+
const error = TestHelpers.Faker.Text.randomString();
|
|
395
|
+
const options = { enums, error }
|
|
396
|
+
it ('returns undefined when passed a valid value', () => {
|
|
397
|
+
const result = controller.validateIsEnum(enums[2], options);
|
|
398
|
+
expect(result).toBe(undefined);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it ('returns an error when passed an invalid date', () => {
|
|
402
|
+
const result = controller.validateIsEnum('foo', options);
|
|
403
|
+
expect(result).toBe(error);
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
describe('.validateIsBoolean', () => {
|
|
409
|
+
const controller = new FooController({}, {});
|
|
410
|
+
it ('returns undefined when passed a matching boolean', () => {
|
|
411
|
+
const result1 = controller.validateIsBoolean(true, true);
|
|
412
|
+
const result2 = controller.validateIsBoolean(false, false);
|
|
413
|
+
expect(result1).toBe(undefined);
|
|
414
|
+
expect(result2).toBe(undefined);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
it ('returns an error when passed an non matching boolean', () => {
|
|
418
|
+
const result1 = controller.validateIsBoolean(true, false);
|
|
419
|
+
const result2 = controller.validateIsBoolean(false, true);
|
|
420
|
+
expect(result1).toBe('must be false');
|
|
421
|
+
expect(result2).toBe('must be true');
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it ('returns an error when passed an non boolean', () => {
|
|
425
|
+
const result1 = controller.validateIsBoolean('', false);
|
|
426
|
+
const result2 = controller.validateIsBoolean(0, false);
|
|
427
|
+
const result3 = controller.validateIsBoolean('foo', true);
|
|
428
|
+
const result4 = controller.validateIsBoolean(1, true);
|
|
429
|
+
expect(result1).toBe('must be false');
|
|
430
|
+
expect(result2).toBe('must be false');
|
|
431
|
+
expect(result3).toBe('must be true');
|
|
432
|
+
expect(result4).toBe('must be true');
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
describe('.validateIsNumber', () => {
|
|
438
|
+
const controller = new FooController({}, {});
|
|
439
|
+
it ('accepts integers and decimals', () => {
|
|
440
|
+
expect(controller.validateIsNumber('3')).toBeUndefined();
|
|
441
|
+
expect(controller.validateIsNumber('3.14')).toBeUndefined();
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it ('rejects non-numeric values', () => {
|
|
445
|
+
expect(controller.validateIsNumber('abc')).toEqual('must be a number');
|
|
446
|
+
expect(controller.validateIsNumber('abc123')).toEqual('must be a number');
|
|
447
|
+
expect(controller.validateIsNumber('123abc')).toEqual('must be a number');
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it ('enforces gte and lte bounds', () => {
|
|
451
|
+
expect(controller.validateIsNumber('0.5', { gte: 1 })).toEqual('must be greater than or equal to 1');
|
|
452
|
+
expect(controller.validateIsNumber('9.5', { lte: 9 })).toEqual('must be less than or equal to 9');
|
|
453
|
+
expect(controller.validateIsNumber('5', { gte: 1, lte: 9 })).toBeUndefined();
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
describe('.validateIsArray', () => {
|
|
459
|
+
const controller = new FooController({}, {});
|
|
460
|
+
it ('rejects non-array values', async () => {
|
|
461
|
+
await expect(controller.validateIsArray('nope')).resolves.toEqual('must be an array');
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it ('enforces length bounds', async () => {
|
|
465
|
+
await expect(controller.validateIsArray([], { length: { gte: 1 } })).resolves.toEqual('must have at least 1 items');
|
|
466
|
+
await expect(controller.validateIsArray([1, 2, 3], { length: { lte: 2 } })).resolves.toEqual('must have at most 2 items');
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it ('validates each element against the of definition', async () => {
|
|
470
|
+
await expect(controller.validateIsArray([1, 'x'], { of: { isInteger: true } }))
|
|
471
|
+
.resolves.toEqual('elements must be an integer');
|
|
472
|
+
await expect(controller.validateIsArray([1, 2], { of: { isInteger: true } })).resolves.toBeUndefined();
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
describe('.validateIsObject', () => {
|
|
478
|
+
const controller = new FooController({}, {});
|
|
479
|
+
it ('rejects non-object values', async () => {
|
|
480
|
+
await expect(controller.validateIsObject('nope')).resolves.toEqual('must be an object');
|
|
481
|
+
await expect(controller.validateIsObject(null)).resolves.toEqual('must be an object');
|
|
482
|
+
await expect(controller.validateIsObject([1, 2])).resolves.toEqual('must be an object');
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it ('validates declared properties', async () => {
|
|
486
|
+
const options = { properties: { age: { isInteger: true } } };
|
|
487
|
+
await expect(controller.validateIsObject({ age: 'old' }, options)).resolves.toEqual('age must be an integer');
|
|
488
|
+
await expect(controller.validateIsObject({ age: 30 }, options)).resolves.toBeUndefined();
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
describe('.validateFunction', () => {
|
|
494
|
+
const controller = new FooController({}, {});
|
|
495
|
+
const correctValue = Math.random();
|
|
496
|
+
const error = TestHelpers.Faker.Text.randomString();
|
|
497
|
+
const validator = (value) => value == correctValue ? undefined : error;
|
|
498
|
+
|
|
499
|
+
it ('returns undefined when passed a valid value', async () => {
|
|
500
|
+
const result = await controller.validateFunction(correctValue, validator);
|
|
501
|
+
expect(result).toBe(undefined);
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it ('returns an error when the function returns an error', async () => {
|
|
505
|
+
const result = await controller.validateFunction(Math.random(), validator);
|
|
506
|
+
expect(result).toBe(error);
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
});
|