UNPKG

mock-http

Version:

mock http request response

241 lines (224 loc) 7.79 kB
'use strict' /** * @module lib/Response * @copyright 2014-present commenthol * @license MIT */ var util = require('util') var Writable = require('stream').Writable var append = require('./append') var pick = require('mergee').pick var STATUS_CODES = require('http').STATUS_CODES var isStream1 = (function () { if (/^v0\.(8|10)\./.test(process.version)) { return true } })() /** * Mock implementation of Class http.ServerResponse * * It behaves like the class, apart from really handling a socket. I.e. it implements the Writable Stream Class as well. * All methods can be used to mock a server response such allowing to unit-test e.g. connect middleware * * States are stored in the interal object `Response._internal` and can be queried from your unit-tests * * _internal: { * headers: {Object} Response headers * rawHeaders: {Object} Response headers * trailers: {Object} Trailing Response headers * buffer: {Buffer} Internal buffer represents response body * timedout: {Boolean} If true than `Response.setTimeout` was called. * ended: {Boolean} If true than `Response.end` was called. * } * * @class * @see http://nodejs.org/api/http.html#http_class_http_serverresponse * @param {Object} [options] - options object * @param {Number} options.highWaterMark - highWaterMark for Writable Stream * @param {Function} options.onEnd - `function()` called on "end" * @param {Function} options.onFinish - `function()` called on "finish" event * @return {Stream.Writable} Writable Stream */ function Response (options) { var self = this options = options || {} Writable.call(this, pick(options, 'highWaterMark')) Object.assign(this, { _internal: { headers: {}, // / {Object} Response headers rawHeaders: {}, // / {Object} Raw Response headers trailers: {}, // / {Object} Trailing Response headers buffer: Buffer.from(''), // / {Buffer} Internal buffer represents response body timedout: false, // / {Boolean} If true than `Response.setTimeout` was called. ended: false, // / {Boolean} If true than `Response.end` was called. timer: null // / {Timer} The timer object used by `Response.setTimeout` }, socket: {}, // / {Object} Dummy object for socket connection: {}, // / {Object} Dummy object for connection statusCode: undefined, /** @see http://nodejs.org/api/http.html#http_response_statuscode */ statusMessage: '', /** @see http://nodejs.org/api/http.html#http_response_statusmessage */ headersSent: false, /** @see http://nodejs.org/api/http.html#http_response_headerssent */ sendDate: true /** @see http://nodejs.org/api/http.html#http_response_senddate */ }) this._headers = this._internal.headers Object.defineProperty(this, '_headerNames', { get () { var re = {} var raw = self._internal.rawHeaders for (var h in raw) { if (Object.prototype.hasOwnProperty.call(raw, h)) { re[h.toLowerCase()] = h } } return re } }) this.on('end', function () { options.onEnd && options.onEnd() }) this.on('finish', function () { options.onFinish && options.onFinish() }) return this } util.inherits(Response, Writable) /** @see http://nodejs.org/api/http.html#http_response_end_data_encoding */ Response.prototype.end = function end (data, encoding, cb) { if (this._internal.timedout && this._internal.ended) { // socket is already destroyed! return } if (this.sendDate) { this._internal.headers.Date = new Date().toUTCString() } this._internal.ended = true this.statusCode = this.statusCode || 200 this.statusMessage = STATUS_CODES[this.statusCode] || '' this.headersSent = true clearTimeout(this._internal.timer) if (isStream1) { this.emit('end') Writable.prototype.end.call(this, data, encoding, cb) } else { Writable.prototype.end.call(this, data, encoding, cb) this.emit('end') } } /** @see http://nodejs.org/api/http.html#http_response_write_chunk_encoding */ Response.prototype._write = function (data, encoding, done) { this._internal.buffer = append(this._internal.buffer, data, encoding) done() } Object.assign(Response.prototype, { /** @see http://nodejs.org/api/http.html#http_response_writecontinue */ writeContinue () { this.headersSent = true this.statusCode = 100 this.statusMessage = STATUS_CODES[this.statusCode] }, /** @see http://nodejs.org/api/http.html#http_response_writehead_statuscode_reasonphrase_headers */ writeHead (statusCode, reasonPhrase, headers) { var i if (typeof reasonPhrase === 'object') { headers = reasonPhrase reasonPhrase = null } if (reasonPhrase) { this._internal.reasonPhrase = reasonPhrase } for (i in headers) { this.setHeader(i, headers[i]) } this.statusCode = statusCode this.statusMessage = STATUS_CODES[this.statusCode] this.headersSent = true }, /** @see http://nodejs.org/api/http.html#http_response_settimeout_msecs_callback */ setTimeout (msecs, callback) { var self = this if (!msecs) return this._internal.timer = setTimeout(function () { self._internal.timedout = true if (callback) { callback() } else { self._internal.ended = true } }, msecs) }, /** @see http://nodejs.org/api/http.html#http_response_setheader_name_value */ setHeader (name, value) { if (this.headersSent) { throw new Error('Can\'t set headers after they are sent.') } else { this._internal.headers[name.toLowerCase()] = value this._internal.rawHeaders[name] = value } }, /** @see http://nodejs.org/api/http.html#http_response_getheader_name */ getHeader (name) { return this._internal.headers[name.toLowerCase()] }, /** @see https://nodejs.org/api/http.html#http_response_getheaders */ getHeaders () { var self = this return Object.keys(this._internal.headers).reduce(function (o, name) { var _name = name.toLowerCase() o[_name] = self._internal.headers[name] return o }, {}) }, /** @see https://nodejs.org/api/http.html#http_response_getheadernames */ getHeaderNames () { return Object.keys(this._internal.headers).map(function (name) { return String(name).toLowerCase() }) }, /** @see https://nodejs.org/api/http.html#http_response_hasheader_name */ hasHeader (name) { return this._internal.headers[name.toLowerCase()] != undefined // eslint-disable-line eqeqeq }, /** @see http://nodejs.org/api/http.html#http_response_removeheader_name */ removeHeader (name) { if (this.headersSent) { throw new Error('Can\'t remove headers after they are sent.') } else { name = name.toLowerCase() if (this._internal.headers[name]) { delete this._internal.headers[name] } } }, /** @see http://nodejs.org/api/http.html#http_response_addtrailers_headers */ addTrailers (headers) { if (this._internal.headers.trailer) { for (var i in headers) { this._internal.trailers[i] = headers[i] } } } }) // test API - they do **NOT** exist in the "real" API Object.assign(Response.prototype, { /** * Test only - get internal buffer * @param {String} [encoding] - Default='utf8' * @return {Buffer} */ getBuffer (encoding) { return this._internal.buffer.toString(encoding || 'utf8') }, /** * Test only - get status if Response has ended * @return {Boolean} */ hasEnded () { return this._internal.ended }, /** * Test only - get status if Response has timed-out * @return {Boolean} */ hasTimedout () { return this._internal.timedout } }) module.exports = Response