@vida-global/core 2.1.1 → 2.2.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 +2 -6
- package/lib/activeRecord/baseRecord.js +151 -41
- package/lib/activeRecord/db/migrator.js +14 -0
- package/lib/activeRecord/db/queryInterface.js +1 -1
- package/lib/cache/memoryCache.js +31 -7
- package/lib/server/controllerMixins/renderer.js +51 -33
- package/lib/server/doc/renderer.md +1 -1
- package/lib/server/serverController.js +2 -1
- package/package.json +1 -1
- package/test/activeRecord/baseRecord.test.js +250 -9
- package/test/activeRecord/db/migrator.test.js +21 -0
- package/test/activeRecord/db/queryInterface.test.js +4 -2
- package/test/activeRecord/helpers/baseRecordMocks.js +23 -4
- package/test/cache/memoryCache.test.js +76 -0
- package/test/server/controllerMixins/callbacks.test.js +6 -5
- package/test/server/controllerMixins/renderer.test.js +74 -14
- package/test/server/helpers/response.js +19 -0
- package/test/server/serverController.test.js +8 -7
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { FooController } = require('../helpers/controllers/fooController');
|
|
2
|
+
const { buildResponseMock } = require('../helpers/response');
|
|
2
3
|
const TestHelpers = require('@vida-global/test-helpers');
|
|
3
4
|
const {
|
|
4
5
|
NoActiveStreamError,
|
|
@@ -76,7 +77,7 @@ describe('VidaServerController', () => {
|
|
|
76
77
|
const successHandlers = [
|
|
77
78
|
['renderCreationSuccessful', 201],
|
|
78
79
|
['renderAccepted', 202],
|
|
79
|
-
['
|
|
80
|
+
['renderNoContent', 204],
|
|
80
81
|
['renderMovedPermanently', 301],
|
|
81
82
|
['renderFound', 302],
|
|
82
83
|
['renderNotModified', 304],
|
|
@@ -159,7 +160,7 @@ describe('VidaServerController', () => {
|
|
|
159
160
|
let response;
|
|
160
161
|
let controller;
|
|
161
162
|
beforeEach(() => {
|
|
162
|
-
response =
|
|
163
|
+
response = buildResponseMock();
|
|
163
164
|
controller = new FooController({}, response);
|
|
164
165
|
});
|
|
165
166
|
|
|
@@ -221,30 +222,34 @@ describe('VidaServerController', () => {
|
|
|
221
222
|
expect(await controller._serializeResponseBody(true, {})).toBe(true);
|
|
222
223
|
});
|
|
223
224
|
|
|
224
|
-
it ('returns Date instances as their epoch
|
|
225
|
+
it ('returns Date instances as their epoch seconds', async () => {
|
|
225
226
|
const date = new Date('2020-01-02T03:04:05Z');
|
|
226
|
-
|
|
227
|
+
const seconds = Math.floor(date.getTime() / 1000);
|
|
228
|
+
expect(await controller._serializeResponseBody(date, {})).toBe(seconds);
|
|
227
229
|
});
|
|
228
230
|
|
|
229
231
|
it ('recursively processes Date values inside objects', async () => {
|
|
230
232
|
const date = new Date('2021-02-03T00:00:00Z');
|
|
233
|
+
const seconds = Math.floor(date.getTime() / 1000);
|
|
231
234
|
const result = await controller._serializeResponseBody({createdAt: date, name: 'foo'}, {});
|
|
232
|
-
expect(result).toEqual({createdAt:
|
|
235
|
+
expect(result).toEqual({createdAt: seconds, name: 'foo'});
|
|
233
236
|
});
|
|
234
237
|
|
|
235
238
|
it ('recursively processes arrays', async () => {
|
|
236
239
|
const date = new Date('2022-03-04T00:00:00Z');
|
|
240
|
+
const seconds = Math.floor(date.getTime() / 1000);
|
|
237
241
|
const result = await controller._serializeResponseBody([1, date, {ts: date}], {});
|
|
238
|
-
expect(result).toEqual([1,
|
|
242
|
+
expect(result).toEqual([1, seconds, {ts: seconds}]);
|
|
239
243
|
});
|
|
240
244
|
|
|
241
245
|
it ('invokes toApiResponse on objects that define it and recurses on the result', async () => {
|
|
242
246
|
const date = new Date('2023-04-05T00:00:00Z');
|
|
247
|
+
const seconds = Math.floor(date.getTime() / 1000);
|
|
243
248
|
const apiModel = {
|
|
244
249
|
async toApiResponse() { return {id: 1, createdAt: date}; }
|
|
245
250
|
};
|
|
246
251
|
const result = await controller._serializeResponseBody(apiModel, {});
|
|
247
|
-
expect(result).toEqual({id: 1, createdAt:
|
|
252
|
+
expect(result).toEqual({id: 1, createdAt: seconds});
|
|
248
253
|
});
|
|
249
254
|
|
|
250
255
|
it ('passes the options argument through to toApiResponse', async () => {
|
|
@@ -278,24 +283,24 @@ describe('VidaServerController', () => {
|
|
|
278
283
|
expect(controller.requestedFormat).toBe('json');
|
|
279
284
|
});
|
|
280
285
|
|
|
281
|
-
it ('
|
|
286
|
+
it ('ignores an xml Accept header and defaults to json', () => {
|
|
282
287
|
const controller = new FooController({headers: {accept: 'application/xml'}}, {});
|
|
283
|
-
expect(controller.requestedFormat).toBe('
|
|
288
|
+
expect(controller.requestedFormat).toBe('json');
|
|
284
289
|
});
|
|
285
290
|
|
|
286
|
-
it ('
|
|
291
|
+
it ('honors the _format param regardless of the Accept header', () => {
|
|
287
292
|
const controller = new FooController({query: {_format: 'json'}, headers: {accept: 'application/xml'}}, {});
|
|
288
293
|
expect(controller.requestedFormat).toBe('json');
|
|
289
294
|
});
|
|
290
295
|
|
|
291
|
-
it ('defaults to json when
|
|
296
|
+
it ('defaults to json when the param does not indicate a format', () => {
|
|
292
297
|
const controller = new FooController({}, {});
|
|
293
298
|
expect(controller.requestedFormat).toBe('json');
|
|
294
299
|
});
|
|
295
300
|
|
|
296
|
-
it ('
|
|
301
|
+
it ('defaults to json for an unrecognized _format, ignoring the Accept header', () => {
|
|
297
302
|
const controller = new FooController({query: {_format: 'yaml'}, headers: {accept: 'application/xml'}}, {});
|
|
298
|
-
expect(controller.requestedFormat).toBe('
|
|
303
|
+
expect(controller.requestedFormat).toBe('json');
|
|
299
304
|
});
|
|
300
305
|
});
|
|
301
306
|
|
|
@@ -329,7 +334,7 @@ describe('VidaServerController', () => {
|
|
|
329
334
|
describe('#render format dispatch', () => {
|
|
330
335
|
let response;
|
|
331
336
|
beforeEach(() => {
|
|
332
|
-
response =
|
|
337
|
+
response = buildResponseMock();
|
|
333
338
|
});
|
|
334
339
|
|
|
335
340
|
it ('renders JSON via response.json for a JSON request', async () => {
|
|
@@ -367,6 +372,18 @@ describe('VidaServerController', () => {
|
|
|
367
372
|
expect(sent).toContain('<status>ok</status>');
|
|
368
373
|
});
|
|
369
374
|
|
|
375
|
+
it ('wraps the body under a custom root element when xmlRootElement is overridden', async () => {
|
|
376
|
+
class CustomRootController extends FooController {
|
|
377
|
+
get xmlRootElement() { return 'Response'; }
|
|
378
|
+
}
|
|
379
|
+
const controller = new CustomRootController({query: {_format: 'xml'}}, response);
|
|
380
|
+
await controller.render({foo: 'bar'});
|
|
381
|
+
const sent = response.send.mock.calls[0][0];
|
|
382
|
+
expect(sent).toContain('<Response>');
|
|
383
|
+
expect(sent).toContain('</Response>');
|
|
384
|
+
expect(sent).not.toContain('<response>');
|
|
385
|
+
});
|
|
386
|
+
|
|
370
387
|
it ('renders array items as separate elements instead of concatenated text', async () => {
|
|
371
388
|
const controller = new FooController({query: {_format: 'xml'}}, response);
|
|
372
389
|
await controller.render({tags: ['one', 'two']});
|
|
@@ -384,6 +401,49 @@ describe('VidaServerController', () => {
|
|
|
384
401
|
});
|
|
385
402
|
|
|
386
403
|
|
|
404
|
+
describe('#render content-type', () => {
|
|
405
|
+
let response;
|
|
406
|
+
beforeEach(() => {
|
|
407
|
+
response = buildResponseMock();
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
const defaultTypeCases = [
|
|
411
|
+
['JSON', {}, 'application/json; charset=utf-8'],
|
|
412
|
+
['XML', {query: {_format: 'xml'}}, 'application/xml; charset=utf-8'],
|
|
413
|
+
];
|
|
414
|
+
it.each(defaultTypeCases)('sets the default %s content-type when none is set', async (_label, params, expected) => {
|
|
415
|
+
const controller = new FooController(params, response);
|
|
416
|
+
await controller.render({foo: 'bar'});
|
|
417
|
+
expect(response.setHeader).toHaveBeenCalledWith('content-type', expected);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it ('sets the default text content-type when none is set for a string body', async () => {
|
|
421
|
+
const controller = new FooController({}, response);
|
|
422
|
+
await controller.render('hello');
|
|
423
|
+
expect(response.setHeader).toHaveBeenCalledWith('content-type', 'text/plain; charset=utf-8');
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
const preexistingTypeCases = [
|
|
428
|
+
['JSON', {}],
|
|
429
|
+
['XML', {query: {_format: 'xml'}}],
|
|
430
|
+
['string', {}],
|
|
431
|
+
];
|
|
432
|
+
it.each(preexistingTypeCases)('does not override an already-set content-type for a %s render', async (label, params) => {
|
|
433
|
+
const customType = TestHelpers.Faker.Text.randomString();
|
|
434
|
+
response.setHeader('content-type', customType);
|
|
435
|
+
response.setHeader.mockClear();
|
|
436
|
+
|
|
437
|
+
const controller = new FooController(params, response);
|
|
438
|
+
const body = label === 'string' ? 'hello' : {foo: 'bar'};
|
|
439
|
+
await controller.render(body);
|
|
440
|
+
|
|
441
|
+
expect(response.setHeader).not.toHaveBeenCalledWith('content-type', expect.anything());
|
|
442
|
+
expect(response.getHeader('content-type')).toBe(customType);
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
|
|
387
447
|
describe('#_serializeArrayForResponseBody', () => {
|
|
388
448
|
class FakeRecord {
|
|
389
449
|
constructor(id) { this.id = id; }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/***************************************************************************************************
|
|
2
|
+
* MOCKS
|
|
3
|
+
***************************************************************************************************/
|
|
4
|
+
// A response double that keeps a real header store so getHeader reflects what setHeader wrote.
|
|
5
|
+
// This lets tests exercise the renderer's "don't override an already-set content type" logic.
|
|
6
|
+
function buildResponseMock(overrides = {}) {
|
|
7
|
+
const headers = {};
|
|
8
|
+
const response = {
|
|
9
|
+
json: jest.fn(),
|
|
10
|
+
send: jest.fn(),
|
|
11
|
+
setHeader: jest.fn((name, value) => { headers[`${name}`.toLowerCase()] = value; }),
|
|
12
|
+
getHeader: jest.fn((name) => headers[`${name}`.toLowerCase()]),
|
|
13
|
+
statusCode: 200,
|
|
14
|
+
};
|
|
15
|
+
return Object.assign(response, overrides);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
module.exports = { buildResponseMock };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { FooController } = require('./helpers/controllers/fooController');
|
|
2
2
|
const Errors = require('../../lib/server/errors');
|
|
3
3
|
const { randomPayload } = require('./helpers/serverController');
|
|
4
|
+
const { buildResponseMock } = require('./helpers/response');
|
|
4
5
|
const TestHelpers = require('@vida-global/test-helpers');
|
|
5
6
|
const {
|
|
6
7
|
VidaServerController,
|
|
@@ -338,7 +339,7 @@ describe('VidaServerController', () => {
|
|
|
338
339
|
});
|
|
339
340
|
|
|
340
341
|
it ('awaits render before calling response.end (regression: ERR_HTTP_HEADERS_SENT)', async () => {
|
|
341
|
-
const response = {
|
|
342
|
+
const response = buildResponseMock({ end: jest.fn() });
|
|
342
343
|
const controller = new FooController({}, response);
|
|
343
344
|
controller.testAction = jest.fn(async () => ({foo: TestHelpers.Faker.Text.randomString()}));
|
|
344
345
|
|
|
@@ -360,7 +361,7 @@ describe('VidaServerController', () => {
|
|
|
360
361
|
|
|
361
362
|
beforeEach(() => {
|
|
362
363
|
request = {headers: randomPayload()};
|
|
363
|
-
response = {headers: randomPayload(), statusCode: TestHelpers.Faker.Math.randomNumber()
|
|
364
|
+
response = buildResponseMock({headers: randomPayload(), statusCode: TestHelpers.Faker.Math.randomNumber()});
|
|
364
365
|
request.headers['content-type'] = TestHelpers.Faker.Text.randomString();
|
|
365
366
|
controller = new FooController(request, response);
|
|
366
367
|
});
|
|
@@ -524,7 +525,7 @@ describe('VidaServerController', () => {
|
|
|
524
525
|
namedBefore() { order.push('before2') }
|
|
525
526
|
testAction() { order.push('action'); return {} }
|
|
526
527
|
}
|
|
527
|
-
const response = {
|
|
528
|
+
const response = buildResponseMock({ end: jest.fn() });
|
|
528
529
|
const controller = new C({}, response);
|
|
529
530
|
|
|
530
531
|
await controller.performRequest('testAction');
|
|
@@ -541,7 +542,7 @@ describe('VidaServerController', () => {
|
|
|
541
542
|
namedAfter() { order.push('after2') }
|
|
542
543
|
testAction() { order.push('action'); return {} }
|
|
543
544
|
}
|
|
544
|
-
const response = {
|
|
545
|
+
const response = buildResponseMock({ end: jest.fn() });
|
|
545
546
|
const controller = new C({}, response);
|
|
546
547
|
|
|
547
548
|
await controller.performRequest('testAction');
|
|
@@ -554,7 +555,7 @@ describe('VidaServerController', () => {
|
|
|
554
555
|
setupCallbacks() { this.beforeCallback(() => false) }
|
|
555
556
|
}
|
|
556
557
|
C.prototype.testAction = testAction;
|
|
557
|
-
const response = {
|
|
558
|
+
const response = buildResponseMock({ end: jest.fn() });
|
|
558
559
|
const controller = new C({}, response);
|
|
559
560
|
|
|
560
561
|
await controller.performRequest('testAction');
|
|
@@ -570,7 +571,7 @@ describe('VidaServerController', () => {
|
|
|
570
571
|
}
|
|
571
572
|
testAction() { return {} }
|
|
572
573
|
}
|
|
573
|
-
const response = {
|
|
574
|
+
const response = buildResponseMock({ end: jest.fn() });
|
|
574
575
|
const controller = new C({}, response);
|
|
575
576
|
|
|
576
577
|
await controller.performRequest('testAction');
|
|
@@ -586,7 +587,7 @@ describe('VidaServerController', () => {
|
|
|
586
587
|
}
|
|
587
588
|
testAction() { return {} }
|
|
588
589
|
}
|
|
589
|
-
const response = {
|
|
590
|
+
const response = buildResponseMock({ end: jest.fn() });
|
|
590
591
|
const controller = new C({}, response);
|
|
591
592
|
|
|
592
593
|
await controller.performRequest('testAction');
|