From 1527f9d6bf3be28e5f74a637a186618d43152791 Mon Sep 17 00:00:00 2001 From: FG Ribreau Date: Sat, 3 Sep 2016 18:41:55 +0200 Subject: [PATCH] feat(api): larger api-surface, just like `request` --- .gitignore | 1 + README.md | 35 +++++++++++------- circle.yml | 10 +++++ index.js | 64 +++++++++++++++++++++++++------- package.json | 21 +++++++++-- strategies/NetworkError.js | 4 +- test/api-surface.test.js | 76 ++++++++++++++++++++++++++++++++++++++ test/promises.test.js | 13 ++----- 8 files changed, 181 insertions(+), 43 deletions(-) create mode 100644 circle.yml create mode 100644 test/api-surface.test.js diff --git a/.gitignore b/.gitignore index c81b8d3..1ed0588 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules npm-debug.log .env +coverage diff --git a/README.md b/README.md index 4031ff5..e3e2e7d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ -# Request-retry -[![Deps]( https://img.shields.io/david/FGRibreau/node-request-retry.svg)](https://david-dm.org/FGRibreau/node-request-retry) [![Build Status]( https://img.shields.io/circleci/project/FGRibreau/node-request-retry.svg)](https://drone.io/github.com/FGRibreau/node-request-retry/latest) [![Downloads](http://img.shields.io/npm/dm/requestretry.svg)](https://www.npmjs.com/package/requestretry) ![extra](https://img.shields.io/badge/actively%20maintained-yes-ff69b4.svg) +
+

request-retry - HTTP(s) request retry on recoverable errors.

+
+ +========================================= + +[![Build Status](https://img.shields.io/circleci/project/FGRibreau/node-request-retry.svg)](https://circleci.com/gh/FGRibreau/node-request-retry/) [![Coverage Status](https://img.shields.io/coveralls/FGRibreau/node-request-retry/master.svg)](https://coveralls.io/github/FGRibreau/node-request-retry?branch=master) [![Deps]( https://img.shields.io/david/FGRibreau/node-request-retry.svg)](https://david-dm.org/FGRibreau/node-request-retry) [![NPM version](https://img.shields.io/npm/v/requestretry.svg)](http://badge.fury.io/js/requestretry) [![Downloads](http://img.shields.io/npm/dm/requestretry.svg)](https://www.npmjs.com/package/requestretry) ![extra](https://img.shields.io/badge/actively%20maintained-yes-ff69b4.svg) ![NPM](https://nodei.co/npm/requestretry.png?downloadRank=true) ![NPM](https://nodei.co/npm-dl/requestretry.png?months=3&height=2) @@ -121,17 +126,21 @@ You can use the `defaults` method to provide default options like so: var request = require('requestretry').defaults({ json: true, retryStrategy: myRetryStrategy }); ``` -## Convenience methods - -As with `request`, several helpers are provided for various HTTP methods: - -* `request.get(url)` - same as `request(options, callback)` or `request(options)`. -* `request.head(url)` - same as `request(options, callback)` or `request(options)`, but it defaults `options.method` to `HEAD`. -* `request.post(url)` - same as `request(options, callback)` or `request(options)`, but it defaults `options.method` to `POST`. -* `request.put(url)` - same as `request(options, callback)` or `request(options)`, but it defaults `options.method` to `PUT`. -* `request.patch(url)` - same as `request(options, callback)` or `request(options)`, but it defaults `options.method` to `PATCH`. -* `request.del(url)` - same as `request(options, callback)` or `request(options)`, but it defaults `options.method` to `DELETE`. -* `request.delete(url)` - same as `request(options, callback)` or `request(options)`, but it defaults `options.method` to `DELETE`. +## API surface + +As with `request`, several helpers are provided for various HTTP methods and usage: + +* `request(options [, callback])`. +* `request(url [, callback])` - same as `request(options [, callback])`. +* `request(url, options [, callback])` - same as `request(options [, callback])`. +* `request.get(url [, callback])` - same as `request(options [, callback])`, defaults `options.method` to `GET`. +* `request.get(url, options [, callback])` - same as `request(options [, callback])`, defaults `options.method` to `GET`. +* `request.head(url)` - same as `request(options [, callback])`, defaults `options.method` to `HEAD`. +* `request.post(url)` - same as `request(options [, callback])`, defaults `options.method` to `POST`. +* `request.put(url)` - same as `request(options [, callback])`, defaults `options.method` to `PUT`. +* `request.patch(url)` - same as `request(options [, callback])`, defaults `options.method` to `PATCH`. +* `request.del(url)` - same as `request(options [, callback])`, defaults `options.method` to `DELETE`. +* `request.delete(url)` - same as `request(options [, callback])`, defaults `options.method` to `DELETE`. ## [Changelog](CHANGELOG.md) diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000..e3c2322 --- /dev/null +++ b/circle.yml @@ -0,0 +1,10 @@ +machine: + node: + version: 6.3 + environment: + COVERALLS_SERVICE_NAME: circleci + +test: + override: + - npm run test + - npm run send-coverage diff --git a/index.js b/index.js index 28ec500..e623b4f 100644 --- a/index.js +++ b/index.js @@ -10,9 +10,8 @@ var extend = require('extend'); var when = require('when'); var request = require('request'); -var _ = require('fg-lodash'); var RetryStrategies = require('./strategies'); - +var _ = require('lodash'); var DEFAULTS = { maxAttempts: 5, // try 5 times @@ -45,7 +44,29 @@ function makePromise(requestInstance, promiseFactoryFn) { return promiseFactoryFn(Resolver.bind(requestInstance)); } -function Request(options, f, retryConfig) { +function Request(url, options, f, retryConfig) { + // ('url') + if(_.isString(url)){ + // ('url', f) + if(_.isFunction(options)){ + f = options; + } + + if(!_.isObject(options)){ + options = {}; + } + + // ('url', {object}) + options.url = url; + } + + if(_.isObject(url)){ + if(_.isFunction(options)){ + f = options; + } + options = url; + } + this.maxAttempts = retryConfig.maxAttempts; this.retryDelay = retryConfig.retryDelay; this.fullResponse = retryConfig.fullResponse; @@ -132,24 +153,39 @@ Request.prototype.abort = function () { }; }); -function Factory(options, f) { - var retryConfig = _(options || {}).defaults(DEFAULTS).pick(Object.keys(DEFAULTS)).value(); - var req = new Request(options, f, retryConfig); +function Factory(url, options, f) { + var retryConfig = _.chain(_.isObject(url) ? url : options || {}).defaults(DEFAULTS).pick(Object.keys(DEFAULTS)).value(); + var req = new Request(url, options, f, retryConfig); req._tryUntilFail(); return req; } // adds a helper for HTTP method `verb` to object `obj` function makeHelper(obj, verb) { - obj[verb] = function helper(options, f) { - if (typeof options === 'object') { - options.method = verb.toUpperCase(); - } else if (typeof options === 'string') { - options = { - method: verb.toUpperCase(), - uri: options - }; + obj[verb] = function helper(url, options, f) { + // ('url') + if(_.isString(url)){ + // ('url', f) + if(_.isFunction(options)){ + f = options; + } + + if(!_.isObject(options)){ + options = {}; + } + + // ('url', {object}) + options.url = url; } + + if(_.isObject(url)){ + if(_.isFunction(options)){ + f = options; + } + options = url; + } + + options.method = verb.toUpperCase(); return obj(options, f); }; } diff --git a/package.json b/package.json index 3a582e6..10a0648 100644 --- a/package.json +++ b/package.json @@ -18,27 +18,40 @@ }, "main": "index.js", "scripts": { - "test": "mocha test", + "test": "mocha -t 1000 -R spec $(find test -name '*.test.js')", + "test-watch": "mocha -t 100000 -R min -w $(find test -name '*.test.js')", + "test-coverage": "nyc --all --statements=100 --lines=100 --functions=100 --branches=100 --check-coverage --reporter=lcov --reporter=cobertura --report-dir=coverage -- mocha -R spec -t 100000 $(find test -name '*.test.js')", + "send-coverage": "cat ./coverage/lcov.info | coveralls", "update": "updtr", "changelog-init": "conventional-changelog -i CHANGELOG.md -s -r 0", "changelog": "conventional-changelog -i CHANGELOG.md -s", "changelog-git": "npm run changelog && git add CHANGELOG.md && git commit -m 'docs(changelog): updated' && git push origin master" }, "license": "MIT", + "nyc": { + "exclude": [ + "node_modules", + "dist", + "coverage", + "webpack.config.js", + "test" + ] + }, "dependencies": { "extend": "^3.0.0", - "fg-lodash": "0.0.2", + "lodash": "^4.15.0", "request": "^2.74.0", - "when": "~3.7.5" + "when": "^3.7.7" }, "devDependencies": { "bluebird": "^3.4.1", "chai": "^3.2.0", "conventional-changelog": "^1.1.0", "conventional-changelog-cli": "^1.2.0", + "coveralls": "^2.11.12", "kew": "~0.7.0", "mocha": "^3.0.2", - "nock": "^8.0.0", + "nyc": "^8.1.0", "q": "~1.4.1", "rsvp": "^3.2.1", "updtr": "^0.2.1" diff --git a/strategies/NetworkError.js b/strategies/NetworkError.js index 46d8d2a..37cd5d0 100644 --- a/strategies/NetworkError.js +++ b/strategies/NetworkError.js @@ -1,7 +1,7 @@ 'use strict'; var RETRIABLE_ERRORS = ['ECONNRESET', 'ENOTFOUND', 'ESOCKETTIMEDOUT', 'ETIMEDOUT', 'ECONNREFUSED', 'EHOSTUNREACH', 'EPIPE', 'EAI_AGAIN']; -var _ = require('fg-lodash'); +var _ = require('lodash'); /** * @param {Null | Object} err @@ -9,7 +9,7 @@ var _ = require('fg-lodash'); * @return {Boolean} true if the request had a network error */ function NetworkError(err /*, response*/ ) { - return err && _.contains(RETRIABLE_ERRORS, err.code); + return err && _.includes(RETRIABLE_ERRORS, err.code); } NetworkError.RETRIABLE_ERRORS = RETRIABLE_ERRORS; diff --git a/test/api-surface.test.js b/test/api-surface.test.js new file mode 100644 index 0000000..0df137f --- /dev/null +++ b/test/api-surface.test.js @@ -0,0 +1,76 @@ +'use strict'; + +var request = require('../'); +var t = require('chai').assert; + +describe('API surface', function () { + + describe('callback api', function(){ + [['request', request], ['request.get', request.get]].forEach(function(pair){ + it('should work with '+pair[0]+'(url, f)', function (done) { + pair[1]('http://www.filltext.com/?rows=1', function (err, response, body) { + t.strictEqual(response.statusCode, 200); + t.strictEqual(response.body, '[{}]'); + done(); + }); + }); + + it('should work with '+pair[0]+'(url, object, f)', function (done) { + pair[1]('http://www.filltext.com/?rows=1', { + json:true, + }, function (err, response, body) { + t.strictEqual(response.statusCode, 200); + t.deepEqual(response.body, [{}]); + done(); + }); + }); + + it('should work with '+pair[0]+'(object, f)', function (done) { + pair[1]({ + url: 'http://www.filltext.com/?rows=1', + json:true + }, function (err, response, body) { + t.strictEqual(response.statusCode, 200); + t.deepEqual(response.body, [{}]); + done(); + }); + }); + }); + }); + + describe('promise api', function(){ + [['request', request], ['request.get', request.get]].forEach(function(pair){ + it('should work with '+pair[0]+'(url)', function (done) { + pair[1]('http://www.filltext.com/?rows=1') + .then(function (response) { + t.strictEqual(response.statusCode, 200); + t.strictEqual(response.body, '[{}]'); + done(); + }); + }); + + it('should work with request(url, object)', function (done) { + pair[1]('http://www.filltext.com/?rows=1', { + json:true, + }) + .then(function (response) { + t.strictEqual(response.statusCode, 200); + t.deepEqual(response.body, [{}]); + done(); + }); + }); + + it('should work with '+pair[0]+'(object)', function (done) { + pair[1]({ + url: 'http://www.filltext.com/?rows=1', + json:true + }) + .then(function (response) { + t.strictEqual(response.statusCode, 200); + t.deepEqual(response.body, [{}]); + done(); + }); + }); + }); + }); +}); diff --git a/test/promises.test.js b/test/promises.test.js index 7cb3a13..d77991d 100644 --- a/test/promises.test.js +++ b/test/promises.test.js @@ -2,7 +2,6 @@ var request = require('../'); var t = require('chai').assert; -var nock = require('nock'); describe('Promises support', function () { @@ -60,24 +59,18 @@ describe('Promises support', function () { }); it('should reject the response on any error', function (done) { - nock('http://www.filltext.com') - .get('/') - .query({rows: 1}) - .replyWithError('Some error'); - request({ - url: 'http://www.filltext.com/?rows=1', // return 1 row of data + url: 'http://localhost:1', // return 1 row of data maxAttempts: 1, + retryStrategy: request.RetryStrategies.HTTPOrNetworkError }) .catch(function (err) { - t.strictEqual(err.message, 'Some error'); + t.strictEqual(err.message, 'connect ECONNREFUSED 127.0.0.1:1'); done(); }); }); it('should still work with callbacks', function (done) { - nock.restore(); // Allow next requests - request({url: 'http://www.filltext.com/?rows=1'}, function requestCallback(err, response, body) { t.strictEqual(response.statusCode, 200); t.strictEqual(response.attempts, 1);