test.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. /**
  2. * Module dependencies.
  3. */
  4. var request = require('superagent')
  5. , util = require('util')
  6. , http = require('http')
  7. , https = require('https')
  8. , assert = require('assert')
  9. , Request = request.Request;
  10. /**
  11. * Expose `Test`.
  12. */
  13. module.exports = Test;
  14. /**
  15. * Initialize a new `Test` with the given `app`,
  16. * request `method` and `path`.
  17. *
  18. * @param {Server} app
  19. * @param {String} method
  20. * @param {String} path
  21. * @api public
  22. */
  23. function Test(app, method, path) {
  24. Request.call(this, method, path);
  25. this.redirects(0);
  26. this.buffer();
  27. this.app = app;
  28. this._asserts = [];
  29. this.url = 'string' == typeof app
  30. ? app + path
  31. : this.serverAddress(app, path);
  32. }
  33. /**
  34. * Inherits from `Request.prototype`.
  35. */
  36. Test.prototype.__proto__ = Request.prototype;
  37. /**
  38. * Returns a URL, extracted from a server.
  39. *
  40. * @param {Server} app
  41. * @param {String} path
  42. * @returns {String} URL address
  43. * @api private
  44. */
  45. Test.prototype.serverAddress = function(app, path){
  46. var addr = app.address();
  47. if (!addr) this._server = app.listen(0);
  48. var port = app.address().port;
  49. var protocol = app instanceof https.Server ? 'https' : 'http';
  50. return protocol + '://127.0.0.1:' + port + path;
  51. };
  52. /**
  53. * Expectations:
  54. *
  55. * .expect(200)
  56. * .expect(200, fn)
  57. * .expect(200, body)
  58. * .expect('Some body')
  59. * .expect('Some body', fn)
  60. * .expect('Content-Type', 'application/json')
  61. * .expect('Content-Type', 'application/json', fn)
  62. * .expect(fn)
  63. *
  64. * @return {Test}
  65. * @api public
  66. */
  67. Test.prototype.expect = function(a, b, c){
  68. // callback
  69. if ('function' == typeof a) {
  70. this._asserts.push(a);
  71. return this;
  72. }
  73. if ('function' == typeof b) this.end(b);
  74. if ('function' == typeof c) this.end(c);
  75. // status
  76. if ('number' == typeof a) {
  77. this._asserts.push(this._assertStatus.bind(this, a));
  78. // body
  79. if ('function' != typeof b && arguments.length > 1)
  80. this._asserts.push(this._assertBody.bind(this, b));
  81. return this;
  82. }
  83. // header field
  84. if ('string' == typeof b || 'number' == typeof b || b instanceof RegExp) {
  85. this._asserts.push(this._assertHeader.bind(this, {name: ''+a, value: b}));
  86. return this;
  87. }
  88. // body
  89. this._asserts.push(this._assertBody.bind(this, a));
  90. return this;
  91. };
  92. /**
  93. * Defer invoking superagent's `.end()` until
  94. * the server is listening.
  95. *
  96. * @param {Function} fn
  97. * @api public
  98. */
  99. Test.prototype.end = function(fn){
  100. var self = this;
  101. var server = this._server;
  102. var end = Request.prototype.end;
  103. end.call(this, function(err, res){
  104. if (server && server._handle) return server.close(assert);
  105. assert();
  106. function assert(){
  107. self.assert(err, res, fn);
  108. }
  109. });
  110. return this;
  111. };
  112. /**
  113. * Perform assertions and invoke `fn(err, res)`.
  114. *
  115. * @param {?Error} resError
  116. * @param {Response} res
  117. * @param {Function} fn
  118. * @api private
  119. */
  120. Test.prototype.assert = function(resError, res, fn){
  121. var error;
  122. // asserts
  123. for (var i = 0; i < this._asserts.length && !error; ++i) {
  124. error = this._assertFunction(this._asserts[i], res);
  125. }
  126. // set unexpected superagent error if no other error has occurred.
  127. if (!error && resError instanceof Error &&
  128. (!res || resError.status !== res.status))
  129. error = resError;
  130. fn.call(this, error || null, res);
  131. };
  132. /**
  133. * Perform assertions on a response body and return an Error upon failure.
  134. *
  135. * @param {Mixed} body
  136. * @param {Response} res
  137. * @return {?Error}
  138. * @api private
  139. */
  140. Test.prototype._assertBody = function(body, res) {
  141. var isregexp = body instanceof RegExp;
  142. // parsed
  143. if ('object' == typeof body && !isregexp) {
  144. try {
  145. assert.deepEqual(body, res.body);
  146. } catch (err) {
  147. var a = util.inspect(body);
  148. var b = util.inspect(res.body);
  149. return error('expected ' + a + ' response body, got ' + b, body, res.body);
  150. }
  151. } else {
  152. // string
  153. if (body !== res.text) {
  154. var a = util.inspect(body);
  155. var b = util.inspect(res.text);
  156. // regexp
  157. if (isregexp) {
  158. if (!body.test(res.text)) {
  159. return error('expected body ' + b + ' to match ' + body, body, res.body);
  160. }
  161. } else {
  162. return error('expected ' + a + ' response body, got ' + b, body, res.body);
  163. }
  164. }
  165. }
  166. };
  167. /**
  168. * Perform assertions on a response header and return an Error upon failure.
  169. *
  170. * @param {Object} header
  171. * @param {Response} res
  172. * @return {?Error}
  173. * @api private
  174. */
  175. Test.prototype._assertHeader = function(header, res) {
  176. var field = header.name;
  177. var actual = res.header[field.toLowerCase()];
  178. if (null == actual) return new Error('expected "' + field + '" header field');
  179. var fieldExpected = header.value;
  180. if (fieldExpected == actual) return;
  181. if (fieldExpected instanceof RegExp) {
  182. if (!fieldExpected.test(actual)) return new Error('expected "' + field + '" matching ' + fieldExpected + ', got "' + actual + '"');
  183. } else {
  184. return new Error('expected "' + field + '" of "' + fieldExpected + '", got "' + actual + '"');
  185. }
  186. };
  187. /**
  188. * Perform assertions on the response status and return an Error upon failure.
  189. *
  190. * @param {Number} status
  191. * @param {Response} res
  192. * @return {?Error}
  193. * @api private
  194. */
  195. Test.prototype._assertStatus = function(status, res) {
  196. if (res.status !== status) {
  197. var a = http.STATUS_CODES[status];
  198. var b = http.STATUS_CODES[res.status];
  199. return new Error('expected ' + status + ' "' + a + '", got ' + res.status + ' "' + b + '"');
  200. }
  201. };
  202. /**
  203. * Performs an assertion by calling a function and return an Error upon failure.
  204. *
  205. * @param {Function} fn
  206. * @param {Response} res
  207. * @return {?Error}
  208. * @api private
  209. */
  210. Test.prototype._assertFunction = function(check, res) {
  211. var err;
  212. try {
  213. err = check(res);
  214. } catch(e) {
  215. err = e;
  216. }
  217. if (err instanceof Error) return err;
  218. };
  219. /**
  220. * Return an `Error` with `msg` and results properties.
  221. *
  222. * @param {String} msg
  223. * @param {Mixed} expected
  224. * @param {Mixed} actual
  225. * @return {Error}
  226. * @api private
  227. */
  228. function error(msg, expected, actual) {
  229. var err = new Error(msg);
  230. err.expected = expected;
  231. err.actual = actual;
  232. err.showDiff = true;
  233. return err;
  234. }