UNPKG

26.5 kBJavaScriptView Raw
1'use strict'
2
3//dependencies
4var path = require('path')
5var contentDisposition = require('content-disposition')
6var onFinished = require('on-finished')
7var escapeHtml = require('escape-html')
8var merge = require('utils-merge')
9var Utils = require('./utils')
10var MockResponse = require('mock-res')
11var util = require('util')
12var send = require('send')
13var mime = send.mime
14var STATUS_CODES = require('http').STATUS_CODES
15var deprecate = () => {}
16var setCharset = Utils.setCharset
17var normalizeType = Utils.normalizeType
18var normalizeTypes = Utils.normalizeTypes
19var isAbsolute = Utils.isAbsolute
20var vary = require('vary')
21var sign = require('cookie-signature').sign
22var cookie = require('cookie')
23var extname = path.extname
24
25/**
26 * @constructor
27 * @description Express response mock
28 * @public
29 */
30function MockExpressResponse(options) {
31 options = options || {}
32
33 MockResponse.call(this, options.finish)
34
35 this.app = {
36 'jsonp callback name': 'callback',
37 render: function(view, data, fn) {
38 //default implementation is
39 //to return uncompiled view
40 //
41 //this must me ovveriden by view engine
42 //of choice
43 if ('function' === typeof options.runtime) {
44 options.runtime(view, data, fn)
45 } else {
46 fn(null, view)
47 }
48 },
49 }
50
51 this.req =
52 options.request ||
53 new MockExpressRequest({
54 query: {},
55 })
56}
57util.inherits(MockExpressResponse, MockResponse)
58
59//------------------------------------------------------------------------------
60// Express respnse methods
61//------------------------------------------------------------------------------
62
63/**
64 * Set status `code`.
65 *
66 * @param {Number} code
67 * @return {ServerResponse}
68 * @api public
69 */
70MockExpressResponse.prototype.status = function(code) {
71 this.statusCode = code
72 this.statusMessage = STATUS_CODES[this.statusCode]
73 return this
74}
75
76/**
77 * Set Link header field with the given `links`.
78 *
79 * Examples:
80 *
81 * res.links({
82 * next: 'http://api.example.com/users?page=2',
83 * last: 'http://api.example.com/users?page=5'
84 * });
85 *
86 * @param {Object} links
87 * @return {ServerResponse}
88 * @api public
89 */
90MockExpressResponse.prototype.links = function(links) {
91 var link = this.get('Link') || ''
92 if (link) {
93 link += ', '
94 }
95 return this.set(
96 'Link',
97 link +
98 Object.keys(links)
99 .map(function(rel) {
100 return '<' + links[rel] + '>; rel="' + rel + '"'
101 })
102 .join(', ')
103 )
104}
105
106/**
107 * Send a response.
108 *
109 * Examples:
110 *
111 * res.send(new Buffer('wahoo'));
112 * res.send({ some: 'json' });
113 * res.send('<p>some html</p>');
114 *
115 * @param {string|number|boolean|object|Buffer} body
116 * @api public
117 */
118MockExpressResponse.prototype.send = function send(body) {
119 var chunk = body
120 var encoding
121 var len
122 var req = this.req
123 var type
124
125 // settings
126 var app = this.app
127
128 // allow status / body
129 if (arguments.length === 2) {
130 // res.send(body, status) backwards compat
131 if (typeof arguments[0] !== 'number' && typeof arguments[1] === 'number') {
132 deprecate('res.send(body, status): Use res.status(status).send(body) instead')
133 this.statusCode = arguments[1]
134 } else {
135 deprecate('res.send(status, body): Use res.status(status).send(body) instead')
136 this.statusCode = arguments[0]
137 chunk = arguments[1]
138 }
139 }
140
141 // disambiguate res.send(status) and res.send(status, num)
142 if (typeof chunk === 'number' && arguments.length === 1) {
143 // res.send(status) will set status message as text string
144 if (!this.get('Content-Type')) {
145 this.type('txt')
146 }
147
148 deprecate('res.send(status): Use res.sendStatus(status) instead')
149 this.statusCode = chunk
150 chunk = STATUS_CODES[chunk]
151 }
152
153 switch (typeof chunk) {
154 // string defaulting to html
155 case 'string':
156 if (!this.get('Content-Type')) {
157 this.type('html')
158 }
159 break
160 case 'boolean':
161 case 'number':
162 case 'object':
163 if (chunk === null) {
164 chunk = ''
165 } else if (Buffer.isBuffer(chunk)) {
166 if (!this.get('Content-Type')) {
167 this.type('bin')
168 }
169 } else {
170 return this.json(chunk)
171 }
172 break
173 }
174
175 // write strings in utf-8
176 if (typeof chunk === 'string') {
177 encoding = 'utf8'
178 type = this.get('Content-Type')
179
180 // reflect this in content-type
181 if (typeof type === 'string') {
182 this.set('Content-Type', setCharset(type, 'utf-8'))
183 }
184 }
185
186 // populate Content-Length
187 if (chunk !== undefined) {
188 if (!Buffer.isBuffer(chunk)) {
189 // convert chunk to Buffer; saves later double conversions
190 chunk = new Buffer(chunk, encoding)
191 encoding = undefined
192 }
193
194 len = chunk.length
195 this.set('Content-Length', len)
196 }
197
198 // populate ETag
199 var etag
200 var generateETag = len !== undefined && app['etag fn']
201 if (typeof generateETag === 'function' && !this.get('ETag')) {
202 if ((etag = generateETag(chunk, encoding))) {
203 this.set('ETag', etag)
204 }
205 }
206
207 // freshness
208 if (req.fresh) {
209 this.statusCode = 304
210 }
211
212 // strip irrelevant headers
213 if (204 === this.statusCode || 304 === this.statusCode) {
214 this.removeHeader('Content-Type')
215 this.removeHeader('Content-Length')
216 this.removeHeader('Transfer-Encoding')
217 chunk = ''
218 }
219
220 if (req.method === 'HEAD') {
221 // skip body for HEAD
222 this.end()
223 } else {
224 // respond
225 this.end(chunk, encoding)
226 }
227
228 return this
229}
230
231/**
232 * Send JSON response.
233 *
234 * Examples:
235 *
236 * res.json(null);
237 * res.json({ user: 'tj' });
238 *
239 * @param {string|number|boolean|object} obj
240 * @api public
241 */
242MockExpressResponse.prototype.json = function json(obj) {
243 var val = obj
244
245 // allow status / body
246 if (arguments.length === 2) {
247 // res.json(body, status) backwards compat
248 if (typeof arguments[1] === 'number') {
249 deprecate('res.json(obj, status): Use res.status(status).json(obj) instead')
250 this.statusCode = arguments[1]
251 } else {
252 deprecate('res.json(status, obj): Use res.status(status).json(obj) instead')
253 this.statusCode = arguments[0]
254 val = arguments[1]
255 }
256 }
257
258 // settings
259 var app = this.app
260 var replacer = app['json replacer']
261 var spaces = app['json spaces']
262 var body = JSON.stringify(val, replacer, spaces)
263
264 // content-type
265 if (!this.get('Content-Type')) {
266 this.set('Content-Type', 'application/json')
267 }
268
269 return this.send(body)
270}
271
272/**
273 * Send JSON response with JSONP callback support.
274 *
275 * Examples:
276 *
277 * res.jsonp(null);
278 * res.jsonp({ user: 'tj' });
279 *
280 * @param {string|number|boolean|object} obj
281 * @api public
282 */
283MockExpressResponse.prototype.jsonp = function jsonp(obj) {
284 var val = obj
285
286 // allow status / body
287 if (arguments.length === 2) {
288 // res.json(body, status) backwards compat
289 if (typeof arguments[1] === 'number') {
290 deprecate('res.jsonp(obj, status): Use res.status(status).json(obj) instead')
291 this.statusCode = arguments[1]
292 } else {
293 deprecate('res.jsonp(status, obj): Use res.status(status).jsonp(obj) instead')
294 this.statusCode = arguments[0]
295 val = arguments[1]
296 }
297 }
298
299 // settings
300 var app = this.app
301 var replacer = app['json replacer']
302 var spaces = app['json spaces']
303 var body = JSON.stringify(val, replacer, spaces)
304 var callback = this.req.query[app['jsonp callback name']]
305
306 // content-type
307 if (!this.get('Content-Type')) {
308 this.set('X-Content-Type-Options', 'nosniff')
309 this.set('Content-Type', 'application/json')
310 }
311
312 // fixup callback
313 if (Array.isArray(callback)) {
314 callback = callback[0]
315 }
316
317 // jsonp
318 if (typeof callback === 'string' && callback.length !== 0) {
319 this.charset = 'utf-8'
320 this.set('X-Content-Type-Options', 'nosniff')
321 this.set('Content-Type', 'text/javascript')
322
323 // restrict callback charset
324 callback = callback.replace(/[^\[\]\w$.]/g, '')
325
326 // replace chars not allowed in JavaScript that are in JSON
327 body = body.replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029')
328
329 // the /**/ is a specific security mitigation for "Rosetta Flash JSONP abuse"
330 // the typeof check is just to reduce client error noise
331 body =
332 '/**/ typeof ' + callback + " === 'function' && " + callback + '(' + body + ');'
333 }
334
335 return this.send(body)
336}
337
338/**
339 * Send given HTTP status code.
340 *
341 * Sets the response status to `statusCode` and the body of the
342 * response to the standard description from node's http.STATUS_CODES
343 * or the statusCode number if no description.
344 *
345 * Examples:
346 *
347 * res.sendStatus(200);
348 *
349 * @param {number} statusCode
350 * @api public
351 */
352MockExpressResponse.prototype.sendStatus = function sendStatus(statusCode) {
353 var body = STATUS_CODES[statusCode] || String(statusCode)
354
355 this.statusCode = statusCode
356 this.type('txt')
357
358 return this.send(body)
359}
360
361// pipe the send file stream
362function sendfile(res, file, options, callback) {
363 var done = false
364 var streaming
365
366 // request aborted
367 function onaborted() {
368 if (done) {
369 return
370 }
371 done = true
372
373 var err = new Error('Request aborted')
374 err.code = 'ECONNABORTED'
375 callback(err)
376 }
377
378 // directory
379 function ondirectory() {
380 if (done) {
381 return
382 }
383 done = true
384
385 var err = new Error('EISDIR, read')
386 err.code = 'EISDIR'
387 callback(err)
388 }
389
390 // errors
391 function onerror(err) {
392 if (done) {
393 return
394 }
395 done = true
396 callback(err)
397 }
398
399 // ended
400 function onend() {
401 if (done) {
402 return
403 }
404 done = true
405 callback()
406 }
407
408 // file
409 function onfile() {
410 streaming = false
411 }
412
413 // finished
414 function onfinish(err) {
415 if (err && err.code === 'ECONNRESET') {
416 return onaborted()
417 }
418 if (err) {
419 return onerror(err)
420 }
421 if (done) {
422 return
423 }
424
425 setImmediate(function() {
426 if (streaming !== false && !done) {
427 onaborted()
428 return
429 }
430
431 if (done) {
432 return
433 }
434 done = true
435 callback()
436 })
437 }
438
439 // streaming
440 function onstream() {
441 streaming = true
442 }
443
444 file.on('directory', ondirectory)
445 file.on('end', onend)
446 file.on('error', onerror)
447 file.on('file', onfile)
448 file.on('stream', onstream)
449 onFinished(res, onfinish)
450
451 if (options.headers) {
452 // set headers on successful transfer
453 file.on('headers', function headers(res) {
454 var obj = options.headers
455 var keys = Object.keys(obj)
456
457 for (var i = 0; i < keys.length; i++) {
458 var k = keys[i]
459 res.setHeader(k, obj[k])
460 }
461 })
462 }
463
464 // pipe
465 file.pipe(res)
466}
467
468/**
469 * Transfer the file at the given `path`.
470 *
471 * Automatically sets the _Content-Type_ response header field.
472 * The callback `fn(err)` is invoked when the transfer is complete
473 * or when an error occurs. Be sure to check `res.sentHeader`
474 * if you wish to attempt responding, as the header and some data
475 * may have already been transferred.
476 *
477 * Options:
478 *
479 * - `maxAge` defaulting to 0 (can be string converted by `ms`)
480 * - `root` root directory for relative filenames
481 * - `headers` object of headers to serve with file
482 * - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them
483 *
484 * Other options are passed along to `send`.
485 *
486 * Examples:
487 *
488 * The following example illustrates how `res.sendFile()` may
489 * be used as an alternative for the `static()` middleware for
490 * dynamic situations. The code backing `res.sendFile()` is actually
491 * the same code, so HTTP cache support etc is identical.
492 *
493 * app.get('/user/:uid/photos/:file', function(req, res){
494 * var uid = req.params.uid
495 * , file = req.params.file;
496 *
497 * req.user.mayViewFilesFrom(uid, function(yes){
498 * if (yes) {
499 * res.sendFile('/uploads/' + uid + '/' + file);
500 * } else {
501 * res.send(403, 'Sorry! you cant see that.');
502 * }
503 * });
504 * });
505 *
506 * @api public
507 */
508MockExpressResponse.prototype.sendFile = function sendFile(path, options, fn) {
509 var req = this.req
510 var res = this
511 var next = req.next
512
513 if (!path) {
514 throw new TypeError('path argument is required to res.sendFile')
515 }
516
517 // support function as second arg
518 if (typeof options === 'function') {
519 fn = options
520 options = {}
521 }
522
523 options = options || {}
524
525 if (!options.root && !isAbsolute(path)) {
526 throw new TypeError('path must be absolute or specify root to res.sendFile')
527 }
528
529 // create file stream
530 var pathname = encodeURI(path)
531 var file = send(req, pathname, options)
532
533 // transfer
534 sendfile(res, file, options, function(err) {
535 if (fn) {
536 return fn(err)
537 }
538 if (err && err.code === 'EISDIR') {
539 return next()
540 }
541
542 // next() all but write errors
543 if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') {
544 next(err)
545 }
546 })
547}
548
549// /**
550// * Transfer the file at the given `path`.
551// *
552// * Automatically sets the _Content-Type_ response header field.
553// * The callback `fn(err)` is invoked when the transfer is complete
554// * or when an error occurs. Be sure to check `res.sentHeader`
555// * if you wish to attempt responding, as the header and some data
556// * may have already been transferred.
557// *
558// * Options:
559// *
560// * - `maxAge` defaulting to 0 (can be string converted by `ms`)
561// * - `root` root directory for relative filenames
562// * - `headers` object of headers to serve with file
563// * - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them
564// *
565// * Other options are passed along to `send`.
566// *
567// * Examples:
568// *
569// * The following example illustrates how `res.sendfile()` may
570// * be used as an alternative for the `static()` middleware for
571// * dynamic situations. The code backing `res.sendfile()` is actually
572// * the same code, so HTTP cache support etc is identical.
573// *
574// * app.get('/user/:uid/photos/:file', function(req, res){
575// * var uid = req.params.uid
576// * , file = req.params.file;
577// *
578// * req.user.mayViewFilesFrom(uid, function(yes){
579// * if (yes) {
580// * res.sendfile('/uploads/' + uid + '/' + file);
581// * } else {
582// * res.send(403, 'Sorry! you cant see that.');
583// * }
584// * });
585// * });
586// *
587// * @api public
588// */
589
590// res.sendfile = function(path, options, fn){
591// var req = this.req;
592// var res = this;
593// var next = req.next;
594
595// // support function as second arg
596// if (typeof options === 'function') {
597// fn = options;
598// options = {};
599// }
600
601// options = options || {};
602
603// // create file stream
604// var file = send(req, path, options);
605
606// // transfer
607// sendfile(res, file, options, function (err) {
608// if (fn) return fn(err);
609// if (err && err.code === 'EISDIR') return next();
610
611// // next() all but write errors
612// if (err && err.code !== 'ECONNABORT' && err.syscall !== 'write') {
613// next(err);
614// }
615// });
616// };
617
618// res.sendfile = deprecate.function(res.sendfile,
619// 'res.sendfile: Use res.sendFile instead');
620
621// /**
622// * Transfer the file at the given `path` as an attachment.
623// *
624// * Optionally providing an alternate attachment `filename`,
625// * and optional callback `fn(err)`. The callback is invoked
626// * when the data transfer is complete, or when an error has
627// * ocurred. Be sure to check `res.headersSent` if you plan to respond.
628// *
629// * This method uses `res.sendfile()`.
630// *
631// * @api public
632// */
633
634// res.download = function download(path, filename, fn) {
635// // support function as second arg
636// if (typeof filename === 'function') {
637// fn = filename;
638// filename = null;
639// }
640
641// filename = filename || path;
642
643// // set Content-Disposition when file is sent
644// var headers = {
645// 'Content-Disposition': contentDisposition(filename)
646// };
647
648// // Resolve the full path for sendFile
649// var fullPath = resolve(path);
650
651// return this.sendFile(fullPath, { headers: headers }, fn);
652// };
653
654/**
655 * Set _Content-Type_ response header with `type` through `mime.lookup()`
656 * when it does not contain "/", or set the Content-Type to `type` otherwise.
657 *
658 * Examples:
659 *
660 * res.type('.html');
661 * res.type('html');
662 * res.type('json');
663 * res.type('application/json');
664 * res.type('png');
665 *
666 * @param {String} type
667 * @return {ServerResponse} for chaining
668 * @api public
669 */
670MockExpressResponse.prototype.contentType = MockExpressResponse.prototype.type = function(
671 type
672) {
673 /*jshint bitwise:false*/
674 return this.set('Content-Type', ~type.indexOf('/') ? type : mime.lookup(type))
675 /*jshint bitwise:true*/
676}
677
678/**
679 * Respond to the Acceptable formats using an `obj`
680 * of mime-type callbacks.
681 *
682 * This method uses `req.accepted`, an array of
683 * acceptable types ordered by their quality values.
684 * When "Accept" is not present the _first_ callback
685 * is invoked, otherwise the first match is used. When
686 * no match is performed the server responds with
687 * 406 "Not Acceptable".
688 *
689 * Content-Type is set for you, however if you choose
690 * you may alter this within the callback using `res.type()`
691 * or `res.set('Content-Type', ...)`.
692 *
693 * res.format({
694 * 'text/plain': function(){
695 * res.send('hey');
696 * },
697 *
698 * 'text/html': function(){
699 * res.send('<p>hey</p>');
700 * },
701 *
702 * 'appliation/json': function(){
703 * res.send({ message: 'hey' });
704 * }
705 * });
706 *
707 * In addition to canonicalized MIME types you may
708 * also use extnames mapped to these types:
709 *
710 * res.format({
711 * text: function(){
712 * res.send('hey');
713 * },
714 *
715 * html: function(){
716 * res.send('<p>hey</p>');
717 * },
718 *
719 * json: function(){
720 * res.send({ message: 'hey' });
721 * }
722 * });
723 *
724 * By default Express passes an `Error`
725 * with a `.status` of 406 to `next(err)`
726 * if a match is not made. If you provide
727 * a `.default` callback it will be invoked
728 * instead.
729 *
730 * @param {Object} obj
731 * @return {ServerResponse} for chaining
732 * @api public
733 */
734MockExpressResponse.prototype.format = function(obj) {
735 var req = this.req
736 var next = req.next
737
738 var fn = obj.default
739 if (fn) {
740 delete obj.default
741 }
742 var keys = Object.keys(obj)
743
744 var key = req.accepts(keys)
745
746 this.vary('Accept')
747
748 if (key) {
749 this.set('Content-Type', normalizeType(key).value)
750 obj[key](req, this, next)
751 } else if (fn) {
752 fn()
753 } else {
754 var err = new Error('Not Acceptable')
755 err.status = 406
756 err.types = normalizeTypes(keys).map(function(o) {
757 return o.value
758 })
759 next(err)
760 }
761
762 return this
763}
764
765/**
766 * Set _Content-Disposition_ header to _attachment_ with optional `filename`.
767 *
768 * @param {String} filename
769 * @return {ServerResponse}
770 * @api public
771 */
772MockExpressResponse.prototype.attachment = function attachment(filename) {
773 if (filename) {
774 this.type(extname(filename))
775 }
776
777 this.set('Content-Disposition', contentDisposition(filename))
778
779 return this
780}
781
782/**
783 * Append additional header `field` with value `val`.
784 *
785 * Example:
786 *
787 * res.append('Link', ['<http://localhost/>', '<http://localhost:3000/>']);
788 * res.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly');
789 * res.append('Warning', '199 Miscellaneous warning');
790 *
791 * @param {String} field
792 * @param {String|Array} val
793 * @return {ServerResponse} for chaining
794 * @api public
795 */
796MockExpressResponse.prototype.append = function append(field, val) {
797 var prev = this.get(field)
798 var value = val
799
800 if (prev) {
801 // concat the new and prev vals
802 value = Array.isArray(prev)
803 ? prev.concat(val)
804 : Array.isArray(val)
805 ? [prev].concat(val)
806 : [prev, val]
807 }
808
809 return this.set(field, value)
810}
811
812/**
813 * Set header `field` to `val`, or pass
814 * an object of header fields.
815 *
816 * Examples:
817 *
818 * res.set('Foo', ['bar', 'baz']);
819 * res.set('Accept', 'application/json');
820 * res.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
821 *
822 * Aliased as `res.header()`.
823 *
824 * @param {String|Object|Array} field
825 * @param {String} val
826 * @return {ServerResponse} for chaining
827 * @api public
828 */
829MockExpressResponse.prototype.set = MockExpressResponse.prototype.header = function header(
830 field,
831 val
832) {
833 if (arguments.length === 2) {
834 if (Array.isArray(val)) {
835 val = val.map(String)
836 } else {
837 val = String(val)
838 }
839 if ('content-type' === field.toLowerCase() && !/;\s*charset\s*=/.test(val)) {
840 var charset = mime.charsets.lookup(val.split(';')[0])
841 if (charset) {
842 val += '; charset=' + charset.toLowerCase()
843 }
844 }
845 this.setHeader(field, val)
846 } else {
847 for (var key in field) {
848 this.set(key, field[key])
849 }
850 }
851 return this
852}
853
854/**
855 * Get value for header `field`.
856 *
857 * @param {String} field
858 * @return {String}
859 * @api public
860 */
861MockExpressResponse.prototype.get = function(field) {
862 return this.getHeader(field)
863}
864
865/**
866 * Clear cookie `name`.
867 *
868 * @param {String} name
869 * @param {Object} options
870 * @return {ServerResponse} for chaining
871 * @api public
872 */
873MockExpressResponse.prototype.clearCookie = function(name, options) {
874 var opts = {
875 expires: new Date(1),
876 path: '/',
877 }
878 return this.cookie(name, '', options ? merge(opts, options) : opts)
879}
880
881/**
882 * Set cookie `name` to `val`, with the given `options`.
883 *
884 * Options:
885 *
886 * - `maxAge` max-age in milliseconds, converted to `expires`
887 * - `signed` sign the cookie
888 * - `path` defaults to "/"
889 *
890 * Examples:
891 *
892 * // "Remember Me" for 15 minutes
893 * res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
894 *
895 * // save as above
896 * res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
897 *
898 * @param {String} name
899 * @param {String|Object} val
900 * @param {Options} options
901 * @return {ServerResponse} for chaining
902 * @api public
903 */
904MockExpressResponse.prototype.cookie = function(name, val, options) {
905 options = merge({}, options)
906 var secret = this.req.secret
907 var signed = options.signed
908 if (signed && !secret) {
909 throw new Error('cookieParser("secret") required for signed cookies')
910 }
911 if ('number' === typeof val) {
912 val = val.toString()
913 }
914 if ('object' === typeof val) {
915 val = 'j:' + JSON.stringify(val)
916 }
917 if (signed) {
918 val = 's:' + sign(val, secret)
919 }
920 if ('maxAge' in options) {
921 options.expires = new Date(Date.now() + options.maxAge)
922 options.maxAge /= 1000
923 }
924 if (null === options.path) {
925 options.path = '/'
926 }
927 var headerVal = cookie.serialize(name, String(val), options)
928
929 // supports multiple 'res.cookie' calls by getting previous value
930 var prev = this.get('Set-Cookie')
931 if (prev) {
932 if (Array.isArray(prev)) {
933 headerVal = prev.concat(headerVal)
934 } else {
935 headerVal = [prev, headerVal]
936 }
937 }
938 this.set('Set-Cookie', headerVal)
939 return this
940}
941
942/**
943 * Set the location header to `url`.
944 *
945 * The given `url` can also be "back", which redirects
946 * to the _Referrer_ or _Referer_ headers or "/".
947 *
948 * Examples:
949 *
950 * res.location('/foo/bar').;
951 * res.location('http://example.com');
952 * res.location('../login');
953 *
954 * @param {String} url
955 * @return {ServerResponse} for chaining
956 * @api public
957 */
958MockExpressResponse.prototype.location = function(url) {
959 var req = this.req
960
961 // "back" is an alias for the referrer
962 if ('back' === url) {
963 url = req.get('Referrer') || '/'
964 }
965
966 // Respond
967 this.set('Location', url)
968 return this
969}
970
971/**
972 * Redirect to the given `url` with optional response `status`
973 * defaulting to 302.
974 *
975 * The resulting `url` is determined by `res.location()`, so
976 * it will play nicely with mounted apps, relative paths,
977 * `"back"` etc.
978 *
979 * Examples:
980 *
981 * res.redirect('/foo/bar');
982 * res.redirect('http://example.com');
983 * res.redirect(301, 'http://example.com');
984 * res.redirect('../login'); // /blog/post/1 -> /blog/login
985 *
986 * @api public
987 */
988MockExpressResponse.prototype.redirect = function redirect(url) {
989 var address = url
990 var body
991 var status = 302
992
993 // allow status / url
994 if (arguments.length === 2) {
995 if (typeof arguments[0] === 'number') {
996 status = arguments[0]
997 address = arguments[1]
998 } else {
999 deprecate('res.redirect(url, status): Use res.redirect(status, url) instead')
1000 status = arguments[1]
1001 }
1002 }
1003
1004 // Set location header
1005 this.location(address)
1006 address = this.get('Location')
1007
1008 // Support text/{plain,html} by default
1009 this.format({
1010 text: function() {
1011 body = STATUS_CODES[status] + '. Redirecting to ' + encodeURI(address)
1012 },
1013
1014 html: function() {
1015 var u = escapeHtml(address)
1016 body =
1017 '<p>' +
1018 STATUS_CODES[status] +
1019 '. Redirecting to <a href="' +
1020 u +
1021 '">' +
1022 u +
1023 '</a></p>'
1024 },
1025
1026 default: function() {
1027 body = ''
1028 },
1029 })
1030
1031 // Respond
1032 this.statusCode = status
1033 this.set('Content-Length', Buffer.byteLength(body))
1034
1035 if (this.req.method === 'HEAD') {
1036 this.end()
1037 } else {
1038 this.end(body)
1039 }
1040}
1041
1042/**
1043 * Add `field` to Vary. If already present in the Vary set, then
1044 * this call is simply ignored.
1045 *
1046 * @param {Array|String} field
1047 * @return {ServerResponse} for chaining
1048 * @api public
1049 */
1050MockExpressResponse.prototype.vary = function(field) {
1051 // checks for back-compat
1052 if (!field || (Array.isArray(field) && !field.length)) {
1053 deprecate('res.vary(): Provide a field name')
1054 return this
1055 }
1056
1057 vary(this, field)
1058
1059 return this
1060}
1061
1062/**
1063 * Render `view` with the given `options` and optional callback `fn`.
1064 * When a callback function is given a response will _not_ be made
1065 * automatically, otherwise a response of _200_ and _text/html_ is given.
1066 *
1067 * Options:
1068 *
1069 * - `cache` boolean hinting to the engine it should cache
1070 * - `filename` filename of the view being rendered
1071 *
1072 * @api public
1073 */
1074MockExpressResponse.prototype.render = function(view, options, fn) {
1075 options = options || {}
1076 var self = this
1077 var req = this.req
1078 var app = this.app
1079
1080 // support callback function as second arg
1081 if ('function' === typeof options) {
1082 ;(fn = options), (options = {})
1083 }
1084
1085 // merge res.locals
1086 options._locals = self.locals
1087
1088 // default callback to respond
1089 fn =
1090 fn ||
1091 function(err, str) {
1092 if (err) {
1093 return req.next(err)
1094 }
1095
1096 self.send(str)
1097 }
1098
1099 // render
1100 app.render(view, options, fn)
1101}
1102
1103/**
1104 * @description export MockExpressResponse
1105 * @type {[type]}
1106 */
1107module.exports = MockExpressResponse