This commit is contained in:
Lev
2021-06-16 19:39:43 -05:00
parent 2dc386e7fd
commit 51ce0e142a
2210 changed files with 263077 additions and 0 deletions

View File

@ -0,0 +1,388 @@
/*!
* chai-http
* Copyright(c) 2011-2012 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
/**
* ## Assertions
*
* The Chai HTTP module provides a number of assertions
* for the `expect` and `should` interfaces.
*/
module.exports = function (chai, _) {
/*!
* Module dependencies.
*/
var net = require('net');
var qs = require('qs');
var url = require('url');
var Cookie = require('cookiejar');
/*!
* Aliases.
*/
var Assertion = chai.Assertion
, i = _.inspect;
/*!
* Expose request builder
*/
chai.request = require('./request');
/*!
* Content types hash. Used to
* define `Assertion` properties.
*
* @type {Object}
*/
var contentTypes = {
json: 'application/json'
, text: 'text/plain'
, html: 'text/html'
};
/*!
* Return a header from `Request` or `Response` object.
*
* @param {Request|Response} object
* @param {String} Header
* @returns {String|Undefined}
*/
function getHeader(obj, key) {
if (key) key = key.toLowerCase();
if (obj.getHeader) return obj.getHeader(key);
if (obj.headers) return obj.headers[key];
};
/**
* ### .status (code)
*
* Assert that a response has a supplied status.
*
* ```js
* expect(res).to.have.status(200);
* ```
*
* @param {Number} status number
* @name status
* @api public
*/
Assertion.addMethod('status', function (code) {
var hasStatus = Boolean('status' in this._obj || 'statusCode' in this._obj);
new Assertion(hasStatus).assert(
hasStatus
, "expected #{act} to have keys 'status', or 'statusCode'"
, null // never negated
, hasStatus // expected
, this._obj // actual
, false // no diff
);
var status = this._obj.status || this._obj.statusCode;
this.assert(
status == code
, 'expected #{this} to have status code #{exp} but got #{act}'
, 'expected #{this} to not have status code #{act}'
, code
, status
);
});
/**
* ### .header (key[, value])
*
* Assert that a `Response` or `Request` object has a header.
* If a value is provided, equality to value will be asserted.
* You may also pass a regular expression to check.
*
* __Note:__ When running in a web browser, the
* [same-origin policy](https://tools.ietf.org/html/rfc6454#section-3)
* only allows Chai HTTP to read
* [certain headers](https://www.w3.org/TR/cors/#simple-response-header),
* which can cause assertions to fail.
*
* ```js
* expect(req).to.have.header('x-api-key');
* expect(req).to.have.header('content-type', 'text/plain');
* expect(req).to.have.header('content-type', /^text/);
* ```
*
* @param {String} header key (case insensitive)
* @param {String|RegExp} header value (optional)
* @name header
* @api public
*/
Assertion.addMethod('header', function (key, value) {
var header = getHeader(this._obj, key);
if (arguments.length < 2) {
this.assert(
'undefined' !== typeof header || null === header
, 'expected header \'' + key + '\' to exist'
, 'expected header \'' + key + '\' to not exist'
);
} else if (arguments[1] instanceof RegExp) {
this.assert(
value.test(header)
, 'expected header \'' + key + '\' to match ' + value + ' but got ' + i(header)
, 'expected header \'' + key + '\' not to match ' + value + ' but got ' + i(header)
, value
, header
);
} else {
this.assert(
header == value
, 'expected header \'' + key + '\' to have value ' + value + ' but got ' + i(header)
, 'expected header \'' + key + '\' to not have value ' + value
, value
, header
);
}
});
/**
* ### .headers
*
* Assert that a `Response` or `Request` object has headers.
*
* __Note:__ When running in a web browser, the
* [same-origin policy](https://tools.ietf.org/html/rfc6454#section-3)
* only allows Chai HTTP to read
* [certain headers](https://www.w3.org/TR/cors/#simple-response-header),
* which can cause assertions to fail.
*
* ```js
* expect(req).to.have.headers;
* ```
*
* @name headers
* @api public
*/
Assertion.addProperty('headers', function () {
this.assert(
this._obj.headers || this._obj.getHeader
, 'expected #{this} to have headers or getHeader method'
, 'expected #{this} to not have headers or getHeader method'
);
});
/**
* ### .ip
*
* Assert that a string represents valid ip address.
*
* ```js
* expect('127.0.0.1').to.be.an.ip;
* expect('2001:0db8:85a3:0000:0000:8a2e:0370:7334').to.be.an.ip;
* ```
*
* @name ip
* @api public
*/
Assertion.addProperty('ip', function () {
this.assert(
net.isIP(this._obj)
, 'expected #{this} to be an ip'
, 'expected #{this} to not be an ip'
);
});
/**
* ### .json / .text / .html
*
* Assert that a `Response` or `Request` object has a given content-type.
*
* ```js
* expect(req).to.be.json;
* expect(req).to.be.html;
* expect(req).to.be.text;
* ```
*
* @name json
* @name html
* @name text
* @api public
*/
function checkContentType (name) {
var val = contentTypes[name];
Assertion.addProperty(name, function () {
new Assertion(this._obj).to.have.headers;
var ct = getHeader(this._obj, 'content-type')
, ins = i(ct) === 'undefined'
? 'headers'
: i(ct);
this.assert(
ct && ~ct.indexOf(val)
, 'expected ' + ins + ' to include \'' + val + '\''
, 'expected ' + ins + ' to not include \'' + val + '\''
);
});
}
Object
.keys(contentTypes)
.forEach(checkContentType);
/**
* ### .redirect
*
* Assert that a `Response` object has a redirect status code.
*
* ```js
* expect(res).to.redirect;
* ```
*
* @name redirect
* @api public
*/
Assertion.addProperty('redirect', function() {
var redirectCodes = [301, 302, 303, 307, 308]
, status = this._obj.status
, redirects = this._obj.redirects;
this.assert(
redirectCodes.indexOf(status) >= 0 || redirects && redirects.length
, "expected redirect with 30X status code but got " + status
, "expected not to redirect but got " + status + " status"
);
});
/**
* ### .redirectTo
*
* Assert that a `Response` object redirects to the supplied location.
*
* ```js
* expect(res).to.redirectTo('http://example.com');
* ```
*
* @param {String|RegExp} location url
* @name redirectTo
* @api public
*/
Assertion.addMethod('redirectTo', function(destination) {
var redirects = this._obj.redirects;
new Assertion(this._obj).to.redirect;
if(redirects && redirects.length) {
var hasRedirected;
if (Object.prototype.toString.call(destination) === '[object RegExp]') {
hasRedirected = redirects.some(redirect => destination.test(redirect));
} else {
hasRedirected = redirects.indexOf(destination) > -1;
}
this.assert(
hasRedirected
, 'expected redirect to ' + destination + ' but got ' + redirects.join(' then ')
, 'expected not to redirect to ' + destination + ' but got ' + redirects.join(' then ')
);
} else {
var assertion = new Assertion(this._obj);
_.transferFlags(this, assertion);
assertion.with.header('location', destination);
}
});
/**
* ### .param
*
* Assert that a `Request` object has a query string parameter with a given
* key, (optionally) equal to value
*
* ```js
* expect(req).to.have.param('orderby');
* expect(req).to.have.param('orderby', 'date');
* expect(req).to.not.have.param('limit');
* ```
*
* @param {String} parameter name
* @param {String} parameter value
* @name param
* @api public
*/
Assertion.addMethod('param', function(name, value) {
var assertion = new Assertion();
_.transferFlags(this, assertion);
assertion._obj = qs.parse(url.parse(this._obj.url).query);
assertion.property.apply(assertion, arguments);
});
/**
* ### .cookie
*
* Assert that a `Request`, `Response` or `Agent` object has a cookie header with a
* given key, (optionally) equal to value
*
* ```js
* expect(req).to.have.cookie('session_id');
* expect(req).to.have.cookie('session_id', '1234');
* expect(req).to.not.have.cookie('PHPSESSID');
* expect(res).to.have.cookie('session_id');
* expect(res).to.have.cookie('session_id', '1234');
* expect(res).to.not.have.cookie('PHPSESSID');
* expect(agent).to.have.cookie('session_id');
* expect(agent).to.have.cookie('session_id', '1234');
* expect(agent).to.not.have.cookie('PHPSESSID');
* ```
*
* @param {String} parameter name
* @param {String} parameter value
* @name param
* @api public
*/
Assertion.addMethod('cookie', function (key, value) {
var header = getHeader(this._obj, 'set-cookie')
, cookie;
if (!header) {
header = (getHeader(this._obj, 'cookie') || '').split(';');
}
if (this._obj instanceof chai.request.agent && this._obj.jar) {
cookie = this._obj.jar.getCookie(key, Cookie.CookieAccessInfo.All);
} else {
cookie = Cookie.CookieJar();
cookie.setCookies(header);
cookie = cookie.getCookie(key, Cookie.CookieAccessInfo.All);
}
if (arguments.length === 2) {
this.assert(
cookie.value == value
, 'expected cookie \'' + key + '\' to have value #{exp} but got #{act}'
, 'expected cookie \'' + key + '\' to not have value #{exp}'
, value
, cookie.value
);
} else {
this.assert(
'undefined' !== typeof cookie || null === cookie
, 'expected cookie \'' + key + '\' to exist'
, 'expected cookie \'' + key + '\' to not exist'
);
}
});
};

View File

@ -0,0 +1,14 @@
/*!
* chai-http - request helper
* Copyright(c) 2011-2012 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
/*!
* net.isIP shim for browsers
*/
var isIP = require('is-ip');
exports.isIP = isIP;
exports.isIPv4 = isIP.v4;
exports.isIPv6 = isIP.v6;

View File

@ -0,0 +1,353 @@
/*!
* chai-http - request helper
* Copyright(c) 2011-2012 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
/*!
* Module dependancies
*/
var http = require('http')
, https = require('https')
, methods = require('methods')
, superagent = require('superagent')
, Agent = superagent.agent
, Request = superagent.Request
, util = require('util');
/**
* ## Integration Testing
*
* Chai HTTP provides an interface for live integration
* testing via [superagent](https://github.com/visionmedia/superagent).
* To do this, you must first
* construct a request to an application or url.
*
* Upon construction you are provided a chainable api that
* allows you to specify the http VERB request (get, post, etc)
* that you wish to invoke.
*
* #### Application / Server
*
* You may use a function (such as an express or connect app)
* or a node.js http(s) server as the foundation for your request.
* If the server is not running, chai-http will find a suitable
* port to listen on for a given test.
*
* __Note:__ This feature is only supported on Node.js, not in web browsers.
*
* ```js
* chai.request(app)
* .get('/')
* ```
*
* #### URL
*
* You may also use a base url as the foundation of your request.
*
* ```js
* chai.request('http://localhost:8080')
* .get('/')
* ```
*
* #### Setting up requests
*
* Once a request is created with a given VERB, it can have headers, form data,
* json, or even file attachments added to it, all with a simple API:
*
* ```js
* // Send some JSON
* chai.request(app)
* .put('/user/me')
* .set('X-API-Key', 'foobar')
* .send({ password: '123', confirmPassword: '123' })
* ```
*
* ```js
* // Send some Form Data
* chai.request(app)
* .post('/user/me')
* .type('form')
* .send({'_method': 'put'})
* .send({'password': '123'})
* .send({'confirmPassword', '123'})
* ```
*
* ```js
* // Attach a file
* chai.request(app)
* .post('/user/avatar')
* .attach('imageField', fs.readFileSync('avatar.png'), 'avatar.png')
* ```
*
* ```js
* // Authenticate with Basic authentication
* chai.request(app)
* .get('/protected')
* .auth('user', 'pass')
* ```
*
* ```js
* // Chain some GET query parameters
* chai.request(app)
* .get('/search')
* .query({name: 'foo', limit: 10}) // /search?name=foo&limit=10
* ```
*
* #### Dealing with the response - traditional
*
* To make the request and assert on its response, the `end` method can be used:
*
* ```js
* chai.request(app)
* .put('/user/me')
* .send({ password: '123', confirmPassword: '123' })
* .end(function (err, res) {
* expect(err).to.be.null;
* expect(res).to.have.status(200);
* });
* ```
* ##### Caveat
* Because the `end` function is passed a callback, assertions are run
* asynchronously. Therefore, a mechanism must be used to notify the testing
* framework that the callback has completed. Otherwise, the test will pass before
* the assertions are checked.
*
* For example, in the [Mocha test framework](http://mochajs.org/), this is
* accomplished using the
* [`done` callback](https://mochajs.org/#asynchronous-code), which signal that the
* callback has completed, and the assertions can be verified:
*
* ```js
* it('fails, as expected', function(done) { // <= Pass in done callback
* chai.request('http://localhost:8080')
* .get('/')
* .end(function(err, res) {
* expect(res).to.have.status(123);
* done(); // <= Call done to signal callback end
* });
* }) ;
*
* it('succeeds silently!', function() { // <= No done callback
* chai.request('http://localhost:8080')
* .get('/')
* .end(function(err, res) {
* expect(res).to.have.status(123); // <= Test completes before this runs
* });
* }) ;
* ```
*
* When `done` is passed in, Mocha will wait until the call to `done()`, or until
* the [timeout](http://mochajs.org/#timeouts) expires. `done` also accepts an
* error parameter when signaling completion.
*
* #### Dealing with the response - Promises
*
* If `Promise` is available, `request()` becomes a Promise capable library -
* and chaining of `then`s becomes possible:
*
* ```js
* chai.request(app)
* .put('/user/me')
* .send({ password: '123', confirmPassword: '123' })
* .then(function (res) {
* expect(res).to.have.status(200);
* })
* .catch(function (err) {
* throw err;
* })
* ```
*
* __Note:__ Node.js version 0.10.x and some older web browsers do not have
* native promise support. You can use any spec compliant library, such as:
* - [kriskowal/q](https://github.com/kriskowal/q)
* - [stefanpenner/es6-promise](https://github.com/stefanpenner/es6-promise)
* - [petkaantonov/bluebird](https://github.com/petkaantonov/bluebird)
* - [then/promise](https://github.com/then/promise)
* You will need to set the library you use to `global.Promise`, before
* requiring in chai-http. For example:
*
* ```js
* // Add promise support if this does not exist natively.
* if (!global.Promise) {
* global.Promise = require('q');
* }
* var chai = require('chai');
* chai.use(require('chai-http'));
*
* ```
*
* #### Retaining cookies with each request
*
* Sometimes you need to keep cookies from one request, and send them with the
* next. For this, `.request.agent()` is available:
*
* ```js
* // Log in
* var agent = chai.request.agent(app)
* agent
* .post('/session')
* .send({ username: 'me', password: '123' })
* .then(function (res) {
* expect(res).to.have.cookie('sessionid');
* // The `agent` now has the sessionid cookie saved, and will send it
* // back to the server in the next request:
* return agent.get('/user/me')
* .then(function (res) {
* expect(res).to.have.status(200);
* })
* })
* ```
*
*/
module.exports = function (app) {
/*!
* @param {Mixed} function or server
* @returns {Object} API
*/
var server = ('function' === typeof app)
? http.createServer(app)
: app
, obj = {};
var keepOpen = false
if (typeof server !== 'string' && server && server.listen && server.address) {
if (!server.address()) {
server = server.listen(0)
}
}
obj.keepOpen = function() {
keepOpen = true
return this
}
obj.close = function(callback) {
if (server && server.close) {
server.close(callback);
}
else if(callback) {
callback();
}
return this
}
methods.forEach(function (method) {
obj[method] = function (path) {
return new Test(server, method, path)
.on('end', function() {
if(keepOpen === false) {
obj.close();
}
});
};
});
obj.del = obj.delete;
return obj;
};
module.exports.Test = Test;
module.exports.Request = Test;
module.exports.agent = TestAgent;
/*!
* Test
*
* An extension of superagent.Request,
* this provides the same chainable api
* as superagent so all things can be modified.
*
* @param {Object|String} server, app, or url
* @param {String} method
* @param {String} path
* @api private
*/
function Test (app, method, path) {
Request.call(this, method, path);
this.app = app;
this.url = typeof app === 'string' ? app + path : serverAddress(app, path);
this.ok(function() {
return true;
});
}
util.inherits(Test, Request);
function serverAddress (app, path) {
if ('string' === typeof app) {
return app + path;
}
var addr = app.address();
if (!addr) {
throw new Error('Server is not listening')
}
var protocol = (app instanceof https.Server) ? 'https' : 'http';
// If address is "unroutable" IPv4/6 address, then set to localhost
if (addr.address === '0.0.0.0' || addr.address === '::') {
addr.address = '127.0.0.1';
}
return protocol + '://' + addr.address + ':' + addr.port + path;
}
/*!
* agent
*
* Follows the same API as superagent.Request,
* but allows persisting of cookies between requests.
*
* @param {Object|String} server, app, or url
* @param {String} method
* @param {String} path
* @api private
*/
function TestAgent(app) {
if (!(this instanceof TestAgent)) return new TestAgent(app);
if (typeof app === 'function') app = http.createServer(app);
(Agent || Request).call(this);
this.app = app;
if (typeof app !== 'string' && app && app.listen && app.address && !app.address()) {
this.app = app.listen(0)
}
}
util.inherits(TestAgent, Agent || Request);
TestAgent.prototype.close = function close(callback) {
if (this.app && this.app.close) {
this.app.close(callback)
}
return this
}
TestAgent.prototype.keepOpen = function keepOpen() {
return this
}
// override HTTP verb methods
methods.forEach(function(method){
TestAgent.prototype[method] = function(url){
var req = new Test(this.app, method, url)
, self = this;
if (Agent) {
// When running in Node, cookies are managed via
// `Agent._saveCookies()` and `Agent._attachCookies()`.
req.on('response', function (res) { self._saveCookies(res); });
req.on('redirect', function (res) { self._saveCookies(res); });
req.on('redirect', function () { self._attachCookies(req); });
this._attachCookies(req);
}
else {
// When running in a web browser, cookies are managed via `Request.withCredentials()`.
// The browser will attach cookies based on same-origin policy.
// https://tools.ietf.org/html/rfc6454#section-3
req.withCredentials();
}
return req;
};
});
TestAgent.prototype.del = TestAgent.prototype.delete;