@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.
@@ -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
- ['renderNoConent', 204],
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 = {json: jest.fn(), send: jest.fn(), setHeader: jest.fn(), statusCode: 200};
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 ms via getTime', async () => {
225
+ it ('returns Date instances as their epoch seconds', async () => {
225
226
  const date = new Date('2020-01-02T03:04:05Z');
226
- expect(await controller._serializeResponseBody(date, {})).toBe(date.getTime());
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: date.getTime(), name: 'foo'});
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, date.getTime(), {ts: date.getTime()}]);
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: date.getTime()});
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 ('returns "xml" when the Accept header requests xml', () => {
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('xml');
288
+ expect(controller.requestedFormat).toBe('json');
284
289
  });
285
290
 
286
- it ('lets the _format param override a conflicting Accept header', () => {
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 neither the param nor the header indicate a format', () => {
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 ('ignores an unrecognized _format and falls back to the Accept header', () => {
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('xml');
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 = {json: jest.fn(), send: jest.fn(), setHeader: jest.fn(), statusCode: 200};
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 = { json: jest.fn(), send: jest.fn(), setHeader: jest.fn(), end: jest.fn(), statusCode: 200 };
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(), json: jest.fn(), send: jest.fn(), setHeader: jest.fn()};
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 = { json: jest.fn(), send: jest.fn(), setHeader: jest.fn(), end: jest.fn(), statusCode: 200 };
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 = { json: jest.fn(), send: jest.fn(), setHeader: jest.fn(), end: jest.fn(), statusCode: 200 };
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 = { json: jest.fn(), send: jest.fn(), setHeader: jest.fn(), end: jest.fn(), statusCode: 200 };
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 = { json: jest.fn(), send: jest.fn(), setHeader: jest.fn(), end: jest.fn(), statusCode: 200 };
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 = { json: jest.fn(), send: jest.fn(), setHeader: jest.fn(), end: jest.fn(), statusCode: 200 };
590
+ const response = buildResponseMock({ end: jest.fn() });
590
591
  const controller = new C({}, response);
591
592
 
592
593
  await controller.performRequest('testAction');