UNPKG

chai-http

Version:

Extend Chai Assertion library with tests for http apis

448 lines (401 loc) 11 kB
/*! * 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 dependencies. */ import net from 'net'; import url from 'url'; import Cookie from 'cookiejar'; import charset from 'charset'; import qs from 'qs'; import * as _request from './request.js'; /** * * @param {ChaiStatic} chai * @param {ChaiUtils} _ */ export default function (chai, _) { /*! * Aliases. */ const Assertion = chai.Assertion; const i = _.inspect; /*! * Expose request builder */ chai.request = _request; /*! * Content types hash. Used to * define `Assertion` properties. * * @type {Object} */ const 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) { const 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 ); const 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) { const 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) { const val = contentTypes[name]; Assertion.addProperty(name, function () { new Assertion(this._obj).to.have.headers; const 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); /** * ### .charset * * Assert that a `Response` or `Request` object has a given charset. * * ```js * expect(req).to.have.charset('utf-8'); * ``` * * @name charset * @api public */ Assertion.addMethod('charset', function (value) { value = value.toLowerCase(); const headers = this._obj.headers; let cs = charset(headers); /* * Fix charset() treating "utf8" as a special case * See https://github.com/node-modules/charset/issues/12 */ if (cs === 'utf8') { cs = 'utf-8'; } this.assert( cs != null && value === cs, 'expected content type to have ' + value + ' charset', 'expected content type to not have ' + value + ' charset' ); }); /** * ### .redirect * * Assert that a `Response` object has a redirect status code. * * ```js * expect(res).to.redirect; * ``` * * @name redirect * @api public */ Assertion.addProperty('redirect', function () { const 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) { const redirects = this._obj.redirects; new Assertion(this._obj).to.redirect; if (redirects && redirects.length) { let 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 { const 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 () { const 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) { let 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" ); } }); }