From 9ea499a2b914f8c27e74da75d7952802df26c550 Mon Sep 17 00:00:00 2001 From: Mark Owsiak Date: Tue, 2 Jan 2018 14:37:54 -0500 Subject: [PATCH] chore: apply latest generator --- .eslintrc | 2 +- .gitignore | 1 + .travis.yml | 18 +-- CONTRIBUTING.md | 6 +- lib/errors.js | 21 +++- lib/fetch.browser.js | 67 ++++++----- lib/fetch.js | 68 +++++++---- lib/gofer.js | 50 ++++---- lib/legacy.js | 1 + lib/request.js | 65 ++++++----- lib/response.js | 10 +- lib/url.js | 4 +- package.json | 12 +- test/.eslintrc | 3 - test/browser.test.js | 22 ++-- test/config.test.js | 61 ++++++---- test/fetch-http.test.js | 188 ++++++++++++++++++------------- test/fetch-https.test.js | 51 ++++----- test/fetch-search-domain.test.js | 65 ++++++----- test/fetch-timeout.test.js | 116 ++++++++++--------- test/gofer.test.js | 61 ++++++---- test/instrument.browser.js | 3 + test/instrument.js | 1 + test/instrumentation.test.js | 29 ++--- test/legacy.test.js | 50 ++++---- test/max-sockets.test.js | 121 ++++++++++++-------- test/mock-service.browser.js | 1 + test/mock-service.js | 57 ++++++---- test/multi-part-mapper.test.js | 47 ++++---- test/request-body.test.js | 92 ++++++++------- 30 files changed, 758 insertions(+), 535 deletions(-) diff --git a/.eslintrc b/.eslintrc index 0026929..243c323 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,3 +1,3 @@ { - "extends": "groupon/legacy" + "extends": "groupon/es5" } diff --git a/.gitignore b/.gitignore index 18927c0..a76bc24 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/yarn.lock node_modules/ npm-debug.log /tmp diff --git a/.travis.yml b/.travis.yml index 1c39b29..3dbe7ac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,12 @@ language: node_js node_js: - - '4' - - '6' - - '8' + - 4.6.1 + - 6.11.5 + - 8.9.0 deploy: - provider: script - script: ./node_modules/.bin/nlm release - skip_cleanup: true - 'on': - branch: master - node: '8' + - provider: script + script: ./node_modules/.bin/nlm release + skip_cleanup: true + 'on': + branch: master + node: 8.9.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cc3c836..934d19e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ - + # Contributing @@ -31,7 +31,7 @@ If you report a bug, please follow these guidelines: For small documentation changes, you can use [Github's editing feature](https://help.github.com/articles/editing-files-in-another-user-s-repository/). The only thing to keep in mind is to prefix the commit message with "docs: ". -The detault commit message generated by Github will lead to a failing CI build. +The default commit message generated by Github will lead to a failing CI build. For larger updates to the documentation it might be better to follow the [instructions for contributing code below](#contributing-code). @@ -52,7 +52,7 @@ The general steps for creating a pull request are: 1. If you're fixing a bug, be sure to write a test *first*. That way you can validate that the test actually catches the bug and doesn't pass. 1. Make your changes to the code. - Remember to update the tests if you add new features or change behavior. + Remember to update the tests if you add new features or change behavior. 1. Run the tests via `npm test`. This will also run style checks and other validations. You might see errors about uncommitted files. This is expected until you commit your changes. diff --git a/lib/errors.js b/lib/errors.js index 62e44e2..80b60d8 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -29,13 +29,21 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + 'use strict'; function StatusCodeError(statusCode, min, max, headers, method, url) { Error.call(this); - this.message = 'API Request returned a response outside the status code range ' + - '(code: ' + statusCode + ', range: [' + min + ', ' + max + '])'; + this.message = + 'API Request returned a response outside the status code range ' + + '(code: ' + + statusCode + + ', range: [' + + min + + ', ' + + max + + '])'; this.headers = headers; this.statusCode = statusCode; this.minStatusCode = min; @@ -73,7 +81,14 @@ function NotFoundError(min, max, headers, method, url) { } exportError(NotFoundError); -StatusCodeError.create = function createError(statusCode, min, max, headers, method, url) { +StatusCodeError.create = function createError( + statusCode, + min, + max, + headers, + method, + url +) { var error; switch (statusCode) { case 301: diff --git a/lib/fetch.browser.js b/lib/fetch.browser.js index 543943f..0878b4c 100644 --- a/lib/fetch.browser.js +++ b/lib/fetch.browser.js @@ -29,7 +29,10 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + 'use strict'; + +/* eslint-env browser */ /* global URLSearchParams */ var Url = require('url'); @@ -51,34 +54,36 @@ if (typeof fetch !== 'function') { throw new Error('Requires native fetch or polyfill'); } -function _callJSON(res) { +function callJSON(res) { return res.json(); } -function _callText(res) { +function callText(res) { return res.text(); } var reqProperties = { json: { value: function json() { - return this.then(_callJSON); + return this.then(callJSON); }, }, text: { value: function text() { - return this.then(_callText); + return this.then(callText); }, }, }; // Blob or BufferSource or FormData or URLSearchParams or USVString function isValidBody(body) { - return body === undefined || + return ( + body === undefined || typeof body === 'string' || (typeof FormData !== 'undefined' && body instanceof FormData) || - (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams); + (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) + ); } function validateBody(body) { @@ -91,15 +96,14 @@ function validateBody(body) { function generateSearch(queryString, qs) { var query = assign(qsParser.parse(queryString), qs || {}); var filtered = {}; - var queryKeys = Object.keys(query) - .filter(function ensureSet(key) { - var value = query[key]; - var isSet = value !== null && value !== undefined; - if (isSet) { - filtered[key] = value; - } - return isSet; - }); + var queryKeys = Object.keys(query).filter(function ensureSet(key) { + var value = query[key]; + var isSet = value !== null && value !== undefined; + if (isSet) { + filtered[key] = value; + } + return isSet; + }); if (queryKeys.length === 0) return ''; return '?' + qsParser.stringify(filtered); @@ -123,7 +127,8 @@ function defaultStatusCode(value, defaultValue) { if (value >= 0) { if (typeof value !== 'number') { throw new TypeError( - 'Invalid status code ' + JSON.stringify(value) + ', not a number'); + 'Invalid status code ' + JSON.stringify(value) + ', not a number' + ); } return value; } @@ -144,8 +149,7 @@ function verifyAndExtendResponse(url, options, response) { var max = defaultStatusCode(options.maxStatusCode, 299); function isAcceptableStatus(code) { - return (min === false || code >= min) && - (max === false || code <= max); + return (min === false || code >= min) && (max === false || code <= max); } var originalHeaders = response.headers; @@ -170,14 +174,21 @@ function verifyAndExtendResponse(url, options, response) { function generateStatusCodeError(code) { var error = StatusCodeError.create( - code, min, max, response.headers, options.method || 'GET', url); + code, + min, + max, + response.headers, + options.method || 'GET', + url + ); function rejectWithBody(body) { error.body = body; throw error; } - return response.text() + return response + .text() .then(parseErrorBody) .then(null, noop) .then(rejectWithBody); @@ -194,14 +205,15 @@ function defaultTimeout(value, defaultValue) { if (value >= 0) { if (typeof value !== 'number') { throw new TypeError( - 'Invalid timeout ' + JSON.stringify(value) + ', not a number'); + 'Invalid timeout ' + JSON.stringify(value) + ', not a number' + ); } return value; } return defaultValue; } -function _fetch(url, options) { +function fetchUrl(url, options) { if (typeof url !== 'string') { throw new TypeError('url has to be a string'); } @@ -224,9 +236,11 @@ function _fetch(url, options) { } else if (form !== undefined && form !== null) { if (typeof form !== 'object') { throw new TypeError( - 'Invalid form body (' + typeof form + ', expected object)'); + 'Invalid form body (' + typeof form + ', expected object)' + ); } - defaultHeaders['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'; + defaultHeaders['Content-Type'] = + 'application/x-www-form-urlencoded;charset=UTF-8'; body = qsParser.stringify(form); } @@ -234,7 +248,8 @@ function _fetch(url, options) { if (typeof auth === 'string') { defaultHeaders.Authorization = 'Basic ' + btoa(auth); } else if (auth !== null && typeof auth === 'object') { - defaultHeaders.Authorization = 'Basic ' + btoa(auth.username + ':' + auth.password); + defaultHeaders.Authorization = + 'Basic ' + btoa(auth.username + ':' + auth.password); } var timeout = defaultTimeout(options.timeout, DEFAULT_TIMEOUT); @@ -283,4 +298,4 @@ function _fetch(url, options) { return Object.defineProperties(result, reqProperties); } -module.exports = _fetch; +module.exports = fetchUrl; diff --git a/lib/fetch.js b/lib/fetch.js index eb2325e..986da44 100644 --- a/lib/fetch.js +++ b/lib/fetch.js @@ -29,7 +29,9 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + 'use strict'; + var http = require('http'); var https = require('https'); var parseUrl = require('url').parse; @@ -61,9 +63,12 @@ function getAgentsForService(options) { } function isValidBody(body) { - return body === undefined || - Buffer.isBuffer(body) || typeof body === 'string' || - (body && typeof body.pipe === 'function'); + return ( + body === undefined || + Buffer.isBuffer(body) || + typeof body === 'string' || + (body && typeof body.pipe === 'function') + ); } function validateBody(body) { @@ -92,15 +97,14 @@ function getAgent(options, urlObj) { function generateSearch(queryString, qs) { var query = assign(qsParser.parse(queryString), qs || {}); var filtered = {}; - var queryKeys = Object.keys(query) - .filter(function ensureSet(key) { - var value = query[key]; - var isSet = value !== null && value !== undefined; - if (isSet) { - filtered[key] = value; - } - return isSet; - }); + var queryKeys = Object.keys(query).filter(function ensureSet(key) { + var value = query[key]; + var isSet = value !== null && value !== undefined; + if (isSet) { + filtered[key] = value; + } + return isSet; + }); if (queryKeys.length === 0) return ''; return '?' + qsParser.stringify(filtered); @@ -133,11 +137,16 @@ function unifyAuth(auth) { function buildUserAgent(options) { return ( - (options.clientName || 'noServiceName') + '/' + - (options.clientVersion || 'noServiceVersion') + ' (' + - (options.appName || 'noAppName') + '/' + - (options.appSha || 'noAppSha') + '; ' + - (options.fqdn || 'noFQDN') + ')' + (options.clientName || 'noServiceName') + + '/' + + (options.clientVersion || 'noServiceVersion') + + ' (' + + (options.appName || 'noAppName') + + '/' + + (options.appSha || 'noAppSha') + + '; ' + + (options.fqdn || 'noFQDN') + + ')' ); } @@ -145,7 +154,8 @@ function defaultTimeout(value, defaultValue) { if (value >= 0) { if (typeof value !== 'number') { throw new TypeError( - 'Invalid timeout ' + JSON.stringify(value) + ', not a number'); + 'Invalid timeout ' + JSON.stringify(value) + ', not a number' + ); } return value; } @@ -159,14 +169,15 @@ function defaultStatusCode(value, defaultValue) { if (value >= 0) { if (typeof value !== 'number') { throw new TypeError( - 'Invalid status code ' + JSON.stringify(value) + ', not a number'); + 'Invalid status code ' + JSON.stringify(value) + ', not a number' + ); } return value; } return defaultValue; } -function _fetch(urlObj, options) { +function fetchUrlObj(urlObj, options) { if (options.baseUrl && typeof options.baseUrl === 'string') { urlObj = applyBaseUrl(urlObj, options.baseUrl); } @@ -185,9 +196,11 @@ function _fetch(urlObj, options) { } else if (form !== undefined && form !== null) { if (typeof form !== 'object') { throw new TypeError( - 'Invalid form body (' + typeof form + ', expected object)'); + 'Invalid form body (' + typeof form + ', expected object)' + ); } - defaultHeaders['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'; + defaultHeaders['Content-Type'] = + 'application/x-www-form-urlencoded;charset=UTF-8'; body = qsParser.stringify(form); } @@ -229,12 +242,17 @@ function _fetch(urlObj, options) { hostname: hostname, port: urlObj.port, method: method, - path: replacePathParams(urlObj.pathname, options.pathParams) + generateSearch(urlObj.query, options.qs), + path: + replacePathParams(urlObj.pathname, options.pathParams) + + generateSearch(urlObj.query, options.qs), headers: filterHeaders(assign(defaultHeaders, options.headers)), auth: unifyAuth(options.auth || urlObj.auth), localAddress: options.localAddress, body: body, - connectTimeout: defaultTimeout(options.connectTimeout, DEFAULT_CONNECT_TIMEOUT), + connectTimeout: defaultTimeout( + options.connectTimeout, + DEFAULT_CONNECT_TIMEOUT + ), timeout: defaultTimeout(options.timeout, DEFAULT_TIMEOUT), completionTimeout: defaultTimeout(options.completionTimeout, 0), minStatusCode: defaultStatusCode(options.minStatusCode, 200), @@ -259,7 +277,7 @@ function fetch(url, options, callback) { if (typeof url !== 'string') { throw new TypeError('url has to be a string'); } - return nodeify(_fetch(parseUrl(url), options || {}), callback); + return nodeify(fetchUrlObj(parseUrl(url), options || {}), callback); } module.exports = fetch; diff --git a/lib/gofer.js b/lib/gofer.js index 5e64c59..77cedb4 100644 --- a/lib/gofer.js +++ b/lib/gofer.js @@ -29,7 +29,9 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + 'use strict'; + var isObjectLike = require('lodash/isObjectLike'); var isPlainObject = require('lodash/isPlainObject'); var merge = require('lodash/merge'); @@ -45,19 +47,22 @@ function Gofer(config, serviceName, clientVersion, clientName) { var globalDefaults = config.globalDefaults || {}; var serviceDefaults = config[serviceName] || {}; - this.defaults = merge({ - serviceName: serviceName, - clientVersion: clientVersion, - clientName: clientName, - endpointDefaults: {}, - }, globalDefaults, serviceDefaults); + this.defaults = merge( + { + serviceName: serviceName, + clientVersion: clientVersion, + clientName: clientName, + endpointDefaults: {}, + }, + globalDefaults, + serviceDefaults + ); } module.exports = Gofer; Gofer.fetch = fetch; Gofer.default = Gofer; -Gofer.prototype.with = -function withOverrides(overrides) { +Gofer.prototype.with = function withOverrides(overrides) { var cloned = this.clone(); merge(cloned.defaults, overrides); return cloned; @@ -68,16 +73,15 @@ function withOverrides(overrides) { * a different implementation. For example it could pass down * additional dependencies. */ -Gofer.prototype.clone = -function clone() { +Gofer.prototype.clone = function clone() { var Client = this.constructor || Gofer; var config = { globalDefaults: this.defaults }; return new Client(config); }; +// eslint-disable-next-line no-underscore-dangle Gofer.prototype._mappers = []; -Gofer.prototype.addOptionMapper = -function addOptionMapper(mapper) { +Gofer.prototype.addOptionMapper = function addOptionMapper(mapper) { this._mappers = this._mappers.concat(mapper); }; @@ -99,12 +103,17 @@ function preventComplexMerge(objValue, srcValue) { return mergeWith({}, objValue, srcValue, preventComplexMerge); } -Gofer.prototype._prepareOptions = -function _prepareOptions(defaults, options) { +// eslint-disable-next-line no-underscore-dangle +Gofer.prototype._prepareOptions = function _prepareOptions(defaults, options) { var endpointName = options.endpointName || defaults.endpointName; - var mergedOptions = mergeWith({}, this.defaults, defaults, + var mergedOptions = mergeWith( + {}, + this.defaults, + defaults, this.defaults.endpointDefaults[endpointName], - options, preventComplexMerge); + options, + preventComplexMerge + ); return this._mappers.reduce(applyOptionMapper, mergedOptions); }; @@ -115,15 +124,13 @@ function fetchWithDefaults(defaults) { }; } -['get', 'put', 'post', 'patch', 'del', 'head'] -.forEach(function withVerb(verb) { +['get', 'put', 'post', 'patch', 'del', 'head'].forEach(function withVerb(verb) { var httpMethod = verb === 'del' ? 'DELETE' : verb.toUpperCase(); Gofer.prototype[verb] = fetchWithDefaults({ method: httpMethod }); }); Gofer.prototype.fetch = Gofer.prototype.get; -Gofer.prototype.registerEndpoint = -function registerEndpoint(name, endpointFn) { +Gofer.prototype.registerEndpoint = function registerEndpoint(name, endpointFn) { Object.defineProperty(this, name, { configurable: true, get: function _getCachedEndpoint() { @@ -136,8 +143,7 @@ function registerEndpoint(name, endpointFn) { return this; }; -Gofer.prototype.registerEndpoints = -function registerEndpoints(endpointMap) { +Gofer.prototype.registerEndpoints = function registerEndpoints(endpointMap) { Object.keys(endpointMap).forEach(function register(name) { this.registerEndpoint(name, endpointMap[name]); }, this); diff --git a/lib/legacy.js b/lib/legacy.js index 9558cb6..975b37b 100644 --- a/lib/legacy.js +++ b/lib/legacy.js @@ -29,6 +29,7 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + 'use strict'; function hasJsonHeader(res) { diff --git a/lib/request.js b/lib/request.js index 0c796f5..0ed7afa 100644 --- a/lib/request.js +++ b/lib/request.js @@ -29,6 +29,7 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + 'use strict'; var http = require('http'); @@ -47,6 +48,7 @@ function clearImmediateSafe(handle) { // See: https://github.com/nodejs/node/pull/9759 if (!handle) return; clearImmediate(handle); + // eslint-disable-next-line no-underscore-dangle handle._onImmediate = noop; } @@ -84,15 +86,15 @@ function clearIOTimeout(handle) { return handle(); } -function _callJSON(res) { +function callJSON(res) { return res.json(); } -function _callText(res) { +function callText(res) { return res.text(); } -function _callRawBody(res) { +function callRawBody(res) { return res.rawBody(); } @@ -108,19 +110,19 @@ function parseErrorBody(rawBody) { var reqProperties = { json: { value: function json() { - return this.then(_callJSON); + return this.then(callJSON); }, }, text: { value: function text() { - return this.then(_callText); + return this.then(callText); }, }, rawBody: { value: function rawBody() { - return this.then(_callRawBody); + return this.then(callRawBody); }, }, }; @@ -136,15 +138,15 @@ function buildFullUrl(options) { }); } -function request_(options, resolve, reject) { +function requestFunc(options, resolve, reject) { var host = options.host; var setHost = options.setHost; var fullUrl = buildFullUrl(options); options.setHost = false; debug('-> %s %s', options.method, fullUrl); - var req_ = null; - var res_ = null; + var reqObj = null; + var resObj = null; var connectTimer = null; var responseTimer = null; var completionTimer = null; @@ -168,33 +170,38 @@ function request_(options, resolve, reject) { clearImmediateSafe(socketTimer); socketTimer = null; - if (req_ !== null) { - req_.abort(); - req_ = null; + if (reqObj !== null) { + reqObj.abort(); + reqObj = null; } reject(error); } function emitError(error) { - if (res_) { - res_.emit('error', error); - } else if (req_) { - req_.emit('error', error); + if (resObj) { + resObj.emit('error', error); + } else if (reqObj) { + reqObj.emit('error', error); } } function isAcceptableStatus(code) { var min = options.minStatusCode; var max = options.maxStatusCode; - return (min === false || code >= min) && - (max === false || code <= max); + return (min === false || code >= min) && (max === false || code <= max); } function generateStatusCodeError() { var error = StatusCodeError.create( - res_.statusCode, options.minStatusCode, options.maxStatusCode, - res_.headers, options.method, fullUrl); - res_.rawBody() + resObj.statusCode, + options.minStatusCode, + options.maxStatusCode, + resObj.headers, + options.method, + fullUrl + ); + resObj + .rawBody() .then(parseErrorBody) .then(null, noop) .then(function rejectWithBody(body) { @@ -209,20 +216,20 @@ function request_(options, resolve, reject) { timing.headers = Date.now() - startTime; - res_ = Object.defineProperties(res, resProperties); - res_.url = fullUrl; - res_.on('error', failAndAbort); + resObj = Object.defineProperties(res, resProperties); + resObj.url = fullUrl; + resObj.on('error', failAndAbort); if (!isAcceptableStatus(res.statusCode)) { generateStatusCodeError(); } else { debug('<- %s %s', res.statusCode, fullUrl); - resolve(res_); + resolve(resObj); } } function isConnecting() { - return !!(req_ && req_.socket && req_.socket.readable === false); + return !!(reqObj && reqObj.socket && reqObj.socket.readable === false); } function onCompletionTimedOut() { @@ -262,7 +269,7 @@ function request_(options, resolve, reject) { function onSocketTimeout() { socketTimer = setImmediate(function checkRealTimeout() { socketTimer = null; - if (req_ && req_.socket && req_.socket.readable) { + if (reqObj && reqObj.socket && reqObj.socket.readable) { onResponseTimedOut('ESOCKETTIMEDOUT'); } }); @@ -278,7 +285,7 @@ function request_(options, resolve, reject) { } function onRequest(req) { - req_ = req; + reqObj = req; if (options.completionTimeout > 0) { setIOTimeout(onCompletionTimedOut, options.completionTimeout); @@ -312,7 +319,7 @@ function request_(options, resolve, reject) { } function request(options) { - var result = new Bluebird(request_.bind(null, options)); + var result = new Bluebird(requestFunc.bind(null, options)); return Object.defineProperties(result, reqProperties); } module.exports = request; diff --git a/lib/response.js b/lib/response.js index 1e87f8d..826f5bf 100644 --- a/lib/response.js +++ b/lib/response.js @@ -29,14 +29,17 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + 'use strict'; + var zlib = require('zlib'); var Bluebird = require('bluebird'); function getStreamForResponse(res) { - var encoding = - (res.headers['content-encoding'] || 'identity').trim().toLowerCase(); + var encoding = (res.headers['content-encoding'] || 'identity') + .trim() + .toLowerCase(); switch (encoding) { case 'gzip': @@ -100,8 +103,7 @@ module.exports = { text: { value: function text() { - return new Bluebird(readBody.bind(this.stream())) - .then(toString); + return new Bluebird(readBody.bind(this.stream())).then(toString); }, }, diff --git a/lib/url.js b/lib/url.js index e4431bd..1bf359b 100644 --- a/lib/url.js +++ b/lib/url.js @@ -29,7 +29,9 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + 'use strict'; + var url = require('url'); function applyBaseUrl(urlObj, baseUrl) { @@ -53,7 +55,7 @@ function applyBaseUrl(urlObj, baseUrl) { port: base.port, // For the pathname, we join. E.g. http://host/v2 + /my-resource - pathname: (basePath + (urlObj.pathname || '')) || '/', + pathname: basePath + (urlObj.pathname || '') || '/', query: urlObj.query, }; } diff --git a/package.json b/package.json index fdd540a..0939eb5 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "homepage": "https://github.com/groupon/gofer", "repository": { "type": "git", - "url": "git+ssh://git@github.com/groupon/gofer" + "url": "https://github.com/groupon/gofer" }, "bugs": { "url": "https://github.com/groupon/gofer/issues" @@ -38,12 +38,16 @@ }, "devDependencies": { "assertive": "^2.1.0", - "eslint": "^1.0.0", - "eslint-config-groupon": "^2.0.0", + "eslint": "^4.7.1", + "eslint-config-groupon": "^5.0.0", + "eslint-plugin-import": "^2.8.0", + "eslint-plugin-node": "^5.2.1", + "eslint-plugin-prettier": "^2.2.0", "form-data": "^1.0.0-rc4", - "mocha": "^2.0.0", + "mocha": "^3.1.2", "mochify": "^2.17.0", "nlm": "^3.0.0", + "prettier": "^1.6.1", "promise": "^7.1.1", "self-signed": "^1.3.1", "whatwg-fetch": "^0.11.0" diff --git a/test/.eslintrc b/test/.eslintrc index 419b38a..7eeefc3 100644 --- a/test/.eslintrc +++ b/test/.eslintrc @@ -1,8 +1,5 @@ { "env": { "mocha": true - }, - "rules": { - "func-names": 0 } } diff --git a/test/browser.test.js b/test/browser.test.js index 3d1d059..09db161 100644 --- a/test/browser.test.js +++ b/test/browser.test.js @@ -1,4 +1,5 @@ 'use strict'; + /** * This re-runs the whole test suite inside of phantomjs. * That's great for CI but tends to be hard to debug. @@ -22,16 +23,21 @@ var execFile = module.require('' + 'child_process').execFile; require('./mock-service'); -describe('in a browser', function () { - it('works (almost) just the same', function (done) { +describe('in a browser', function() { + it('works (almost) just the same', function(done) { var mochifyBin = require.resolve('.bin/mochify'); this.timeout(60 * 1000); - var child = execFile(mochifyBin, [ - '--reporter', 'spec', - './node_modules/promise/polyfill', - './node_modules/whatwg-fetch', - './test/**/*.test.js', - ], done); + var child = execFile( + mochifyBin, + [ + '--reporter', + 'spec', + './node_modules/promise/polyfill', + './node_modules/whatwg-fetch', + './test/**/*.test.js', + ], + done + ); child.stdout.pipe(process.stdout); child.stderr.pipe(process.stderr); }); diff --git a/test/config.test.js b/test/config.test.js index edc26bf..45c89e0 100644 --- a/test/config.test.js +++ b/test/config.test.js @@ -1,4 +1,7 @@ +/* eslint-disable no-underscore-dangle */ + 'use strict'; + var assert = require('assertive'); var Gofer = require('../'); @@ -23,14 +26,16 @@ function GoferB(config) { } GoferB.prototype = Object.create(Gofer.prototype); -describe('config handling', function () { +describe('config handling', function() { var a; var b; - before(function () { + before(function() { GoferB.prototype.registerEndpoints({ - x: function (fetch) { - return function (cb) { return fetch('/something', cb); }; + x: function(fetch) { + return function(cb) { + return fetch('/something', cb); + }; }, }); @@ -38,20 +43,38 @@ describe('config handling', function () { b = new GoferB(CONFIG); }); - it('applies service-level config', function () { - assert.equal('applies service-level default', - 1001, a._prepareOptions({}, {}).timeout); - assert.equal('falls back to global default', - 55, a._prepareOptions({}, {}).connectTimeout); - - assert.equal('does not apply endpoint default if endpointName is not provided', - 70, b._prepareOptions({}, {}).connectTimeout); - assert.equal('does not apply endpoint default for other endpoints', - 70, b._prepareOptions({ endpointName: 'y' }, {}).connectTimeout); - - assert.equal('applies endpoint-level defaults', - 23, b._prepareOptions({ endpointName: 'x' }, {}).timeout); - assert.equal('falls back to service-level defaults', - 70, b._prepareOptions({ endpointName: 'x' }, {}).connectTimeout); + it('applies service-level config', function() { + assert.equal( + 'applies service-level default', + 1001, + a._prepareOptions({}, {}).timeout + ); + assert.equal( + 'falls back to global default', + 55, + a._prepareOptions({}, {}).connectTimeout + ); + + assert.equal( + 'does not apply endpoint default if endpointName is not provided', + 70, + b._prepareOptions({}, {}).connectTimeout + ); + assert.equal( + 'does not apply endpoint default for other endpoints', + 70, + b._prepareOptions({ endpointName: 'y' }, {}).connectTimeout + ); + + assert.equal( + 'applies endpoint-level defaults', + 23, + b._prepareOptions({ endpointName: 'x' }, {}).timeout + ); + assert.equal( + 'falls back to service-level defaults', + 70, + b._prepareOptions({ endpointName: 'x' }, {}).connectTimeout + ); }); }); diff --git a/test/fetch-http.test.js b/test/fetch-http.test.js index 979b1b2..f4bdfef 100644 --- a/test/fetch-http.test.js +++ b/test/fetch-http.test.js @@ -1,4 +1,5 @@ 'use strict'; + var assert = require('assertive'); var Bluebird = require('bluebird'); @@ -10,183 +11,212 @@ var fetch = require('../').fetch; var options = require('./mock-service'); -describe('fetch: the basics', function () { - it('can load using just a url string', function () { - return fetch(options.baseUrl) - .then(function (res) { - assert.equal(200, res.statusCode); - }); +describe('fetch: the basics', function() { + it('can load using just a url string', function() { + return fetch(options.baseUrl).then(function(res) { + assert.equal(200, res.statusCode); + }); }); - it('exposes the url on the response', function () { - return fetch(options.baseUrl + '/echo/foo/bar', { qs: { x: 42 } }) - .then(function (res) { + it('exposes the url on the response', function() { + return fetch(options.baseUrl + '/echo/foo/bar', { qs: { x: 42 } }).then( + function(res) { assert.equal(options.baseUrl + '/echo/foo/bar?x=42', res.url); - }); + } + ); }); - it('can load using path and baseUrl option', function () { - return fetch('/text/path', { baseUrl: options.baseUrl }) - .then(function (res) { - assert.equal(200, res.statusCode); - }); + it('can load using path and baseUrl option', function() { + return fetch('/text/path', { baseUrl: options.baseUrl }).then(function( + res + ) { + assert.equal(200, res.statusCode); + }); }); - it('has a convenience .json method', function () { + it('has a convenience .json method', function() { return fetch('/echo', options) - .json().then(function (echo) { + .json() + .then(function(echo) { assert.equal('GET', echo.method); assert.equal('/echo', echo.url); }); }); - it('exposes the response body on status code error object', function () { - return assert.rejects(fetch('/json/404', options).json()) - .then(function (error) { + it('exposes the response body on status code error object', function() { + return assert + .rejects(fetch('/json/404', options).json()) + .then(function(error) { assert.truthy(error.body); // The response body constains a request mirror just like /echo assert.equal('GET', error.body.method); }); }); - it('exposes the full URL and HTTP method on status code error object', function () { - return assert.rejects(fetch('/json/404', { - baseUrl: options.baseUrl, - method: 'PUT', - qs: { x: 42 }, - }).json()) - .then(function (error) { + it('exposes the full URL and HTTP method on status code error object', function() { + return assert + .rejects( + fetch('/json/404', { + baseUrl: options.baseUrl, + method: 'PUT', + qs: { x: 42 }, + }).json() + ) + .then(function(error) { assert.equal(options.baseUrl + '/json/404?x=42', error.url); assert.equal('PUT', error.method); }); }); - it('can add query string arguments', function () { + it('can add query string arguments', function() { return fetch('/echo?y=url&z=bar', { baseUrl: options.baseUrl, qs: { x: [1, 'foo'], y: 'qs' }, - }).json() - .then(function (echo) { - assert.equal('/echo?y=qs&z=bar&x[0]=1&x[1]=foo', - decodeURIComponent(echo.url)); + }) + .json() + .then(function(echo) { + assert.equal( + '/echo?y=qs&z=bar&x[0]=1&x[1]=foo', + decodeURIComponent(echo.url) + ); }); }); - it('can replace path params', function () { + it('can replace path params', function() { return fetch('/{foo}/other/{foo}/{bar}', { baseUrl: options.baseUrl + '/echo/{foo}', pathParams: { foo: 'abc', bar: 'xyz' }, - }).json() - .then(function (echo) { + }) + .json() + .then(function(echo) { assert.equal('/echo/abc/abc/other/abc/xyz', echo.url); }); }); - it('fails when a {pathParam} is not provided', function () { - var error = assert.throws(function () { + it('fails when a {pathParam} is not provided', function() { + var error = assert.throws(function() { fetch('/{foo}', options); }); assert.equal('Missing value for path param foo', error.message); }); - it('fails when a {pathParam} in the baseUrl is not provided', function () { + it('fails when a {pathParam} in the baseUrl is not provided', function() { // The baseUrl will be parsed which turns '{' into '%7B' etc. - var error = assert.throws(function () { + var error = assert.throws(function() { fetch('/', { baseUrl: options.baseUrl + '/{foo}' }); }); assert.equal('Missing value for path param foo', error.message); }); - it('does not fail when a pathParam is not used', function () { + it('does not fail when a pathParam is not used', function() { return fetch('/echo', { baseUrl: options.baseUrl, pathParams: { foo: 'abc', bar: 'xyz' }, - }).json() - .then(function (echo) { + }) + .json() + .then(function(echo) { assert.equal('/echo', echo.url); }); }); - it('throws when the url is not a string', function () { + it('throws when the url is not a string', function() { assert.equal( 'url has to be a string', - assert.throws(function () { fetch(); }).message); + assert.throws(function() { + fetch(); + }).message + ); assert.equal( 'url has to be a string', - assert.throws(function () { fetch(true); }).message); + assert.throws(function() { + fetch(true); + }).message + ); assert.equal( 'url has to be a string', - assert.throws(function () { fetch(null); }).message); + assert.throws(function() { + fetch(null); + }).message + ); }); - it('throws when the baseUrl contains a query string', function () { - var error = assert.throws(function () { + it('throws when the baseUrl contains a query string', function() { + var error = assert.throws(function() { fetch('/text/path', { baseUrl: options.baseUrl + '?x=1' }); }); assert.equal('baseUrl may not contain a query string', error.message); }); - it('exposes a promise to a response body stream', function () { + it('exposes a promise to a response body stream', function() { if (typeof document !== 'undefined') { // Streams in the browser and streams in node are two different things. return this.skip(); } function concat(stream) { - return new Bluebird(function (resolve, reject) { + return new Bluebird(function(resolve, reject) { stream.on('error', reject); var chunks = []; - stream.on('data', function (chunk) { chunks.push(chunk); }); - stream.on('end', function () { resolve(Buffer.concat(chunks)); }); + stream.on('data', function(chunk) { + chunks.push(chunk); + }); + stream.on('end', function() { + resolve(Buffer.concat(chunks)); + }); }); } return fetch('/test/path', options) - .then(function (res) { return res.stream(); }) + .then(function(res) { + return res.stream(); + }) .then(concat) - .then(function (body) { + .then(function(body) { assert.equal('ok', '' + body); }); }); - it('allows controlling the http method', function () { + it('allows controlling the http method', function() { return fetch('/echo', { baseUrl: options.baseUrl, method: 'POST' }) .json() - .then(function (echo) { + .then(function(echo) { assert.equal('POST', echo.method); }); }); - describe('host header', function () { - it('sends a valid host header', function () { - return fetch(options.baseUrl + '/echo').json() - .then(function (echo) { + describe('host header', function() { + it('sends a valid host header', function() { + return fetch(options.baseUrl + '/echo') + .json() + .then(function(echo) { assert.equal('localhost:3066', echo.headers.host); }); }); - it('sends a valid host header with baseUrl', function () { - return fetch('/echo', { baseUrl: options.baseUrl }).json() - .then(function (echo) { + it('sends a valid host header with baseUrl', function() { + return fetch('/echo', { baseUrl: options.baseUrl }) + .json() + .then(function(echo) { assert.equal('localhost:3066', echo.headers.host); }); }); }); - it('allows passing headers', function () { + it('allows passing headers', function() { return fetch('/echo', { baseUrl: options.baseUrl, method: 'POST', headers: { 'Content-Type': 'text/x-pizza' }, body: '🍕🍕🍕', - }).json() - .then(function (echo) { + }) + .json() + .then(function(echo) { assert.equal('text/x-pizza', echo.headers['content-type']); }); }); - it('allows passing in nested query string params', function () { + it('allows passing in nested query string params', function() { var withQuery = { baseUrl: options.baseUrl, qs: { @@ -196,39 +226,39 @@ describe('fetch: the basics', function () { }; return fetch('/echo', withQuery) .json() - .then(function (echo) { + .then(function(echo) { assert.equal( encodeURI('/echo?nested[key]=value&arr[0][x]=3&arr[1][x]=4'), - echo.url); + echo.url + ); }); }); - it('sets basic auth header from string', function () { + it('sets basic auth header from string', function() { return fetch('/echo', { baseUrl: options.baseUrl, auth: 'user:p4ssword' }) .json() - .then(function (echo) { + .then(function(echo) { assert.equal('Basic dXNlcjpwNHNzd29yZA==', echo.headers.authorization); }); }); - it('sets basic auth header from object', function () { + it('sets basic auth header from object', function() { var authObject = { username: 'user', password: 'p4ssword', }; return fetch('/echo', { baseUrl: options.baseUrl, auth: authObject }) .json() - .then(function (echo) { + .then(function(echo) { assert.equal('Basic dXNlcjpwNHNzd29yZA==', echo.headers.authorization); }); }); - it('returns response headers', function () { + it('returns response headers', function() { // this is a silly test in node but is relevant to browser usage - return fetch('/test/path', options) - .then(function (res) { - // Testing content-language b/c it's a "simple response header" - assert.equal('has%20stuff', res.headers['content-language']); - }); + return fetch('/test/path', options).then(function(res) { + // Testing content-language b/c it's a "simple response header" + assert.equal('has%20stuff', res.headers['content-language']); + }); }); }); diff --git a/test/fetch-https.test.js b/test/fetch-https.test.js index cfc190b..b151751 100644 --- a/test/fetch-https.test.js +++ b/test/fetch-https.test.js @@ -1,4 +1,5 @@ 'use strict'; + var assert = require('assertive'); var Bluebird = require('bluebird'); @@ -10,54 +11,50 @@ var fetch = require('../').fetch; var options = require('./mock-service'); -describe('fetch: https', function () { - it('can load from valid https remote', function () { +describe('fetch: https', function() { + it('can load from valid https remote', function() { // This is a remote call which isn't great but it means we get a valid // https certificate without having to pull any tricks. this.timeout(2000); - return fetch('https://api.reddit.com/user/ageitgey/about.json') - .json(); + return fetch('https://api.reddit.com/user/ageitgey/about.json').json(); }); - it('fails with self-signed https', function () { - return assert.rejects(fetch(options.baseUrlTls)) - .then(function (error) { - // In browsers we don't get any nice, reliable errors (yet?) - if (typeof document === 'undefined') { - if (error.code) { - // more recent node versions (e.g. 4+) - assert.match(/SELF_SIGNED/, error.code); - } else { - // old node versions (e.g. 0.10) - assert.match(/SELF_SIGNED/, error.message); - } + it('fails with self-signed https', function() { + return assert.rejects(fetch(options.baseUrlTls)).then(function(error) { + // In browsers we don't get any nice, reliable errors (yet?) + if (typeof document === 'undefined') { + if (error.code) { + // more recent node versions (e.g. 4+) + assert.match(/SELF_SIGNED/, error.code); + } else { + // old node versions (e.g. 0.10) + assert.match(/SELF_SIGNED/, error.message); } - }); + } + }); }); - it('supports rejectUnauthorized=false', function () { + it('supports rejectUnauthorized=false', function() { if (typeof document !== 'undefined') { // Browsers don't allow to side-step https return this.skip(); } return fetch(options.baseUrlTls, { rejectUnauthorized: false, - }) - .then(function (res) { - assert.equal(200, res.statusCode); - }); + }).then(function(res) { + assert.equal(200, res.statusCode); + }); }); - it('can load from self-signed https remote', function () { + it('can load from self-signed https remote', function() { if (typeof document !== 'undefined') { // Browsers don't allow to side-step https return this.skip(); } return fetch(options.baseUrlTls, { ca: [options.certOptions.cert], - }) - .then(function (res) { - assert.equal(200, res.statusCode); - }); + }).then(function(res) { + assert.equal(200, res.statusCode); + }); }); }); diff --git a/test/fetch-search-domain.test.js b/test/fetch-search-domain.test.js index 6753dd7..21273c4 100644 --- a/test/fetch-search-domain.test.js +++ b/test/fetch-search-domain.test.js @@ -1,56 +1,67 @@ 'use strict'; + var assert = require('assertive'); var fetch = require('../').fetch; -describe('fetch: searchDomain', function () { +describe('fetch: searchDomain', function() { if (typeof document !== 'undefined') { // This is not really a feature relevant for client-side code. it('is not implemented'); return; } - it('appends the searchDomain to non-fqdns in uri', function () { + it('appends the searchDomain to non-fqdns in uri', function() { var options = { searchDomain: 'bar123' }; - return assert.rejects(fetch('http://some.invalid.thing/a/path', options)) - .then(function (error) { + return assert + .rejects(fetch('http://some.invalid.thing/a/path', options)) + .then(function(error) { assert.equal('ENOTFOUND', error.code); - if ('hostname' in error) { // node 4.x+ + if ('hostname' in error) { + // node 4.x+ assert.equal('some.invalid.thing.bar123.', error.hostname); } }); }); - it('appends the searchDomain to non-fqdns in baseUrl', function () { - var options = { baseUrl: 'http://some.invalid.thing/a', searchDomain: 'bar123' }; - return assert.rejects(fetch('/path', options)) - .then(function (error) { - assert.equal('ENOTFOUND', error.code); - if ('hostname' in error) { // node 4.x+ - assert.equal('some.invalid.thing.bar123.', error.hostname); - } - }); + it('appends the searchDomain to non-fqdns in baseUrl', function() { + var options = { + baseUrl: 'http://some.invalid.thing/a', + searchDomain: 'bar123', + }; + return assert.rejects(fetch('/path', options)).then(function(error) { + assert.equal('ENOTFOUND', error.code); + if ('hostname' in error) { + // node 4.x+ + assert.equal('some.invalid.thing.bar123.', error.hostname); + } + }); }); - it('never appends the searchDomain to fqdns in uri', function () { + it('never appends the searchDomain to fqdns in uri', function() { var options = { searchDomain: 'bar123' }; - return assert.rejects(fetch('http://some.invalid.thing./a/path', options)) - .then(function (error) { + return assert + .rejects(fetch('http://some.invalid.thing./a/path', options)) + .then(function(error) { assert.equal('ENOTFOUND', error.code); - if ('hostname' in error) { // node 4.x+ + if ('hostname' in error) { + // node 4.x+ assert.equal('some.invalid.thing.', error.hostname); } }); }); - it('never appends the searchDomain to fqdns in baseUrl', function () { - var options = { baseUrl: 'http://some.invalid.thing./a', searchDomain: 'bar123' }; - return assert.rejects(fetch('/path', options)) - .then(function (error) { - assert.equal('ENOTFOUND', error.code); - if ('hostname' in error) { // node 4.x+ - assert.equal('some.invalid.thing.', error.hostname); - } - }); + it('never appends the searchDomain to fqdns in baseUrl', function() { + var options = { + baseUrl: 'http://some.invalid.thing./a', + searchDomain: 'bar123', + }; + return assert.rejects(fetch('/path', options)).then(function(error) { + assert.equal('ENOTFOUND', error.code); + if ('hostname' in error) { + // node 4.x+ + assert.equal('some.invalid.thing.', error.hostname); + } + }); }); }); diff --git a/test/fetch-timeout.test.js b/test/fetch-timeout.test.js index fd5b6a9..88f3085 100644 --- a/test/fetch-timeout.test.js +++ b/test/fetch-timeout.test.js @@ -1,4 +1,5 @@ 'use strict'; + var assert = require('assertive'); var fetch = require('../').fetch; @@ -14,16 +15,15 @@ function fetchWithLatency(latency, hang, timeout) { }); } -describe('fetch: timeouts', function () { - it('succeeds if timeout is not exceeded', function () { +describe('fetch: timeouts', function() { + it('succeeds if timeout is not exceeded', function() { this.timeout(500); - return fetchWithLatency(100, 100, 300) - .then(function (res) { - assert.equal(200, res.statusCode); - }); + return fetchWithLatency(100, 100, 300).then(function(res) { + assert.equal(200, res.statusCode); + }); }); - it('response- & read-timeout are independent', function () { + it('response- & read-timeout are independent', function() { if (typeof document !== 'undefined') { // This isn't reliable in browser because we can't rely // that the response will be exposed in time. @@ -31,49 +31,51 @@ describe('fetch: timeouts', function () { } this.timeout(500); // total latency is 400 but each indendently is <300 - return fetchWithLatency(200, 200, 300) - .then(function (res) { - assert.equal(200, res.statusCode); - }); + return fetchWithLatency(200, 200, 300).then(function(res) { + assert.equal(200, res.statusCode); + }); }); - it('will time out if response takes too long', function () { + it('will time out if response takes too long', function() { this.timeout(300); - return assert.rejects(fetchWithLatency(200, 0, 100)) - .then(function (error) { - // We set both the socket timeout & the response timeout to the same number. - // Since the socket isn't active while waiting for the response headers, - // both timers fire at the same time. - assert.expect(error.code === 'ETIMEDOUT' || error.code === 'ESOCKETTIMEDOUT'); - }); + return assert.rejects(fetchWithLatency(200, 0, 100)).then(function(error) { + // We set both the socket timeout & the response timeout to the same number. + // Since the socket isn't active while waiting for the response headers, + // both timers fire at the same time. + assert.expect( + error.code === 'ETIMEDOUT' || error.code === 'ESOCKETTIMEDOUT' + ); + }); }); - it('will time out if body takes too long', function () { + it('will time out if body takes too long', function() { if (typeof document !== 'undefined') { // This isn't reliable in browser because we can't rely // that the response will be exposed in time. this.skip(); } this.timeout(150); - return assert.rejects(fetchWithLatency(0, 300, 100).text()) - .then(function (error) { + return assert + .rejects(fetchWithLatency(0, 300, 100).text()) + .then(function(error) { assert.equal('ESOCKETTIMEDOUT', error.code); }); }); - it('connection timed out', function () { + it('connection timed out', function() { if (typeof document !== 'undefined') { // This isn't reliable in browser because there is no connection timeout. this.skip(); } this.timeout(200); - return assert.rejects(fetch('http://10.255.255.1', { connectTimeout: 100 })) - .then(function (error) { + return assert + .rejects(fetch('http://10.255.255.1', { connectTimeout: 100 })) + .then(function(error) { assert.equal('ECONNECTTIMEDOUT', error.code); }); }); - describe('timeout in the presence of blocking event loop', function () { + describe('timeout in the presence of blocking event loop', function() { if (typeof document !== 'undefined') { // There's no fork in the browser. return; @@ -82,12 +84,16 @@ describe('fetch: timeouts', function () { before(remoteServer.fork); after(remoteServer.kill); - it('gives it a last chance', function (done) { + it('gives it a last chance', function(done) { this.timeout(500); - fetch('http://127.0.0.1:' + remoteServer.port, { - timeout: 100, - }, done); + fetch( + 'http://127.0.0.1:' + remoteServer.port, + { + timeout: 100, + }, + done + ); function blockEventLoop() { var endTime = Date.now() + 150; @@ -100,13 +106,13 @@ describe('fetch: timeouts', function () { }); }); - describe('completionTimeout', function () { + describe('completionTimeout', function() { if (typeof document !== 'undefined') { // We don't have enough visibility in a browser to support this. return; } - it('does not pass an error when timeout is not exceeded', function () { + it('does not pass an error when timeout is not exceeded', function() { return fetch('/', { baseUrl: options.baseUrl, qs: { __delay: 20 }, @@ -114,29 +120,37 @@ describe('fetch: timeouts', function () { }).text(); }); - it('passes an error when timeout is exceeded', function () { - return assert.rejects(fetch('/', { - baseUrl: options.baseUrl, - qs: { __delay: 50 }, - completionTimeout: 20, - }).text()).then(function (err) { - assert.equal('ETIMEDOUT', err.code); - assert.expect(err.completion); - }); + it('passes an error when timeout is exceeded', function() { + return assert + .rejects( + fetch('/', { + baseUrl: options.baseUrl, + qs: { __delay: 50 }, + completionTimeout: 20, + }).text() + ) + .then(function(err) { + assert.equal('ETIMEDOUT', err.code); + assert.expect(err.completion); + }); }); - it('is triggered by a constant trickle of packages', function () { + it('is triggered by a constant trickle of packages', function() { this.timeout(400); - return assert.rejects(fetch('/', { - baseUrl: options.baseUrl, - qs: { __chunkDelay: 50, __totalDelay: 1000 }, - timeout: 100, // ensure we would not hit the "normal" timeout - completionTimeout: 200, - }).text()).then(function (err) { - assert.equal('ETIMEDOUT', err.code); - assert.expect(err.completion); - }); + return assert + .rejects( + fetch('/', { + baseUrl: options.baseUrl, + qs: { __chunkDelay: 50, __totalDelay: 1000 }, + timeout: 100, // ensure we would not hit the "normal" timeout + completionTimeout: 200, + }).text() + ) + .then(function(err) { + assert.equal('ETIMEDOUT', err.code); + assert.expect(err.completion); + }); }); }); }); diff --git a/test/gofer.test.js b/test/gofer.test.js index d7531ab..443d5e5 100644 --- a/test/gofer.test.js +++ b/test/gofer.test.js @@ -1,67 +1,80 @@ +/* eslint-env browser */ + 'use strict'; + var assert = require('assertive'); var Gofer = require('../'); var options = require('./mock-service'); -describe('gofer', function () { - it('exports a `fetch` function', function () { +describe('gofer', function() { + it('exports a `fetch` function', function() { assert.hasType(Function, Gofer.fetch); }); - it('exposes Gofer as exports.default', function () { + it('exposes Gofer as exports.default', function() { assert.equal(Gofer, Gofer.default); }); - describe('direct usage', function () { + describe('direct usage', function() { var gofer; - before('create Gofer instance', function () { + before('create Gofer instance', function() { gofer = new Gofer().with(options); }); - it('can fetch something', function () { + it('can fetch something', function() { return gofer.get('/echo'); }); }); - describe('call that sets a header', function () { + describe('call that sets a header', function() { var gofer; - before(function () { - gofer = new Gofer().with({ - headers: { 'x-a': 'foo' }, - }).with(options); + before(function() { + gofer = new Gofer() + .with({ + headers: { 'x-a': 'foo' }, + }) + .with(options); - return gofer.fetch('/echo', { - headers: { 'x-b': 'should not leak' }, - }).json(); + return gofer + .fetch('/echo', { + headers: { 'x-b': 'should not leak' }, + }) + .json(); }); - it('does not affect defaults', function () { - return gofer.fetch('/echo').json() - .then(function (echo) { + it('does not affect defaults', function() { + return gofer + .fetch('/echo') + .json() + .then(function(echo) { assert.equal('foo', echo.headers['x-a']); assert.equal(undefined, echo.headers['x-b']); }); }); }); - describe('sub-class', function () { + describe('sub-class', function() { function SubGofer(config) { Gofer.call(this, config, 'sub', '1.2.3', 'my-sub-client'); } SubGofer.prototype = Object.create(Gofer.prototype); var sub; - before('create SubGofer instance', function () { + before('create SubGofer instance', function() { sub = new SubGofer({ sub: options }); }); - it('can fetch something', function () { - return sub.get('/echo').json() - .then(function (echo) { - var expectedUserAgent = typeof navigator !== 'undefined' ? - navigator.userAgent : 'my-sub-client/1.2.3'; + it('can fetch something', function() { + return sub + .get('/echo') + .json() + .then(function(echo) { + var expectedUserAgent = + typeof navigator !== 'undefined' + ? navigator.userAgent + : 'my-sub-client/1.2.3'; assert.include(expectedUserAgent, echo.headers['user-agent']); }); }); diff --git a/test/instrument.browser.js b/test/instrument.browser.js index 7608d84..fb3d9fd 100644 --- a/test/instrument.browser.js +++ b/test/instrument.browser.js @@ -1,4 +1,7 @@ +/* eslint-env browser */ + 'use strict'; + var assign = require('lodash/assign'); var original = typeof window !== 'undefined' ? window.fetch : null; diff --git a/test/instrument.js b/test/instrument.js index 089bf7e..f5fe086 100644 --- a/test/instrument.js +++ b/test/instrument.js @@ -1,4 +1,5 @@ 'use strict'; + var http = require('http'); var assign = require('lodash/assign'); diff --git a/test/instrumentation.test.js b/test/instrumentation.test.js index 2591233..96d1386 100644 --- a/test/instrumentation.test.js +++ b/test/instrumentation.test.js @@ -1,4 +1,5 @@ 'use strict'; + var assert = require('assertive'); var assign = require('lodash/assign'); @@ -21,17 +22,17 @@ function ensureEmpty() { assert.equal(null, instrument.pathParams); } -describe('Verify instrumentation support', function () { +describe('Verify instrumentation support', function() { var client = new Gofer({ instrumented: options }, 'instrumented'); client.registerEndpoints({ - echo: function (fetch) { - return function () { + echo: function(fetch) { + return function() { return fetch('/{x}', { method: 'PUT', pathParams: { x: 'echo' } }); }; }, - named: function (fetch) { - return function () { + named: function(fetch) { + return function() { return fetch('/echo', { method: 'PUT', methodName: 'echo' }); }; }, @@ -40,14 +41,14 @@ describe('Verify instrumentation support', function () { before('add instrumentation', instrument); after('remove instrumentation', instrument.reset); - describe('direct request', function () { + describe('direct request', function() { before(ensureEmpty); - before('make a request', function () { + before('make a request', function() { return client.fetch('/echo'); }); - it('sets the meta data', function () { + it('sets the meta data', function() { assert.equal('instrumented', instrument.serviceName); assert.equal(undefined, instrument.endpointName); assert.equal('get', instrument.methodName); @@ -55,14 +56,14 @@ describe('Verify instrumentation support', function () { }); }); - describe('call endpoint', function () { + describe('call endpoint', function() { before(ensureEmpty); - before('make a request', function () { + before('make a request', function() { return client.echo(); }); - it('sets the meta data', function () { + it('sets the meta data', function() { assert.equal('instrumented', instrument.serviceName); assert.equal('echo', instrument.endpointName); assert.equal('put', instrument.methodName); @@ -70,14 +71,14 @@ describe('Verify instrumentation support', function () { }); }); - describe('with explicit methodName', function () { + describe('with explicit methodName', function() { before(ensureEmpty); - before('make a request', function () { + before('make a request', function() { return client.named(); }); - it('sets the meta data', function () { + it('sets the meta data', function() { assert.equal('instrumented', instrument.serviceName); assert.equal('named', instrument.endpointName); assert.equal('echo', instrument.methodName); diff --git a/test/legacy.test.js b/test/legacy.test.js index 5ecea42..7a53aa6 100644 --- a/test/legacy.test.js +++ b/test/legacy.test.js @@ -1,25 +1,27 @@ 'use strict'; + var assert = require('assertive'); var Gofer = require('../'); + var fetch = Gofer.fetch; var options = require('./mock-service'); -describe('legacy / callback mode', function () { +describe('legacy / callback mode', function() { if (typeof document === 'object') { it.skip('(callback interface not supported in browser builds)'); return; } - describe('using Gofer.fetch', function () { + describe('using Gofer.fetch', function() { var returnValue; var error; var data; var response; - before(function (done) { - returnValue = fetch('/echo', options, function (_error, _data, _response) { + before(function(done) { + returnValue = fetch('/echo', options, function(_error, _data, _response) { error = _error; data = _data; response = _response; @@ -27,22 +29,22 @@ describe('legacy / callback mode', function () { }); }); - it('returns undefined', function () { + it('returns undefined', function() { assert.equal(undefined, returnValue); }); - it('returns the parsed body as data', function () { + it('returns the parsed body as data', function() { assert.truthy(data); assert.equal('GET', data.method); }); - it('includes the response object', function () { + it('includes the response object', function() { assert.truthy(response); assert.equal(200, response.statusCode); }); - it('includes the parsed body on 404s', function (done) { - fetch('/json/404', options, function (notFoundError, body) { + it('includes the parsed body on 404s', function(done) { + fetch('/json/404', options, function(notFoundError, body) { assert.truthy(notFoundError); assert.truthy(body); assert.equal('/json/404', body.url); @@ -52,7 +54,7 @@ describe('legacy / callback mode', function () { }); }); - describe('registerEndpoints', function () { + describe('registerEndpoints', function() { function EchoClient(config) { Gofer.call(this, config, 'echo'); } @@ -61,8 +63,8 @@ describe('legacy / callback mode', function () { }); EchoClient.prototype.registerEndpoints({ - echo: function (withDefaults) { - return function (qs, callback) { + echo: function(withDefaults) { + return function(qs, callback) { return withDefaults('/echo', { qs: qs }, callback); }; }, @@ -72,18 +74,16 @@ describe('legacy / callback mode', function () { echo: { baseUrl: options.baseUrl }, }); - it('can be used as a promise', function () { - return client.echo({ a: 42 }) - .then(function (response) { - assert.equal(200, response.statusCode); - }); + it('can be used as a promise', function() { + return client.echo({ a: 42 }).then(function(response) { + assert.equal(200, response.statusCode); + }); }); - it('turns into errback-style when callback is provided', function (done) { - var result = client.echo({ a: 42 }, function (error, data, response) { + it('turns into errback-style when callback is provided', function(done) { + var result = client.echo({ a: 42 }, function(error, data, response) { if (error) return done(error); - assert.equal('Does *not* return a promise', - undefined, result); + assert.equal('Does *not* return a promise', undefined, result); assert.equal(200, response.statusCode); assert.equal('GET', data.method); done(); @@ -91,14 +91,14 @@ describe('legacy / callback mode', function () { }); }); - describe('using new Gofer().*', function () { + describe('using new Gofer().*', function() { var gofer; - before('create Gofer instance', function () { + before('create Gofer instance', function() { gofer = new Gofer().with(options); }); - it('exposes the legacy mode interface', function (done) { - gofer.get('/echo', {}, function (error, body) { + it('exposes the legacy mode interface', function(done) { + gofer.get('/echo', {}, function(error, body) { assert.truthy(body); done(error); }); diff --git a/test/max-sockets.test.js b/test/max-sockets.test.js index 117bdb3..6d85272 100644 --- a/test/max-sockets.test.js +++ b/test/max-sockets.test.js @@ -1,4 +1,5 @@ 'use strict'; + var assert = require('assertive'); var Bluebird = require('bluebird'); @@ -14,62 +15,94 @@ function getUrlPath(item) { return item.url.replace(/^\/echo\/([^?]+)(?:\?.*)?/, '$1'); } -describe('fetch: maxSockets', function () { - it('loads two one after the other', function () { +describe('fetch: maxSockets', function() { + it('loads two one after the other', function() { if (typeof document !== 'undefined') { // Browsers don't allow us to control the parallism return this.skip(); } this.timeout(2000); - var foo = new Gofer({ - foo: { - baseUrl: options.baseUrl, - maxSockets: 2, - qs: { __latency: 100 }, + var foo = new Gofer( + { + foo: { + baseUrl: options.baseUrl, + maxSockets: 2, + qs: { __latency: 100 }, + }, }, - }, 'foo'); - var bar = new Gofer({ - bar: { - baseUrl: options.baseUrl, - maxSockets: 2, - qs: { __latency: 100 }, + 'foo' + ); + var bar = new Gofer( + { + bar: { + baseUrl: options.baseUrl, + maxSockets: 2, + qs: { __latency: 100 }, + }, }, - }, 'bar'); + 'bar' + ); return Bluebird.all([ - foo.fetch('/echo/1').json().then(urlWithDate), - foo.fetch('/echo/2').json().then(urlWithDate), - foo.fetch('/echo/3').json().then(urlWithDate), - foo.fetch('/echo/4').json().then(urlWithDate), - foo.fetch('/echo/5', { baseUrl: 'http://127.0.0.1:3066' }) - .json().then(urlWithDate), - bar.fetch('/echo/bar').json().then(urlWithDate), - ]) - .then(function (results) { - assert.hasType(Array, results); - assert.deepEqual(['1', '2', '3', '4', '5', 'bar'], results.map(getUrlPath)); + foo + .fetch('/echo/1') + .json() + .then(urlWithDate), + foo + .fetch('/echo/2') + .json() + .then(urlWithDate), + foo + .fetch('/echo/3') + .json() + .then(urlWithDate), + foo + .fetch('/echo/4') + .json() + .then(urlWithDate), + foo + .fetch('/echo/5', { baseUrl: 'http://127.0.0.1:3066' }) + .json() + .then(urlWithDate), + bar + .fetch('/echo/bar') + .json() + .then(urlWithDate), + ]).then(function(results) { + assert.hasType(Array, results); + assert.deepEqual( + ['1', '2', '3', '4', '5', 'bar'], + results.map(getUrlPath) + ); - var times = results.map(function (result) { return result.time; }); + var times = results.map(function(result) { + return result.time; + }); - assert.expect( - 'The first two should happen around the same time', - Math.abs(times[0] - times[1]) < 50); + assert.expect( + 'The first two should happen around the same time', + Math.abs(times[0] - times[1]) < 50 + ); - // 2. The next two are the same, just 100+ ms later - assert.expect( - 'The next two are the same...', - Math.abs(times[2] - times[3]) < 50); - assert.expect( - // leaving some space for timing issues - '...just 100+ ms later (' + times[2] + ' - ' + times[0] + ' >= 90)', - times[2] - times[0] > 90); + // 2. The next two are the same, just 100+ ms later + assert.expect( + 'The next two are the same...', + Math.abs(times[2] - times[3]) < 50 + ); + assert.expect( + // leaving some space for timing issues + '...just 100+ ms later (' + times[2] + ' - ' + times[0] + ' >= 90)', + times[2] - times[0] > 90 + ); - assert.expect( - '#5 uses a different hostname, so it should happen with the first 2', - Math.abs(times[4] - times[1]) < 50); + assert.expect( + '#5 uses a different hostname, so it should happen with the first 2', + Math.abs(times[4] - times[1]) < 50 + ); - assert.expect( - '`bar` has a different serviceName, so it should get its own agents', - Math.abs(times[5] - times[1]) < 50); - }); + assert.expect( + '`bar` has a different serviceName, so it should get its own agents', + Math.abs(times[5] - times[1]) < 50 + ); + }); }); }); diff --git a/test/mock-service.browser.js b/test/mock-service.browser.js index daf2aa5..4f229b0 100644 --- a/test/mock-service.browser.js +++ b/test/mock-service.browser.js @@ -1,4 +1,5 @@ 'use strict'; + var MOCK_SERVICE_PORT = 3066; var MOCK_SERVICE_PORT_TLS = 3067; diff --git a/test/mock-service.js b/test/mock-service.js index 0491c87..1a5b1ba 100644 --- a/test/mock-service.js +++ b/test/mock-service.js @@ -1,3 +1,5 @@ +/* eslint-disable no-underscore-dangle */ + 'use strict'; var http = require('http'); @@ -8,23 +10,26 @@ var selfSigned = require('self-signed'); var options = require('./mock-service.browser'); -var MOCK_SERVICE_PORT = +(options.baseUrl.match(/:(\d+)/)[1]); -var MOCK_SERVICE_PORT_TLS = +(options.baseUrlTls.match(/:(\d+)/)[1]); +var MOCK_SERVICE_PORT = +options.baseUrl.match(/:(\d+)/)[1]; +var MOCK_SERVICE_PORT_TLS = +options.baseUrlTls.match(/:(\d+)/)[1]; var server; var serverTls; function generateCertOptions() { - var keypair = selfSigned({ - name: 'localhost', - city: 'Chicago', - state: 'Illinois', - organization: 'Test', - unit: 'Test', - }, { - alt: ['127.0.0.1', 'http://localhost'], - expire: 60 * 60 * 1000, // one hour - }); + var keypair = selfSigned( + { + name: 'localhost', + city: 'Chicago', + state: 'Illinois', + organization: 'Test', + unit: 'Test', + }, + { + alt: ['127.0.0.1', 'http://localhost'], + expire: 60 * 60 * 1000, // one hour + } + ); return { cert: keypair.cert, key: keypair.private, @@ -44,12 +49,14 @@ function sendEcho(req, res) { } function sendBody() { - res.end(JSON.stringify({ - method: req.method, - url: req.url, - headers: req.headers, - body: Buffer.concat(chunks).toString(), - })); + res.end( + JSON.stringify({ + method: req.method, + url: req.url, + headers: req.headers, + body: Buffer.concat(chunks).toString(), + }) + ); } function sendHeader() { @@ -115,8 +122,14 @@ function handleRequest(req, res) { if (req.headers.origin) { res.setHeader('Access-Control-Allow-Origin', req.headers.origin); } - res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, HEAD, OPTIONS, DELETE, PATCH'); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, x-a, x-b'); + res.setHeader( + 'Access-Control-Allow-Methods', + 'GET, POST, PUT, HEAD, OPTIONS, DELETE, PATCH' + ); + res.setHeader( + 'Access-Control-Allow-Headers', + 'Content-Type, Authorization, x-a, x-b' + ); // Preflight requests that return a 404 confuse Chrome if (req.method === 'OPTIONS') return res.end(); @@ -152,7 +165,7 @@ function bootupServers(done) { if (typeof before === 'function') { before(bootupServers); - after(function () { + after(function() { if (server) { try { server.close(); @@ -170,7 +183,7 @@ if (typeof before === 'function') { }); } if (process.mainModule === module) { - bootupServers(function (error) { + bootupServers(function(error) { if (error) throw error; /* eslint no-console:0 */ console.log('Listening on %s', options.baseUrl); diff --git a/test/multi-part-mapper.test.js b/test/multi-part-mapper.test.js index 4af7c28..244473c 100644 --- a/test/multi-part-mapper.test.js +++ b/test/multi-part-mapper.test.js @@ -1,4 +1,5 @@ 'use strict'; + // This is using file system APIs, no need to run it in a browser. if (typeof document !== 'undefined') return; @@ -28,20 +29,20 @@ function appendAll(form, formData) { } } -describe('fetch: multi-part via form-data', function () { +describe('fetch: multi-part via form-data', function() { // This serves as a POC that more "exotic" features can be // added via option mappers without requiring support in Gofer. var client = new Gofer().with(defaultOptions); - client.registerEndpoint('echo', function (fetch) { - return function (options) { + client.registerEndpoint('echo', function(fetch) { + return function(options) { return fetch('/echo', options).json(); }; }); - client.addOptionMapper(function (options) { + client.addOptionMapper(function(options) { var formData = options.formData; if (formData) { delete options.formData; - var form = options.body = new FormData(); + var form = (options.body = new FormData()); appendAll(form, formData); options.headers = options.headers || {}; assign(options.headers, form.getHeaders()); @@ -49,30 +50,32 @@ describe('fetch: multi-part via form-data', function () { return options; }); - it('can send a complex form with file uploads', function () { + it('can send a complex form with file uploads', function() { var file = fs.createReadStream('test/.eslintrc'); - return client.echo({ - formData: { - str: 'I💖🍕', - myFile: file, - }, - method: 'PUT', - }) - .then(function (echo) { + return client + .echo({ + formData: { + str: 'I💖🍕', + myFile: file, + }, + method: 'PUT', + }) + .then(function(echo) { assert.equal('PUT', echo.method); assert.match( /multipart\/form-data; boundary=/, - echo.headers['content-type']); + echo.headers['content-type'] + ); assert.include( 'Content-Disposition: form-data; name="myFile"; filename=".eslintrc"', - echo.body); + echo.body + ); assert.include( - [ - 'Content-Disposition: form-data; name="str"', - '', - 'I💖🍕', - ].join('\r\n'), - echo.body); + ['Content-Disposition: form-data; name="str"', '', 'I💖🍕'].join( + '\r\n' + ), + echo.body + ); }); }); }); diff --git a/test/request-body.test.js b/test/request-body.test.js index 9994f42..74b2cf4 100644 --- a/test/request-body.test.js +++ b/test/request-body.test.js @@ -1,40 +1,43 @@ +/* eslint-disable no-underscore-dangle */ + 'use strict'; + var assert = require('assertive'); var Gofer = require('../'); var defaultOptions = require('./mock-service'); -describe('fetch: sending a body', function () { +describe('fetch: sending a body', function() { var client = new Gofer().with(defaultOptions); - client.registerEndpoint('echo', function (fetch) { - return function (options) { + client.registerEndpoint('echo', function(fetch) { + return function(options) { return fetch('/echo', options).json(); }; }); - it('can send a string', function () { - return client.echo({ body: 'I💖🍕', method: 'PUT' }) - .then(function (echo) { - assert.equal('PUT', echo.method); - assert.equal('9', echo.headers['content-length']); - assert.equal('I💖🍕', echo.body); - }); + it('can send a string', function() { + return client.echo({ body: 'I💖🍕', method: 'PUT' }).then(function(echo) { + assert.equal('PUT', echo.method); + assert.equal('9', echo.headers['content-length']); + assert.equal('I💖🍕', echo.body); + }); }); - it('can send a Buffer', function () { + it('can send a Buffer', function() { if (typeof document !== 'undefined') { return this.skip(); } - return client.echo({ body: new Buffer('I💖🍕'), method: 'PUT' }) - .then(function (echo) { + return client + .echo({ body: new Buffer('I💖🍕'), method: 'PUT' }) + .then(function(echo) { assert.equal('PUT', echo.method); assert.equal('9', echo.headers['content-length']); assert.equal('I💖🍕', echo.body); }); }); - it('can send a node ReadableStream', function () { + it('can send a node ReadableStream', function() { if (typeof document !== 'undefined') { return this.skip(); } @@ -53,16 +56,15 @@ describe('fetch: sending a body', function () { method: 'PUT', body: stream, }; - return client.echo(withStreamBody) - .then(function (echo) { - assert.equal('PUT', echo.method); - // it should chunk the response - assert.equal(undefined, echo.headers['content-length']); - assert.equal('I💖🍕', echo.body); - }); + return client.echo(withStreamBody).then(function(echo) { + assert.equal('PUT', echo.method); + // it should chunk the response + assert.equal(undefined, echo.headers['content-length']); + assert.equal('I💖🍕', echo.body); + }); }); - it('can send a JSON body', function () { + it('can send a JSON body', function() { var withJsonBody = { baseUrl: defaultOptions.baseUrl, method: 'PUT', @@ -71,18 +73,16 @@ describe('fetch: sending a body', function () { arr: [3, 4], }, }; - return client.echo(withJsonBody) - .then(function (echo) { - assert.equal( - 'application/json;charset=UTF-8', - echo.headers['content-type']); - assert.equal( - '{"utf8":"I💖🍕","arr":[3,4]}', - echo.body); - }); + return client.echo(withJsonBody).then(function(echo) { + assert.equal( + 'application/json;charset=UTF-8', + echo.headers['content-type'] + ); + assert.equal('{"utf8":"I💖🍕","arr":[3,4]}', echo.body); + }); }); - it('can send a form body', function () { + it('can send a form body', function() { var withFormBody = { baseUrl: defaultOptions.baseUrl, method: 'PUT', @@ -91,16 +91,22 @@ describe('fetch: sending a body', function () { arr: [3, 4], }, }; - return client.echo(withFormBody) - .then(function (echo) { - assert.equal( - 'application/x-www-form-urlencoded;charset=UTF-8', - echo.headers['content-type']); - assert.equal( - encodeURIComponent('nested[utf8]') + '=' + encodeURIComponent('I💖🍕') + '&' + - encodeURIComponent('arr[0]') + '=3&' + - encodeURIComponent('arr[1]') + '=4', - echo.body); - }); + return client.echo(withFormBody).then(function(echo) { + assert.equal( + 'application/x-www-form-urlencoded;charset=UTF-8', + echo.headers['content-type'] + ); + assert.equal( + encodeURIComponent('nested[utf8]') + + '=' + + encodeURIComponent('I💖🍕') + + '&' + + encodeURIComponent('arr[0]') + + '=3&' + + encodeURIComponent('arr[1]') + + '=4', + echo.body + ); + }); }); });