123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 |
- /**
- * Module dependencies.
- */
- var request = require('superagent');
- var util = require('util');
- var http = require('http');
- var https = require('https');
- var assert = require('assert');
- var Request = request.Request;
- /**
- * Expose `Test`.
- */
- module.exports = Test;
- /**
- * Initialize a new `Test` with the given `app`,
- * request `method` and `path`.
- *
- * @param {Server} app
- * @param {String} method
- * @param {String} path
- * @api public
- */
- function Test(app, method, path, host) {
- Request.call(this, method.toUpperCase(), path);
- this.redirects(0);
- this.buffer();
- this.app = app;
- this._asserts = [];
- this.url = typeof app === 'string'
- ? app + path
- : this.serverAddress(app, path, host);
- }
- /**
- * Inherits from `Request.prototype`.
- */
- Object.setPrototypeOf(Test.prototype, Request.prototype);
- /**
- * Returns a URL, extracted from a server.
- *
- * @param {Server} app
- * @param {String} path
- * @returns {String} URL address
- * @api private
- */
- Test.prototype.serverAddress = function(app, path, host) {
- var addr = app.address();
- var port;
- var protocol;
- if (!addr) this._server = app.listen(0);
- port = app.address().port;
- protocol = app instanceof https.Server ? 'https' : 'http';
- return protocol + '://' + (host || '127.0.0.1') + ':' + port + path;
- };
- /**
- * Expectations:
- *
- * .expect(200)
- * .expect(200, fn)
- * .expect(200, body)
- * .expect('Some body')
- * .expect('Some body', fn)
- * .expect('Content-Type', 'application/json')
- * .expect('Content-Type', 'application/json', fn)
- * .expect(fn)
- *
- * @return {Test}
- * @api public
- */
- Test.prototype.expect = function(a, b, c) {
- // callback
- if (typeof a === 'function') {
- this._asserts.push(a);
- return this;
- }
- if (typeof b === 'function') this.end(b);
- if (typeof c === 'function') this.end(c);
- // status
- if (typeof a === 'number') {
- this._asserts.push(this._assertStatus.bind(this, a));
- // body
- if (typeof b !== 'function' && arguments.length > 1) {
- this._asserts.push(this._assertBody.bind(this, b));
- }
- return this;
- }
- // header field
- if (typeof b === 'string' || typeof b === 'number' || b instanceof RegExp) {
- this._asserts.push(this._assertHeader.bind(this, { name: '' + a, value: b }));
- return this;
- }
- // body
- this._asserts.push(this._assertBody.bind(this, a));
- return this;
- };
- /**
- * Defer invoking superagent's `.end()` until
- * the server is listening.
- *
- * @param {Function} fn
- * @api public
- */
- Test.prototype.end = function(fn) {
- var self = this;
- var server = this._server;
- var end = Request.prototype.end;
- end.call(this, function(err, res) {
- if (server && server._handle) return server.close(localAssert);
- localAssert();
- function localAssert() {
- self.assert(err, res, fn);
- }
- });
- return this;
- };
- /**
- * Perform assertions and invoke `fn(err, res)`.
- *
- * @param {?Error} resError
- * @param {Response} res
- * @param {Function} fn
- * @api private
- */
- Test.prototype.assert = function(resError, res, fn) {
- var error;
- var i;
- // check for unexpected network errors or server not running/reachable errors
- // when there is no response and superagent sends back a System Error
- // do not check further for other asserts, if any, in such case
- // https://nodejs.org/api/errors.html#errors_common_system_errors
- var sysErrors = {
- ECONNREFUSED: 'Connection refused',
- ECONNRESET: 'Connection reset by peer',
- EPIPE: 'Broken pipe',
- ETIMEDOUT: 'Operation timed out'
- };
- if (!res && resError) {
- if (resError instanceof Error && resError.syscall === 'connect'
- && Object.getOwnPropertyNames(sysErrors).indexOf(resError.code) >= 0) {
- error = new Error(resError.code + ': ' + sysErrors[resError.code]);
- } else {
- error = resError;
- }
- }
- // asserts
- for (i = 0; i < this._asserts.length && !error; i += 1) {
- error = this._assertFunction(this._asserts[i], res);
- }
- // set unexpected superagent error if no other error has occurred.
- if (!error && resError instanceof Error && (!res || resError.status !== res.status)) {
- error = resError;
- }
- fn.call(this, error || null, res);
- };
- /**
- * Perform assertions on a response body and return an Error upon failure.
- *
- * @param {Mixed} body
- * @param {Response} res
- * @return {?Error}
- * @api private
- */
- Test.prototype._assertBody = function(body, res) {
- var isregexp = body instanceof RegExp;
- var a;
- var b;
- // parsed
- if (typeof body === 'object' && !isregexp) {
- try {
- assert.deepStrictEqual(body, res.body);
- } catch (err) {
- a = util.inspect(body);
- b = util.inspect(res.body);
- return error('expected ' + a + ' response body, got ' + b, body, res.body);
- }
- } else if (body !== res.text) {
- // string
- a = util.inspect(body);
- b = util.inspect(res.text);
- // regexp
- if (isregexp) {
- if (!body.test(res.text)) {
- return error('expected body ' + b + ' to match ' + body, body, res.body);
- }
- } else {
- return error('expected ' + a + ' response body, got ' + b, body, res.body);
- }
- }
- };
- /**
- * Perform assertions on a response header and return an Error upon failure.
- *
- * @param {Object} header
- * @param {Response} res
- * @return {?Error}
- * @api private
- */
- Test.prototype._assertHeader = function(header, res) {
- var field = header.name;
- var actual = res.header[field.toLowerCase()];
- var fieldExpected = header.value;
- if (typeof actual === 'undefined') return new Error('expected "' + field + '" header field');
- // This check handles header values that may be a String or single element Array
- if ((Array.isArray(actual) && actual.toString() === fieldExpected)
- || fieldExpected === actual) {
- return;
- }
- if (fieldExpected instanceof RegExp) {
- if (!fieldExpected.test(actual)) {
- return new Error('expected "' + field + '" matching '
- + fieldExpected + ', got "' + actual + '"');
- }
- } else {
- return new Error('expected "' + field + '" of "' + fieldExpected + '", got "' + actual + '"');
- }
- };
- /**
- * Perform assertions on the response status and return an Error upon failure.
- *
- * @param {Number} status
- * @param {Response} res
- * @return {?Error}
- * @api private
- */
- Test.prototype._assertStatus = function(status, res) {
- var a;
- var b;
- if (res.status !== status) {
- a = http.STATUS_CODES[status];
- b = http.STATUS_CODES[res.status];
- return new Error('expected ' + status + ' "' + a + '", got ' + res.status + ' "' + b + '"');
- }
- };
- /**
- * Performs an assertion by calling a function and return an Error upon failure.
- *
- * @param {Function} fn
- * @param {Response} res
- * @return {?Error}
- * @api private
- */
- Test.prototype._assertFunction = function(fn, res) {
- var err;
- try {
- err = fn(res);
- } catch (e) {
- err = e;
- }
- if (err instanceof Error) return err;
- };
- /**
- * Return an `Error` with `msg` and results properties.
- *
- * @param {String} msg
- * @param {Mixed} expected
- * @param {Mixed} actual
- * @return {Error}
- * @api private
- */
- function error(msg, expected, actual) {
- var err = new Error(msg);
- err.expected = expected;
- err.actual = actual;
- err.showDiff = true;
- return err;
- }
|