UNPKG

37.5 kBJavaScriptView Raw
1'use strict'
2
3const t = require('tap')
4const test = t.test
5const sget = require('simple-get').concat
6const http = require('http')
7const NotFound = require('http-errors').NotFound
8const Reply = require('../../lib/reply')
9const { Writable } = require('readable-stream')
10const {
11 kReplyErrorHandlerCalled,
12 kReplyHeaders,
13 kReplySerializer,
14 kReplyIsError
15} = require('../../lib/symbols')
16
17test('Once called, Reply should return an object with methods', t => {
18 t.plan(13)
19 const response = { res: 'res' }
20 function context () {}
21 function request () {}
22 const reply = new Reply(response, context, request)
23 t.is(typeof reply, 'object')
24 t.is(typeof reply[kReplyIsError], 'boolean')
25 t.is(typeof reply[kReplyErrorHandlerCalled], 'boolean')
26 t.is(typeof reply.send, 'function')
27 t.is(typeof reply.code, 'function')
28 t.is(typeof reply.status, 'function')
29 t.is(typeof reply.header, 'function')
30 t.is(typeof reply.serialize, 'function')
31 t.is(typeof reply.getResponseTime, 'function')
32 t.is(typeof reply[kReplyHeaders], 'object')
33 t.strictEqual(reply.res, response)
34 t.strictEqual(reply.context, context)
35 t.strictEqual(reply.request, request)
36})
37
38test('reply.send throw with circular JSON', t => {
39 t.plan(1)
40 const response = {
41 setHeader: () => {},
42 hasHeader: () => false,
43 getHeader: () => undefined,
44 writeHead: () => {},
45 end: () => {}
46 }
47 const reply = new Reply(response, { onSend: [] }, null)
48 t.throws(() => {
49 var obj = {}
50 obj.obj = obj
51 reply.send(JSON.stringify(obj))
52 }, 'Converting circular structure to JSON')
53})
54
55test('reply.send returns itself', t => {
56 t.plan(1)
57 const response = {
58 setHeader: () => {},
59 hasHeader: () => false,
60 getHeader: () => undefined,
61 writeHead: () => {},
62 end: () => {}
63 }
64 const reply = new Reply(response, { onSend: [] }, null)
65 t.equal(reply.send('hello'), reply)
66})
67
68test('reply.serializer should set a custom serializer', t => {
69 t.plan(2)
70 const reply = new Reply(null, null, null)
71 t.equal(reply[kReplySerializer], null)
72 reply.serializer('serializer')
73 t.equal(reply[kReplySerializer], 'serializer')
74})
75
76test('reply.serialize should serialize payload', t => {
77 t.plan(1)
78 const response = { statusCode: 200 }
79 const context = {}
80 const reply = new Reply(response, context, null)
81 t.equal(reply.serialize({ foo: 'bar' }), '{"foo":"bar"}')
82})
83
84test('reply.serialize should serialize payload with a custom serializer', t => {
85 t.plan(2)
86 let customSerializerCalled = false
87 const response = { statusCode: 200 }
88 const context = {}
89 const reply = new Reply(response, context, null)
90 reply.serializer((x) => (customSerializerCalled = true) && JSON.stringify(x))
91 t.equal(reply.serialize({ foo: 'bar' }), '{"foo":"bar"}')
92 t.equal(customSerializerCalled, true, 'custom serializer not called')
93})
94
95test('reply.serialize should serialize payload with Fastify instance', t => {
96 t.plan(2)
97 const fastify = require('../..')()
98 fastify.route({
99 method: 'GET',
100 url: '/',
101 schema: {
102 response: {
103 200: {
104 type: 'object',
105 properties: {
106 foo: { type: 'string' }
107 }
108 }
109 }
110 },
111 handler: (req, reply) => {
112 reply.send(
113 reply.serialize({ foo: 'bar' })
114 )
115 }
116 })
117
118 fastify.inject({
119 method: 'GET',
120 url: '/'
121 }, (err, res) => {
122 t.error(err)
123 t.strictEqual(res.payload, '{"foo":"bar"}')
124 })
125})
126
127test('within an instance', t => {
128 const fastify = require('../..')()
129 const test = t.test
130
131 fastify.get('/', function (req, reply) {
132 reply.code(200)
133 reply.header('Content-Type', 'text/plain')
134 reply.send('hello world!')
135 })
136
137 fastify.get('/auto-type', function (req, reply) {
138 reply.code(200)
139 reply.type('text/plain')
140 reply.send('hello world!')
141 })
142
143 fastify.get('/auto-status-code', function (req, reply) {
144 reply.send('hello world!')
145 })
146
147 fastify.get('/redirect', function (req, reply) {
148 reply.redirect('/')
149 })
150
151 fastify.get('/redirect-code', function (req, reply) {
152 reply.redirect(301, '/')
153 })
154
155 fastify.get('/redirect-code-before-call', function (req, reply) {
156 reply.code(307).redirect('/')
157 })
158
159 fastify.get('/redirect-code-before-call-overwrite', function (req, reply) {
160 reply.code(307).redirect(302, '/')
161 })
162
163 fastify.get('/custom-serializer', function (req, reply) {
164 reply.code(200)
165 reply.type('text/plain')
166 reply.serializer(function (body) {
167 return require('querystring').stringify(body)
168 })
169 reply.send({ hello: 'world!' })
170 })
171
172 fastify.register(function (instance, options, next) {
173 fastify.addHook('onSend', function (req, reply, payload, next) {
174 reply.header('x-onsend', 'yes')
175 next()
176 })
177 fastify.get('/redirect-onsend', function (req, reply) {
178 reply.redirect('/')
179 })
180 next()
181 })
182
183 fastify.listen(0, err => {
184 t.error(err)
185 fastify.server.unref()
186
187 test('custom serializer should be used', t => {
188 t.plan(3)
189 sget({
190 method: 'GET',
191 url: 'http://localhost:' + fastify.server.address().port + '/custom-serializer'
192 }, (err, response, body) => {
193 t.error(err)
194 t.strictEqual(response.headers['content-type'], 'text/plain')
195 t.deepEqual(body.toString(), 'hello=world!')
196 })
197 })
198
199 test('status code and content-type should be correct', t => {
200 t.plan(4)
201 sget({
202 method: 'GET',
203 url: 'http://localhost:' + fastify.server.address().port
204 }, (err, response, body) => {
205 t.error(err)
206 t.strictEqual(response.statusCode, 200)
207 t.strictEqual(response.headers['content-type'], 'text/plain')
208 t.deepEqual(body.toString(), 'hello world!')
209 })
210 })
211
212 test('auto status code shoud be 200', t => {
213 t.plan(3)
214 sget({
215 method: 'GET',
216 url: 'http://localhost:' + fastify.server.address().port + '/auto-status-code'
217 }, (err, response, body) => {
218 t.error(err)
219 t.strictEqual(response.statusCode, 200)
220 t.deepEqual(body.toString(), 'hello world!')
221 })
222 })
223
224 test('auto type shoud be text/plain', t => {
225 t.plan(3)
226 sget({
227 method: 'GET',
228 url: 'http://localhost:' + fastify.server.address().port + '/auto-type'
229 }, (err, response, body) => {
230 t.error(err)
231 t.strictEqual(response.headers['content-type'], 'text/plain')
232 t.deepEqual(body.toString(), 'hello world!')
233 })
234 })
235
236 test('redirect to `/` - 1', t => {
237 t.plan(1)
238
239 http.get('http://localhost:' + fastify.server.address().port + '/redirect', function (response) {
240 t.strictEqual(response.statusCode, 302)
241 })
242 })
243
244 test('redirect to `/` - 2', t => {
245 t.plan(1)
246
247 http.get('http://localhost:' + fastify.server.address().port + '/redirect-code', function (response) {
248 t.strictEqual(response.statusCode, 301)
249 })
250 })
251
252 test('redirect to `/` - 3', t => {
253 t.plan(4)
254 sget({
255 method: 'GET',
256 url: 'http://localhost:' + fastify.server.address().port + '/redirect'
257 }, (err, response, body) => {
258 t.error(err)
259 t.strictEqual(response.statusCode, 200)
260 t.strictEqual(response.headers['content-type'], 'text/plain')
261 t.deepEqual(body.toString(), 'hello world!')
262 })
263 })
264
265 test('redirect to `/` - 4', t => {
266 t.plan(4)
267 sget({
268 method: 'GET',
269 url: 'http://localhost:' + fastify.server.address().port + '/redirect-code'
270 }, (err, response, body) => {
271 t.error(err)
272 t.strictEqual(response.statusCode, 200)
273 t.strictEqual(response.headers['content-type'], 'text/plain')
274 t.deepEqual(body.toString(), 'hello world!')
275 })
276 })
277
278 test('redirect to `/` - 5', t => {
279 t.plan(3)
280 const url = 'http://localhost:' + fastify.server.address().port + '/redirect-onsend'
281 http.get(url, (response) => {
282 t.strictEqual(response.headers['x-onsend'], 'yes')
283 t.strictEqual(response.headers['content-length'], '0')
284 t.strictEqual(response.headers.location, '/')
285 })
286 })
287
288 test('redirect to `/` - 6', t => {
289 t.plan(4)
290 sget({
291 method: 'GET',
292 url: 'http://localhost:' + fastify.server.address().port + '/redirect-code-before-call'
293 }, (err, response, body) => {
294 t.error(err)
295 t.strictEqual(response.statusCode, 200)
296 t.strictEqual(response.headers['content-type'], 'text/plain')
297 t.deepEqual(body.toString(), 'hello world!')
298 })
299 })
300
301 test('redirect to `/` - 7', t => {
302 t.plan(4)
303 sget({
304 method: 'GET',
305 url: 'http://localhost:' + fastify.server.address().port + '/redirect-code-before-call-overwrite'
306 }, (err, response, body) => {
307 t.error(err)
308 t.strictEqual(response.statusCode, 200)
309 t.strictEqual(response.headers['content-type'], 'text/plain')
310 t.deepEqual(body.toString(), 'hello world!')
311 })
312 })
313
314 test('redirect to `/` - 8', t => {
315 t.plan(1)
316
317 http.get('http://localhost:' + fastify.server.address().port + '/redirect-code-before-call', function (response) {
318 t.strictEqual(response.statusCode, 307)
319 })
320 })
321
322 test('redirect to `/` - 9', t => {
323 t.plan(1)
324
325 http.get('http://localhost:' + fastify.server.address().port + '/redirect-code-before-call-overwrite', function (response) {
326 t.strictEqual(response.statusCode, 302)
327 })
328 })
329
330 t.end()
331 })
332})
333
334test('buffer without content type should send a application/octet-stream and raw buffer', t => {
335 t.plan(4)
336
337 const fastify = require('../..')()
338
339 fastify.get('/', function (req, reply) {
340 reply.send(Buffer.alloc(1024))
341 })
342
343 fastify.listen(0, err => {
344 t.error(err)
345 fastify.server.unref()
346
347 sget({
348 method: 'GET',
349 url: 'http://localhost:' + fastify.server.address().port
350 }, (err, response, body) => {
351 t.error(err)
352 t.strictEqual(response.headers['content-type'], 'application/octet-stream')
353 t.deepEqual(body, Buffer.alloc(1024))
354 })
355 })
356})
357
358test('buffer with content type should not send application/octet-stream', t => {
359 t.plan(4)
360
361 const fastify = require('../..')()
362
363 fastify.get('/', function (req, reply) {
364 reply.header('Content-Type', 'text/plain')
365 reply.send(Buffer.alloc(1024))
366 })
367
368 fastify.listen(0, err => {
369 t.error(err)
370 fastify.server.unref()
371
372 sget({
373 method: 'GET',
374 url: 'http://localhost:' + fastify.server.address().port
375 }, (err, response, body) => {
376 t.error(err)
377 t.strictEqual(response.headers['content-type'], 'text/plain')
378 t.deepEqual(body, Buffer.alloc(1024))
379 })
380 })
381})
382
383test('stream with content type should not send application/octet-stream', t => {
384 t.plan(4)
385
386 const fastify = require('../..')()
387 const fs = require('fs')
388 const path = require('path')
389
390 var streamPath = path.join(__dirname, '..', '..', 'package.json')
391 var stream = fs.createReadStream(streamPath)
392 var buf = fs.readFileSync(streamPath)
393
394 fastify.get('/', function (req, reply) {
395 reply.header('Content-Type', 'text/plain').send(stream)
396 })
397
398 fastify.listen(0, err => {
399 t.error(err)
400 fastify.server.unref()
401 sget({
402 method: 'GET',
403 url: 'http://localhost:' + fastify.server.address().port
404 }, (err, response, body) => {
405 t.error(err)
406 t.strictEqual(response.headers['content-type'], 'text/plain')
407 t.deepEqual(body, buf)
408 })
409 })
410})
411
412test('stream using reply.res.writeHead should return customize headers', t => {
413 t.plan(6)
414
415 const fastify = require('../..')()
416 const fs = require('fs')
417 const path = require('path')
418
419 var streamPath = path.join(__dirname, '..', '..', 'package.json')
420 var stream = fs.createReadStream(streamPath)
421 var buf = fs.readFileSync(streamPath)
422
423 fastify.get('/', function (req, reply) {
424 reply.log.warn = function mockWarn (message) {
425 t.equal(message, 'response will send, but you shouldn\'t use res.writeHead in stream mode')
426 }
427 reply.res.writeHead(200, {
428 location: '/'
429 })
430 reply.send(stream)
431 })
432
433 fastify.listen(0, err => {
434 t.error(err)
435 fastify.server.unref()
436 sget({
437 method: 'GET',
438 url: 'http://localhost:' + fastify.server.address().port
439 }, (err, response, body) => {
440 t.error(err)
441 t.strictEqual(response.headers.location, '/')
442 t.strictEqual(response.headers['Content-Type'], undefined)
443 t.deepEqual(body, buf)
444 })
445 })
446})
447
448test('plain string without content type should send a text/plain', t => {
449 t.plan(4)
450
451 const fastify = require('../..')()
452
453 fastify.get('/', function (req, reply) {
454 reply.send('hello world!')
455 })
456
457 fastify.listen(0, err => {
458 t.error(err)
459 fastify.server.unref()
460
461 sget({
462 method: 'GET',
463 url: 'http://localhost:' + fastify.server.address().port
464 }, (err, response, body) => {
465 t.error(err)
466 t.strictEqual(response.headers['content-type'], 'text/plain; charset=utf-8')
467 t.deepEqual(body.toString(), 'hello world!')
468 })
469 })
470})
471
472test('plain string with content type should be sent unmodified', t => {
473 t.plan(4)
474
475 const fastify = require('../..')()
476
477 fastify.get('/', function (req, reply) {
478 reply.type('text/css').send('hello world!')
479 })
480
481 fastify.listen(0, err => {
482 t.error(err)
483 fastify.server.unref()
484
485 sget({
486 method: 'GET',
487 url: 'http://localhost:' + fastify.server.address().port
488 }, (err, response, body) => {
489 t.error(err)
490 t.strictEqual(response.headers['content-type'], 'text/css')
491 t.deepEqual(body.toString(), 'hello world!')
492 })
493 })
494})
495
496test('plain string with content type and custom serializer should be serialized', t => {
497 t.plan(4)
498
499 const fastify = require('../..')()
500
501 fastify.get('/', function (req, reply) {
502 reply
503 .serializer(() => 'serialized')
504 .type('text/css')
505 .send('hello world!')
506 })
507
508 fastify.listen(0, err => {
509 t.error(err)
510 fastify.server.unref()
511
512 sget({
513 method: 'GET',
514 url: 'http://localhost:' + fastify.server.address().port
515 }, (err, response, body) => {
516 t.error(err)
517 t.strictEqual(response.headers['content-type'], 'text/css')
518 t.deepEqual(body.toString(), 'serialized')
519 })
520 })
521})
522
523test('plain string with content type application/json should NOT be serialized as json', t => {
524 t.plan(4)
525
526 const fastify = require('../..')()
527
528 fastify.get('/', function (req, reply) {
529 reply.type('application/json').send('{"key": "hello world!"}')
530 })
531
532 fastify.listen(0, err => {
533 t.error(err)
534 fastify.server.unref()
535
536 sget({
537 method: 'GET',
538 url: 'http://localhost:' + fastify.server.address().port
539 }, (err, response, body) => {
540 t.error(err)
541 t.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8')
542 t.deepEqual(body.toString(), '{"key": "hello world!"}')
543 })
544 })
545})
546
547test('plain string with custom json content type should NOT be serialized as json', t => {
548 t.plan(16)
549
550 const fastify = require('../..')()
551
552 const customSamples = {
553 collectionjson: {
554 mimeType: 'application/vnd.collection+json',
555 sample: '{"collection":{"version":"1.0","href":"http://api.example.com/people/"}}'
556 },
557 hal: {
558 mimeType: 'application/hal+json',
559 sample: '{"_links":{"self":{"href":"https://api.example.com/people/1"}},"name":"John Doe"}'
560 },
561 jsonapi: {
562 mimeType: 'application/vnd.api+json',
563 sample: '{"data":{"type":"people","id":"1"}}'
564 },
565 jsonld: {
566 mimeType: 'application/ld+json',
567 sample: '{"@context":"https://json-ld.org/contexts/person.jsonld","name":"John Doe"}'
568 },
569 siren: {
570 mimeType: 'application/vnd.siren+json',
571 sample: '{"class":"person","properties":{"name":"John Doe"}}'
572 }
573 }
574
575 Object.keys(customSamples).forEach((path) => {
576 fastify.get(`/${path}`, function (req, reply) {
577 reply.type(customSamples[path].mimeType).send(customSamples[path].sample)
578 })
579 })
580
581 fastify.listen(0, err => {
582 t.error(err)
583 fastify.server.unref()
584
585 Object.keys(customSamples).forEach((path) => {
586 sget({
587 method: 'GET',
588 url: 'http://localhost:' + fastify.server.address().port + '/' + path
589 }, (err, response, body) => {
590 t.error(err)
591 t.strictEqual(response.headers['content-type'], customSamples[path].mimeType + '; charset=utf-8')
592 t.deepEqual(body.toString(), customSamples[path].sample)
593 })
594 })
595 })
596})
597
598test('non-string with content type application/json SHOULD be serialized as json', t => {
599 t.plan(4)
600
601 const fastify = require('../..')()
602
603 fastify.get('/', function (req, reply) {
604 reply.type('application/json').send({ key: 'hello world!' })
605 })
606
607 fastify.listen(0, err => {
608 t.error(err)
609 fastify.server.unref()
610
611 sget({
612 method: 'GET',
613 url: 'http://localhost:' + fastify.server.address().port
614 }, (err, response, body) => {
615 t.error(err)
616 t.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8')
617 t.deepEqual(body.toString(), JSON.stringify({ key: 'hello world!' }))
618 })
619 })
620})
621
622test('non-string with custom json content type SHOULD be serialized as json', t => {
623 t.plan(16)
624
625 const fastify = require('../..')()
626
627 const customSamples = {
628 collectionjson: {
629 mimeType: 'application/vnd.collection+json',
630 sample: JSON.parse('{"collection":{"version":"1.0","href":"http://api.example.com/people/"}}')
631 },
632 hal: {
633 mimeType: 'application/hal+json',
634 sample: JSON.parse('{"_links":{"self":{"href":"https://api.example.com/people/1"}},"name":"John Doe"}')
635 },
636 jsonapi: {
637 mimeType: 'application/vnd.api+json',
638 sample: JSON.parse('{"data":{"type":"people","id":"1"}}')
639 },
640 jsonld: {
641 mimeType: 'application/ld+json',
642 sample: JSON.parse('{"@context":"https://json-ld.org/contexts/person.jsonld","name":"John Doe"}')
643 },
644 siren: {
645 mimeType: 'application/vnd.siren+json',
646 sample: JSON.parse('{"class":"person","properties":{"name":"John Doe"}}')
647 }
648 }
649
650 Object.keys(customSamples).forEach((path) => {
651 fastify.get(`/${path}`, function (req, reply) {
652 reply.type(customSamples[path].mimeType).send(customSamples[path].sample)
653 })
654 })
655
656 fastify.listen(0, err => {
657 t.error(err)
658 fastify.server.unref()
659
660 Object.keys(customSamples).forEach((path) => {
661 sget({
662 method: 'GET',
663 url: 'http://localhost:' + fastify.server.address().port + '/' + path
664 }, (err, response, body) => {
665 t.error(err)
666 t.strictEqual(response.headers['content-type'], customSamples[path].mimeType + '; charset=utf-8')
667 t.deepEqual(body.toString(), JSON.stringify(customSamples[path].sample))
668 })
669 })
670 })
671})
672
673test('error object with a content type that is not application/json should work', t => {
674 t.plan(6)
675
676 const fastify = require('../..')()
677
678 fastify.get('/text', function (req, reply) {
679 reply.type('text/plain')
680 reply.send(new Error('some application error'))
681 })
682
683 fastify.get('/html', function (req, reply) {
684 reply.type('text/html')
685 reply.send(new Error('some application error'))
686 })
687
688 fastify.inject({
689 method: 'GET',
690 url: '/text'
691 }, (err, res) => {
692 t.error(err)
693 t.strictEqual(res.statusCode, 500)
694 t.strictEqual(JSON.parse(res.payload).message, 'some application error')
695 })
696
697 fastify.inject({
698 method: 'GET',
699 url: '/html'
700 }, (err, res) => {
701 t.error(err)
702 t.strictEqual(res.statusCode, 500)
703 t.strictEqual(JSON.parse(res.payload).message, 'some application error')
704 })
705})
706
707test('undefined payload should be sent as-is', t => {
708 t.plan(6)
709
710 const fastify = require('../..')()
711
712 fastify.addHook('onSend', function (request, reply, payload, next) {
713 t.strictEqual(payload, undefined)
714 next()
715 })
716
717 fastify.get('/', function (req, reply) {
718 reply.code(204).send()
719 })
720
721 fastify.listen(0, err => {
722 t.error(err)
723 fastify.server.unref()
724
725 sget({
726 method: 'GET',
727 url: `http://localhost:${fastify.server.address().port}`
728 }, (err, response, body) => {
729 t.error(err)
730 t.strictEqual(response.headers['content-type'], undefined)
731 t.strictEqual(response.headers['content-length'], undefined)
732 t.strictEqual(body.length, 0)
733 })
734 })
735})
736
737test('reply.send(new NotFound()) should not invoke the 404 handler', t => {
738 t.plan(9)
739
740 const fastify = require('../..')()
741
742 fastify.setNotFoundHandler((req, reply) => {
743 t.fail('Should not be called')
744 })
745
746 fastify.get('/not-found', function (req, reply) {
747 reply.send(new NotFound())
748 })
749
750 fastify.register(function (instance, options, next) {
751 instance.get('/not-found', function (req, reply) {
752 reply.send(new NotFound())
753 })
754
755 next()
756 }, { prefix: '/prefixed' })
757
758 fastify.listen(0, err => {
759 t.error(err)
760
761 fastify.server.unref()
762
763 sget({
764 method: 'GET',
765 url: 'http://localhost:' + fastify.server.address().port + '/not-found'
766 }, (err, response, body) => {
767 t.error(err)
768 t.strictEqual(response.statusCode, 404)
769 t.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8')
770 t.deepEqual(JSON.parse(body.toString()), {
771 statusCode: 404,
772 error: 'Not Found',
773 message: 'Not Found'
774 })
775 })
776
777 sget({
778 method: 'GET',
779 url: 'http://localhost:' + fastify.server.address().port + '/prefixed/not-found'
780 }, (err, response, body) => {
781 t.error(err)
782 t.strictEqual(response.statusCode, 404)
783 t.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8')
784 t.deepEqual(JSON.parse(body), {
785 error: 'Not Found',
786 message: 'Not Found',
787 statusCode: 404
788 })
789 })
790 })
791})
792
793test('reply can set multiple instances of same header', t => {
794 t.plan(4)
795
796 const fastify = require('../../')()
797
798 fastify.get('/headers', function (req, reply) {
799 reply
800 .header('set-cookie', 'one')
801 .header('set-cookie', 'two')
802 .send({})
803 })
804
805 fastify.listen(0, err => {
806 t.error(err)
807 fastify.server.unref()
808
809 sget({
810 method: 'GET',
811 url: 'http://localhost:' + fastify.server.address().port + '/headers'
812 }, (err, response, body) => {
813 t.error(err)
814 t.ok(response.headers['set-cookie'])
815 t.strictDeepEqual(response.headers['set-cookie'], ['one', 'two'])
816 })
817 })
818})
819
820test('reply.hasHeader returns correct values', t => {
821 t.plan(3)
822
823 const fastify = require('../../')()
824
825 fastify.get('/headers', function (req, reply) {
826 reply.header('x-foo', 'foo')
827 t.is(reply.hasHeader('x-foo'), true)
828 t.is(reply.hasHeader('x-bar'), false)
829 reply.send()
830 })
831
832 fastify.listen(0, err => {
833 t.error(err)
834 fastify.server.unref()
835 sget({
836 method: 'GET',
837 url: 'http://localhost:' + fastify.server.address().port + '/headers'
838 }, () => {})
839 })
840})
841
842test('reply.getHeader returns correct values', t => {
843 t.plan(4)
844
845 const fastify = require('../../')()
846
847 fastify.get('/headers', function (req, reply) {
848 reply.header('x-foo', 'foo')
849 t.is(reply.getHeader('x-foo'), 'foo')
850
851 reply.header('x-foo', 'bar')
852 t.strictDeepEqual(reply.getHeader('x-foo'), 'bar')
853
854 reply.header('set-cookie', 'one')
855 reply.header('set-cookie', 'two')
856 t.strictDeepEqual(reply.getHeader('set-cookie'), ['one', 'two'])
857
858 reply.send()
859 })
860
861 fastify.listen(0, err => {
862 t.error(err)
863 fastify.server.unref()
864 sget({
865 method: 'GET',
866 url: 'http://localhost:' + fastify.server.address().port + '/headers'
867 }, () => {})
868 })
869})
870
871test('reply.removeHeader can remove the value', t => {
872 t.plan(5)
873
874 const fastify = require('../../')()
875
876 t.teardown(fastify.close.bind(fastify))
877
878 fastify.get('/headers', function (req, reply) {
879 reply.header('x-foo', 'foo')
880 t.is(reply.getHeader('x-foo'), 'foo')
881
882 t.is(reply.removeHeader('x-foo'), reply)
883 t.strictDeepEqual(reply.getHeader('x-foo'), undefined)
884
885 reply.send()
886 })
887
888 fastify.listen(0, err => {
889 t.error(err)
890 fastify.server.unref()
891 sget({
892 method: 'GET',
893 url: 'http://localhost:' + fastify.server.address().port + '/headers'
894 }, () => {
895 t.pass()
896 })
897 })
898})
899
900test('reply.header can reset the value', t => {
901 t.plan(3)
902
903 const fastify = require('../../')()
904
905 t.teardown(fastify.close.bind(fastify))
906
907 fastify.get('/headers', function (req, reply) {
908 reply.header('x-foo', 'foo')
909 reply.header('x-foo', undefined)
910 t.strictDeepEqual(reply.getHeader('x-foo'), '')
911
912 reply.send()
913 })
914
915 fastify.listen(0, err => {
916 t.error(err)
917 fastify.server.unref()
918 sget({
919 method: 'GET',
920 url: 'http://localhost:' + fastify.server.address().port + '/headers'
921 }, () => {
922 t.pass()
923 })
924 })
925})
926
927test('Reply should handle JSON content type with a charset', t => {
928 t.plan(16)
929
930 const fastify = require('../../')()
931
932 fastify.get('/default', function (req, reply) {
933 reply.send({ hello: 'world' })
934 })
935
936 fastify.get('/utf8', function (req, reply) {
937 reply
938 .header('content-type', 'application/json; charset=utf-8')
939 .send({ hello: 'world' })
940 })
941
942 fastify.get('/utf16', function (req, reply) {
943 reply
944 .header('content-type', 'application/json; charset=utf-16')
945 .send({ hello: 'world' })
946 })
947
948 fastify.get('/utf32', function (req, reply) {
949 reply
950 .header('content-type', 'application/json; charset=utf-32')
951 .send({ hello: 'world' })
952 })
953
954 fastify.get('/type-utf8', function (req, reply) {
955 reply
956 .type('application/json; charset=utf-8')
957 .send({ hello: 'world' })
958 })
959
960 fastify.get('/type-utf16', function (req, reply) {
961 reply
962 .type('application/json; charset=utf-16')
963 .send({ hello: 'world' })
964 })
965
966 fastify.get('/type-utf32', function (req, reply) {
967 reply
968 .type('application/json; charset=utf-32')
969 .send({ hello: 'world' })
970 })
971
972 fastify.get('/no-space-type-utf32', function (req, reply) {
973 reply
974 .type('application/json;charset=utf-32')
975 .send({ hello: 'world' })
976 })
977
978 fastify.inject('/default', (err, res) => {
979 t.error(err)
980 t.is(res.headers['content-type'], 'application/json; charset=utf-8')
981 })
982
983 fastify.inject('/utf8', (err, res) => {
984 t.error(err)
985 t.is(res.headers['content-type'], 'application/json; charset=utf-8')
986 })
987
988 fastify.inject('/utf16', (err, res) => {
989 t.error(err)
990 t.is(res.headers['content-type'], 'application/json; charset=utf-16')
991 })
992
993 fastify.inject('/utf32', (err, res) => {
994 t.error(err)
995 t.is(res.headers['content-type'], 'application/json; charset=utf-32')
996 })
997
998 fastify.inject('/type-utf8', (err, res) => {
999 t.error(err)
1000 t.is(res.headers['content-type'], 'application/json; charset=utf-8')
1001 })
1002
1003 fastify.inject('/type-utf16', (err, res) => {
1004 t.error(err)
1005 t.is(res.headers['content-type'], 'application/json; charset=utf-16')
1006 })
1007
1008 fastify.inject('/type-utf32', (err, res) => {
1009 t.error(err)
1010 t.is(res.headers['content-type'], 'application/json; charset=utf-32')
1011 })
1012
1013 fastify.inject('/no-space-type-utf32', (err, res) => {
1014 t.error(err)
1015 t.is(res.headers['content-type'], 'application/json;charset=utf-32')
1016 })
1017})
1018
1019test('Content type and charset set previously', t => {
1020 t.plan(2)
1021
1022 const fastify = require('../../')()
1023
1024 fastify.addHook('onRequest', function (req, reply, next) {
1025 reply.header('content-type', 'application/json; charset=utf-16')
1026 next()
1027 })
1028
1029 fastify.get('/', function (req, reply) {
1030 reply.send({ hello: 'world' })
1031 })
1032
1033 fastify.inject('/', (err, res) => {
1034 t.error(err)
1035 t.is(res.headers['content-type'], 'application/json; charset=utf-16')
1036 })
1037})
1038
1039test('.status() is an alias for .code()', t => {
1040 t.plan(2)
1041 const fastify = require('../..')()
1042
1043 fastify.get('/', function (req, reply) {
1044 reply.status(418).send()
1045 })
1046
1047 fastify.inject('/', (err, res) => {
1048 t.error(err)
1049 t.is(res.statusCode, 418)
1050 })
1051})
1052
1053test('.statusCode is getter and setter', t => {
1054 t.plan(4)
1055 const fastify = require('../..')()
1056
1057 fastify.get('/', function (req, reply) {
1058 t.ok(reply.statusCode, 200, 'default status value')
1059 reply.statusCode = 418
1060 t.ok(reply.statusCode, 418)
1061 reply.send()
1062 })
1063
1064 fastify.inject('/', (err, res) => {
1065 t.error(err)
1066 t.is(res.statusCode, 418)
1067 })
1068})
1069
1070test('reply.header setting multiple cookies as multiple Set-Cookie headers', t => {
1071 t.plan(7)
1072
1073 const fastify = require('../../')()
1074
1075 fastify.get('/headers', function (req, reply) {
1076 reply
1077 .header('set-cookie', 'one')
1078 .header('set-cookie', 'two')
1079 .header('set-cookie', 'three')
1080 .header('set-cookie', ['four', 'five', 'six'])
1081 .send({})
1082 })
1083
1084 fastify.listen(0, err => {
1085 t.error(err)
1086 fastify.server.unref()
1087
1088 sget({
1089 method: 'GET',
1090 url: 'http://localhost:' + fastify.server.address().port + '/headers'
1091 }, (err, response, body) => {
1092 t.error(err)
1093 t.ok(response.headers['set-cookie'])
1094 t.strictDeepEqual(response.headers['set-cookie'], ['one', 'two', 'three', 'four', 'five', 'six'])
1095 })
1096 })
1097
1098 fastify.inject('/headers', (error, response) => {
1099 t.error(error)
1100 t.ok(response.headers['set-cookie'])
1101 t.strictDeepEqual(response.headers['set-cookie'], ['one', 'two', 'three', 'four', 'five', 'six'])
1102 })
1103})
1104
1105test('should throw error when passing falsy value to reply.sent', t => {
1106 t.plan(3)
1107 const fastify = require('../..')()
1108
1109 fastify.get('/', function (req, reply) {
1110 try {
1111 reply.sent = false
1112 } catch (err) {
1113 t.strictEqual(err.message, 'FST_ERR_REP_SENT_VALUE: The only possible value for reply.sent is true.')
1114 reply.send()
1115 }
1116 })
1117
1118 fastify.inject('/', (err, res) => {
1119 t.error(err)
1120 t.pass()
1121 })
1122})
1123
1124test('should throw error when attempting to set reply.sent more than once', t => {
1125 t.plan(3)
1126 const fastify = require('../..')()
1127
1128 fastify.get('/', function (req, reply) {
1129 reply.sent = true
1130 try {
1131 reply.sent = true
1132 } catch (err) {
1133 t.strictEqual(err.message, 'FST_ERR_REP_ALREADY_SENT: Reply was already sent.')
1134 }
1135 reply.res.end()
1136 })
1137
1138 fastify.inject('/', (err, res) => {
1139 t.error(err)
1140 t.pass()
1141 })
1142})
1143
1144test('reply.getResponseTime() should return 0 before the timer is initialised on the reply by setting up response listeners', t => {
1145 t.plan(1)
1146 const response = { statusCode: 200 }
1147 const context = {}
1148 const reply = new Reply(response, context, null)
1149 t.equal(reply.getResponseTime(), 0)
1150})
1151
1152test('reply.getResponseTime() should return a number greater than 0 after the timer is initialised on the reply by setting up response listeners', t => {
1153 t.plan(1)
1154 const fastify = require('../..')()
1155 fastify.route({
1156 method: 'GET',
1157 url: '/',
1158 handler: (req, reply) => {
1159 reply.send('hello world')
1160 }
1161 })
1162
1163 fastify.addHook('onResponse', (req, reply) => {
1164 t.true(reply.getResponseTime() > 0)
1165 t.end()
1166 })
1167
1168 fastify.inject({ method: 'GET', url: '/' })
1169})
1170
1171test('reply should use the custom serializer', t => {
1172 t.plan(4)
1173 const fastify = require('../..')()
1174 fastify.setReplySerializer((payload, statusCode) => {
1175 t.deepEqual(payload, { foo: 'bar' })
1176 t.equal(statusCode, 200)
1177 payload.foo = 'bar bar'
1178 return JSON.stringify(payload)
1179 })
1180
1181 fastify.route({
1182 method: 'GET',
1183 url: '/',
1184 handler: (req, reply) => {
1185 reply.send({ foo: 'bar' })
1186 }
1187 })
1188
1189 fastify.inject({
1190 method: 'GET',
1191 url: '/'
1192 }, (err, res) => {
1193 t.error(err)
1194 t.strictEqual(res.payload, '{"foo":"bar bar"}')
1195 })
1196})
1197
1198test('reply should use the right serializer in encapsulated context', t => {
1199 t.plan(9)
1200
1201 const fastify = require('../..')()
1202 fastify.setReplySerializer((payload) => {
1203 t.deepEqual(payload, { foo: 'bar' })
1204 payload.foo = 'bar bar'
1205 return JSON.stringify(payload)
1206 })
1207
1208 fastify.route({
1209 method: 'GET',
1210 url: '/',
1211 handler: (req, reply) => { reply.send({ foo: 'bar' }) }
1212 })
1213
1214 fastify.register(function (instance, opts, next) {
1215 instance.route({
1216 method: 'GET',
1217 url: '/sub',
1218 handler: (req, reply) => { reply.send({ john: 'doo' }) }
1219 })
1220 instance.setReplySerializer((payload) => {
1221 t.deepEqual(payload, { john: 'doo' })
1222 payload.john = 'too too'
1223 return JSON.stringify(payload)
1224 })
1225 next()
1226 })
1227
1228 fastify.register(function (instance, opts, next) {
1229 instance.route({
1230 method: 'GET',
1231 url: '/sub',
1232 handler: (req, reply) => { reply.send({ sweet: 'potato' }) }
1233 })
1234 instance.setReplySerializer((payload) => {
1235 t.deepEqual(payload, { sweet: 'potato' })
1236 payload.sweet = 'potato potato'
1237 return JSON.stringify(payload)
1238 })
1239 next()
1240 }, { prefix: 'sub' })
1241
1242 fastify.inject({
1243 method: 'GET',
1244 url: '/'
1245 }, (err, res) => {
1246 t.error(err)
1247 t.strictEqual(res.payload, '{"foo":"bar bar"}')
1248 })
1249
1250 fastify.inject({
1251 method: 'GET',
1252 url: '/sub'
1253 }, (err, res) => {
1254 t.error(err)
1255 t.strictEqual(res.payload, '{"john":"too too"}')
1256 })
1257
1258 fastify.inject({
1259 method: 'GET',
1260 url: '/sub/sub'
1261 }, (err, res) => {
1262 t.error(err)
1263 t.strictEqual(res.payload, '{"sweet":"potato potato"}')
1264 })
1265})
1266
1267test('reply should use the right serializer in deep encapsulated context', t => {
1268 t.plan(8)
1269
1270 const fastify = require('../..')()
1271
1272 fastify.route({
1273 method: 'GET',
1274 url: '/',
1275 handler: (req, reply) => { reply.send({ foo: 'bar' }) }
1276 })
1277
1278 fastify.register(function (instance, opts, next) {
1279 instance.route({
1280 method: 'GET',
1281 url: '/sub',
1282 handler: (req, reply) => { reply.send({ john: 'doo' }) }
1283 })
1284 instance.setReplySerializer((payload) => {
1285 t.deepEqual(payload, { john: 'doo' })
1286 payload.john = 'too too'
1287 return JSON.stringify(payload)
1288 })
1289
1290 instance.register(function (subInstance, opts, next) {
1291 subInstance.route({
1292 method: 'GET',
1293 url: '/deep',
1294 handler: (req, reply) => { reply.send({ john: 'deep' }) }
1295 })
1296 subInstance.setReplySerializer((payload) => {
1297 t.deepEqual(payload, { john: 'deep' })
1298 payload.john = 'deep deep'
1299 return JSON.stringify(payload)
1300 })
1301 next()
1302 })
1303 next()
1304 })
1305
1306 fastify.inject({
1307 method: 'GET',
1308 url: '/'
1309 }, (err, res) => {
1310 t.error(err)
1311 t.strictEqual(res.payload, '{"foo":"bar"}')
1312 })
1313
1314 fastify.inject({
1315 method: 'GET',
1316 url: '/sub'
1317 }, (err, res) => {
1318 t.error(err)
1319 t.strictEqual(res.payload, '{"john":"too too"}')
1320 })
1321
1322 fastify.inject({
1323 method: 'GET',
1324 url: '/deep'
1325 }, (err, res) => {
1326 t.error(err)
1327 t.strictEqual(res.payload, '{"john":"deep deep"}')
1328 })
1329})
1330
1331test('reply should use the route serializer', t => {
1332 t.plan(3)
1333
1334 const fastify = require('../..')()
1335 fastify.setReplySerializer(() => {
1336 t.fail('this serializer should not be executed')
1337 })
1338
1339 fastify.route({
1340 method: 'GET',
1341 url: '/',
1342 handler: (req, reply) => {
1343 reply
1344 .serializer((payload) => {
1345 t.deepEqual(payload, { john: 'doo' })
1346 payload.john = 'too too'
1347 return JSON.stringify(payload)
1348 })
1349 .send({ john: 'doo' })
1350 }
1351 })
1352
1353 fastify.inject({
1354 method: 'GET',
1355 url: '/'
1356 }, (err, res) => {
1357 t.error(err)
1358 t.strictEqual(res.payload, '{"john":"too too"}')
1359 })
1360})
1361
1362test('cannot set the replySerializer when the server is running', t => {
1363 t.plan(2)
1364
1365 const fastify = require('../..')()
1366 t.teardown(fastify.close.bind(fastify))
1367
1368 fastify.listen(err => {
1369 t.error(err)
1370 try {
1371 fastify.setReplySerializer(() => {})
1372 t.fail('this serializer should not be setup')
1373 } catch (e) {
1374 t.is(e.message, 'Cannot call "setReplySerializer" when fastify instance is already started!')
1375 }
1376 })
1377})
1378
1379test('reply should not call the custom serializer for errors and not found', t => {
1380 t.plan(9)
1381
1382 const fastify = require('../..')()
1383 fastify.setReplySerializer((payload, statusCode) => {
1384 t.deepEqual(payload, { foo: 'bar' })
1385 t.equal(statusCode, 200)
1386 return JSON.stringify(payload)
1387 })
1388
1389 fastify.get('/', (req, reply) => { reply.send({ foo: 'bar' }) })
1390 fastify.get('/err', (req, reply) => { reply.send(new Error('an error')) })
1391
1392 fastify.inject({
1393 method: 'GET',
1394 url: '/'
1395 }, (err, res) => {
1396 t.error(err)
1397 t.strictEqual(res.statusCode, 200)
1398 t.strictEqual(res.payload, '{"foo":"bar"}')
1399 })
1400
1401 fastify.inject({
1402 method: 'GET',
1403 url: '/err'
1404 }, (err, res) => {
1405 t.error(err)
1406 t.strictEqual(res.statusCode, 500)
1407 })
1408
1409 fastify.inject({
1410 method: 'GET',
1411 url: '/not-existing'
1412 }, (err, res) => {
1413 t.error(err)
1414 t.strictEqual(res.statusCode, 404)
1415 })
1416})
1417
1418test('reply.then', t => {
1419 t.plan(2)
1420
1421 function context () {}
1422 function request () {}
1423
1424 t.test('without an error', t => {
1425 t.plan(1)
1426
1427 const response = new Writable()
1428 const reply = new Reply(response, context, request)
1429
1430 reply.then(function () {
1431 t.pass('fullfilled called')
1432 })
1433
1434 response.destroy()
1435 })
1436
1437 t.test('with an error', t => {
1438 t.plan(1)
1439
1440 const response = new Writable()
1441 const reply = new Reply(response, context, request)
1442 const _err = new Error('kaboom')
1443
1444 reply.then(function () {
1445 t.fail('fullfilled called')
1446 }, function (err) {
1447 t.equal(err, _err)
1448 })
1449
1450 response.destroy(_err)
1451 })
1452})