From c44e8b013f0c05b5193fcb5000406f674112ccf6 Mon Sep 17 00:00:00 2001 From: Luke Childs Date: Fri, 23 Dec 2016 16:14:09 +0700 Subject: [PATCH] Drop standard in favour of xo --- package.json | 4 +- src/index.js | 235 +++++++++++++++++++++++----------------------- test/base-url.js | 19 ++++ test/baseUrl.js | 19 ---- test/cache.js | 180 +++++++++++++++++------------------ test/endpoints.js | 60 ++++++------ test/errors.js | 78 +++++++-------- test/queries.js | 40 ++++---- test/types.js | 40 ++++---- 9 files changed, 336 insertions(+), 339 deletions(-) create mode 100644 test/base-url.js delete mode 100644 test/baseUrl.js diff --git a/package.json b/package.json index 3ca4a65..a93a370 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "src/index.js", "scripts": { "test": "nyc ava", - "lint": "snazzy", + "lint": "xo", "coverage": "nyc report --reporter=text-lcov | coveralls" }, "repository": { @@ -38,6 +38,6 @@ "delay": "^1.3.1", "nock": "^9.0.2", "nyc": "^10.0.0", - "snazzy": "^5.0.0" + "xo": "^0.17.1" } } diff --git a/src/index.js b/src/index.js index 656aa6d..a63b004 100644 --- a/src/index.js +++ b/src/index.js @@ -1,124 +1,121 @@ -const got = require('got') -const cacheManager = require('cache-manager') -const querystring = require('querystring') -const pkg = require('../package.json') -const expired = require('expired') -const deepAssign = require('deep-assign') +const querystring = require('querystring'); +const got = require('got'); +const cacheManager = require('cache-manager'); +const expired = require('expired'); +const deepAssign = require('deep-assign'); +const pkg = require('../package.json'); class Onionoo { - // Constructor returns a new object so instance properties are private - constructor (options = {}) { - // Set default options - this.options = Object.assign({}, { - baseUrl: 'https://onionoo.torproject.org', - endpoints: [ - 'summary', - 'details', - 'bandwidth', - 'weights', - 'clients', - 'uptime' - ] - }, options) - if (options.cache !== false) { - this.options.cache = cacheManager.caching(Object.assign({}, { - store: 'memory', - max: 500 - }, options.cache)) - } - - // Return object containing endpoint methods - return this.options.endpoints.reduce((onionoo, endpoint) => { - onionoo[endpoint] = this.createEndpointMethod(endpoint) - - return onionoo - }, {}) - } - - // Returns a function to make requests to a given endpoint - createEndpointMethod (endpoint) { - return options => { - // Build query string (don't encode ':' for search filters) - const qs = querystring.encode(options).replace(/%3A/g, ':') - - // Build url - let url = `${this.options.baseUrl}/${endpoint}` - url += qs ? `?${qs}` : '' - - // If caching is enabled, check for url in cache - if (this.options.cache) { - return this.options.cache.get(url) - .then(cachedResult => { - let options = {} - - // If we have it cached - if (cachedResult) { - // Return the cached entry if it's still valid - if (!expired(cachedResult.headers)) { - return cachedResult - - // If it's stale, add last-modified date to headers - } else if (cachedResult.headers['last-modified']) { - options.headers = { - 'if-modified-since': cachedResult.headers['last-modified'] - } - } - } - - // Make a request - return this.makeRequest(url, options) - .then(response => { - // If we get a 304, fill in the body - if (response.statusCode === 304) { - response.body = cachedResult.body - } - - // If we get a 200 or 304, cache it - if ([200, 304].includes(response.statusCode)) { - this.options.cache.set(url, response) - } - - return response - }) - }) - - // If caching is disabled, just make the request - } else { - return this.makeRequest(url) - } - } - } - - // Returns a promise for a request - makeRequest (url, options = {}) { - options = deepAssign({ - headers: { - 'user-agent': `onionoo-node-client v${pkg.version} (${pkg.homepage})` - } - }, options) - - return got(url, options) - .catch(error => { - // Don't throw 304 responses - if (error.statusCode === 304) { - return error.response - } else { - throw error - } - }) - .then(response => { - // Format response - response = { - statusCode: response.statusCode, - statusMessage: response.statusMessage, - headers: response.headers, - body: response.body && JSON.parse(response.body) - } - - return response - }) - } + // Constructor returns a new object so instance properties are private + constructor(options = {}) { + // Set default options + this.options = Object.assign({}, { + baseUrl: 'https://onionoo.torproject.org', + endpoints: [ + 'summary', + 'details', + 'bandwidth', + 'weights', + 'clients', + 'uptime' + ] + }, options); + if (options.cache !== false) { + this.options.cache = cacheManager.caching(Object.assign({}, { + store: 'memory', + max: 500 + }, options.cache)); + } + + // Return object containing endpoint methods + return this.options.endpoints.reduce((onionoo, endpoint) => { + onionoo[endpoint] = this.createEndpointMethod(endpoint); + + return onionoo; + }, {}); + } + + // Returns a function to make requests to a given endpoint + createEndpointMethod(endpoint) { + return options => { + // Build query string (don't encode ':' for search filters) + const qs = querystring.encode(options).replace(/%3A/g, ':'); + + // Build url + let url = `${this.options.baseUrl}/${endpoint}`; + url += qs ? `?${qs}` : ''; + + // If caching is enabled, check for url in cache + if (this.options.cache) { + return this.options.cache.get(url) + .then(cachedResult => { + let options = {}; + + // If we have it cached + if (cachedResult) { + // Return the cached entry if it's still valid + if (!expired(cachedResult.headers)) { + return cachedResult; + + // If it's stale, add last-modified date to headers + } else if (cachedResult.headers['last-modified']) { + options.headers = { + 'if-modified-since': cachedResult.headers['last-modified'] + }; + } + } + + // Make a request + return this.makeRequest(url, options) + .then(response => { + // If we get a 304, fill in the body + if (response.statusCode === 304) { + response.body = cachedResult.body; + } + + // If we get a 200 or 304, cache it + if ([200, 304].includes(response.statusCode)) { + this.options.cache.set(url, response); + } + + return response; + }); + }); + } + // If caching is disabled, just make the request + return this.makeRequest(url); + }; + } + + // Returns a promise for a request + makeRequest(url, options = {}) { + options = deepAssign({ + headers: { + 'user-agent': `onionoo-node-client v${pkg.version} (${pkg.homepage})` + } + }, options); + + return got(url, options) + .catch(err => { + // Don't throw 304 responses + if (err.statusCode === 304) { + return err.response; + } + throw err; + }) + .then(response => { + // Format response + response = { + statusCode: response.statusCode, + statusMessage: response.statusMessage, + headers: response.headers, + body: response.body && JSON.parse(response.body) + }; + + return response; + }); + } } -module.exports = Onionoo +module.exports = Onionoo; diff --git a/test/base-url.js b/test/base-url.js new file mode 100644 index 0000000..6488666 --- /dev/null +++ b/test/base-url.js @@ -0,0 +1,19 @@ +import test from 'ava'; +import nock from 'nock'; +import Onionoo from '../'; +import data from './fixtures/data'; + +test('Can pass in custom endpoint', async t => { + const baseUrl = 'http://foo.com'; + const defaultEndpoint = data.defaultEndpoints[0]; + const onionoo = new Onionoo({baseUrl}); + + const scope = nock(baseUrl) + .get(`/${defaultEndpoint}`) + .reply(200, data.dummyResponse); + + const response = await onionoo[defaultEndpoint](); + + t.deepEqual(response.body, data.dummyResponse); + t.truthy(scope.isDone()); +}); diff --git a/test/baseUrl.js b/test/baseUrl.js deleted file mode 100644 index 067da4b..0000000 --- a/test/baseUrl.js +++ /dev/null @@ -1,19 +0,0 @@ -import test from 'ava' -import nock from 'nock' -import data from './fixtures/data' -import Onionoo from '../' - -test('Can pass in custom endpoint', async t => { - const baseUrl = 'http://foo.com' - const defaultEndpoint = data.defaultEndpoints[0] - const onionoo = new Onionoo({ baseUrl }) - - const scope = nock(baseUrl) - .get(`/${defaultEndpoint}`) - .reply(200, data.dummyResponse) - - const response = await onionoo[defaultEndpoint]() - - t.deepEqual(response.body, data.dummyResponse) - t.truthy(scope.isDone()) -}) diff --git a/test/cache.js b/test/cache.js index 485c9e1..6d252da 100644 --- a/test/cache.js +++ b/test/cache.js @@ -1,133 +1,133 @@ -import test from 'ava' -import nock from 'nock' -import subSeconds from 'date-fns/sub_seconds' -import delay from 'delay' -import data from './fixtures/data' -import Onionoo from '../' +import test from 'ava'; +import nock from 'nock'; +import subSeconds from 'date-fns/sub_seconds'; +import delay from 'delay'; +import Onionoo from '../'; +import data from './fixtures/data'; test('Cache can be disabled', async t => { - const onionoo = new Onionoo({ cache: false }) + const onionoo = new Onionoo({cache: false}); - const defaultEndpoint = data.defaultEndpoints[0] - const responseHeaders = { - date: new Date().toUTCString(), - age: 0, - 'cache-control': 'public, max-age=300' - } + const defaultEndpoint = data.defaultEndpoints[0]; + const responseHeaders = { + date: new Date().toUTCString(), + age: 0, + 'cache-control': 'public, max-age=300' + }; - const scope = nock(data.defaultBaseUrl) + const scope = nock(data.defaultBaseUrl) .get(`/${defaultEndpoint}`) - .reply(200, data.dummyResponse, responseHeaders) + .reply(200, data.dummyResponse, responseHeaders); - const response = await onionoo[defaultEndpoint]() + const response = await onionoo[defaultEndpoint](); - t.deepEqual(response.body, data.dummyResponse) - t.truthy(scope.isDone()) + t.deepEqual(response.body, data.dummyResponse); + t.truthy(scope.isDone()); - const scope2 = nock(data.defaultBaseUrl) + const scope2 = nock(data.defaultBaseUrl) .get(`/${defaultEndpoint}`) - .reply(200, data.dummyResponse, responseHeaders) + .reply(200, data.dummyResponse, responseHeaders); - const response2 = await onionoo[defaultEndpoint]() + const response2 = await onionoo[defaultEndpoint](); - t.deepEqual(response2.body, data.dummyResponse) - t.truthy(scope2.isDone()) -}) + t.deepEqual(response2.body, data.dummyResponse); + t.truthy(scope2.isDone()); +}); test('Responses with future max-age are cached', async t => { - const onionoo = new Onionoo() + const onionoo = new Onionoo(); - const defaultEndpoint = data.defaultEndpoints[0] - const responseHeaders = { - date: new Date().toUTCString(), - age: 0, - 'cache-control': 'public, max-age=300' - } + const defaultEndpoint = data.defaultEndpoints[0]; + const responseHeaders = { + date: new Date().toUTCString(), + age: 0, + 'cache-control': 'public, max-age=300' + }; - const scope = nock(data.defaultBaseUrl) + const scope = nock(data.defaultBaseUrl) .get(`/${defaultEndpoint}`) - .reply(200, data.dummyResponse, responseHeaders) + .reply(200, data.dummyResponse, responseHeaders); - const response = await onionoo[defaultEndpoint]() + const response = await onionoo[defaultEndpoint](); - t.deepEqual(response.body, data.dummyResponse) - t.truthy(scope.isDone()) + t.deepEqual(response.body, data.dummyResponse); + t.truthy(scope.isDone()); - const cachedResponse = await onionoo[defaultEndpoint]() + const cachedResponse = await onionoo[defaultEndpoint](); - t.deepEqual(cachedResponse.body, data.dummyResponse) -}) + t.deepEqual(cachedResponse.body, data.dummyResponse); +}); test('Responses older than max-age are not cached', async t => { - const onionoo = new Onionoo() + const onionoo = new Onionoo(); - const defaultEndpoint = data.defaultEndpoints[0] - const responseHeaders = { - date: subSeconds(new Date(), 15).toUTCString(), - age: 0, - 'cache-control': 'public, max-age=10' - } + const defaultEndpoint = data.defaultEndpoints[0]; + const responseHeaders = { + date: subSeconds(new Date(), 15).toUTCString(), + age: 0, + 'cache-control': 'public, max-age=10' + }; - const scope = nock(data.defaultBaseUrl) + const scope = nock(data.defaultBaseUrl) .get(`/${defaultEndpoint}`) - .reply(200, data.dummyResponse, responseHeaders) + .reply(200, data.dummyResponse, responseHeaders); - const response = await onionoo[defaultEndpoint]() + const response = await onionoo[defaultEndpoint](); - t.deepEqual(response.body, data.dummyResponse) - t.truthy(scope.isDone()) + t.deepEqual(response.body, data.dummyResponse); + t.truthy(scope.isDone()); - const scope2 = nock(data.defaultBaseUrl) + const scope2 = nock(data.defaultBaseUrl) .get(`/${defaultEndpoint}`) - .reply(200, data.dummyResponse, responseHeaders) + .reply(200, data.dummyResponse, responseHeaders); - const response2 = await onionoo[defaultEndpoint]() + const response2 = await onionoo[defaultEndpoint](); - t.deepEqual(response2.body, data.dummyResponse) - t.truthy(scope2.isDone()) -}) + t.deepEqual(response2.body, data.dummyResponse); + t.truthy(scope2.isDone()); +}); test('When expired, add last-modified date to headers and handle 304', async t => { - const onionoo = new Onionoo() - - const defaultEndpoint = data.defaultEndpoints[0] - const initialDate = new Date().toUTCString() - const responseHeaders = { - date: initialDate, - age: 0, - 'cache-control': 'public, max-age=1', - 'last-modified': initialDate - } - - const scope = nock(data.defaultBaseUrl) + const onionoo = new Onionoo(); + + const defaultEndpoint = data.defaultEndpoints[0]; + const initialDate = new Date().toUTCString(); + const responseHeaders = { + date: initialDate, + age: 0, + 'cache-control': 'public, max-age=1', + 'last-modified': initialDate + }; + + const scope = nock(data.defaultBaseUrl) .get(`/${defaultEndpoint}`) - .reply(200, data.dummyResponse, responseHeaders) + .reply(200, data.dummyResponse, responseHeaders); - const response = await onionoo[defaultEndpoint]() + const response = await onionoo[defaultEndpoint](); - t.deepEqual(response.body, data.dummyResponse) - t.truthy(scope.isDone()) + t.deepEqual(response.body, data.dummyResponse); + t.truthy(scope.isDone()); - const requestHeaders = { - 'if-modified-since': initialDate - } - const responseHeaders304 = { - date: new Date().toUTCString(), - age: 0, - 'cache-control': 'public, max-age=10', - 'last-modified': initialDate - } + const requestHeaders = { + 'if-modified-since': initialDate + }; + const responseHeaders304 = { + date: new Date().toUTCString(), + age: 0, + 'cache-control': 'public, max-age=10', + 'last-modified': initialDate + }; - const scope2 = nock(data.defaultBaseUrl, { requestHeaders }) + const scope2 = nock(data.defaultBaseUrl, {requestHeaders}) .get(`/${defaultEndpoint}`) - .reply(304, null, responseHeaders304) + .reply(304, null, responseHeaders304); - const response2 = await delay(2000).then(onionoo[defaultEndpoint]) + const response2 = await delay(2000).then(onionoo[defaultEndpoint]); - t.deepEqual(response2.body, data.dummyResponse) - t.truthy(scope2.isDone()) + t.deepEqual(response2.body, data.dummyResponse); + t.truthy(scope2.isDone()); - const cachedResponse = await onionoo[defaultEndpoint]() + const cachedResponse = await onionoo[defaultEndpoint](); - t.deepEqual(cachedResponse.body, data.dummyResponse) -}) + t.deepEqual(cachedResponse.body, data.dummyResponse); +}); diff --git a/test/endpoints.js b/test/endpoints.js index 84ccb63..ecf8690 100644 --- a/test/endpoints.js +++ b/test/endpoints.js @@ -1,48 +1,48 @@ -import test from 'ava' -import nock from 'nock' -import data from './fixtures/data' -import Onionoo from '../' +import test from 'ava'; +import nock from 'nock'; +import Onionoo from '../'; +import data from './fixtures/data'; test('Onionoo instance contains default endpoints', t => { - const onionoo = new Onionoo() + const onionoo = new Onionoo(); - t.deepEqual(Object.keys(onionoo), data.defaultEndpoints) -}) + t.deepEqual(Object.keys(onionoo), data.defaultEndpoints); +}); test('Default endpoint makes correct request', async t => { - const onionoo = new Onionoo() + const onionoo = new Onionoo(); - const defaultEndpoint = data.defaultEndpoints[0] - const scope = nock(data.defaultBaseUrl) + const defaultEndpoint = data.defaultEndpoints[0]; + const scope = nock(data.defaultBaseUrl) .get(`/${defaultEndpoint}`) - .reply(200, data.dummyResponse) + .reply(200, data.dummyResponse); - const response = await onionoo[defaultEndpoint]() + const response = await onionoo[defaultEndpoint](); - t.deepEqual(response.body, data.dummyResponse) - t.truthy(scope.isDone()) -}) + t.deepEqual(response.body, data.dummyResponse); + t.truthy(scope.isDone()); +}); test('Can pass in custom endpoint array', t => { - const endpoints = [ - 'foo', - 'bar' - ] - const onionoo = new Onionoo({ endpoints }) + const endpoints = [ + 'foo', + 'bar' + ]; + const onionoo = new Onionoo({endpoints}); - t.deepEqual(Object.keys(onionoo), endpoints) -}) + t.deepEqual(Object.keys(onionoo), endpoints); +}); test('Custom endpoint makes correct request', async t => { - const customEndpoint = 'foo' - const onionoo = new Onionoo({ endpoints: [customEndpoint] }) + const customEndpoint = 'foo'; + const onionoo = new Onionoo({endpoints: [customEndpoint]}); - const scope = nock(data.defaultBaseUrl) + const scope = nock(data.defaultBaseUrl) .get(`/${customEndpoint}`) - .reply(200, data.dummyResponse) + .reply(200, data.dummyResponse); - const response = await onionoo[customEndpoint]() + const response = await onionoo[customEndpoint](); - t.deepEqual(response.body, data.dummyResponse) - t.truthy(scope.isDone()) -}) + t.deepEqual(response.body, data.dummyResponse); + t.truthy(scope.isDone()); +}); diff --git a/test/errors.js b/test/errors.js index 8df4fc9..f43b8af 100644 --- a/test/errors.js +++ b/test/errors.js @@ -1,48 +1,48 @@ -import test from 'ava' -import nock from 'nock' -import data from './fixtures/data' -import Onionoo from '../' +import test from 'ava'; +import nock from 'nock'; +import Onionoo from '../'; +import data from './fixtures/data'; test('Handle HTML responses for errors', async t => { - const onionoo = new Onionoo() + const onionoo = new Onionoo(); - const defaultEndpoint = data.defaultEndpoints[0] - const scope = nock(data.defaultBaseUrl) + const defaultEndpoint = data.defaultEndpoints[0]; + const scope = nock(data.defaultBaseUrl) .get(`/${defaultEndpoint}`) - .reply(400, data.dummy400Response) + .reply(400, data.dummy400Response); - try { - await onionoo[defaultEndpoint]() - } catch (e) { - t.deepEqual(e.response.body, data.dummy400Response) - } + try { + await onionoo[defaultEndpoint](); + } catch (err) { + t.deepEqual(err.response.body, data.dummy400Response); + } - t.truthy(scope.isDone()) -}) + t.truthy(scope.isDone()); +}); test('Throw useful errors for HTTP response codes', async t => { - const onionoo = new Onionoo() - - const defaultEndpoint = data.defaultEndpoints[0] - const responseCodes = { - 400: 'Bad Request', - 404: 'Not Found', - 500: 'Internal Server Error', - 503: 'Service Unavailable' - } - - for (const responseCode in responseCodes) { - const scope = nock(data.defaultBaseUrl) + const onionoo = new Onionoo(); + + const defaultEndpoint = data.defaultEndpoints[0]; + const responseCodes = { + 400: 'Bad Request', + 404: 'Not Found', + 500: 'Internal Server Error', + 503: 'Service Unavailable' + }; + + Object.keys(responseCodes).forEach(async responseCode => { + const scope = nock(data.defaultBaseUrl) .get(`/${defaultEndpoint}`) - .reply(responseCode) - - try { - await onionoo[defaultEndpoint]() - } catch (e) { - t.is(e.message, `Response code ${responseCode} (${responseCodes[responseCode]})`) - t.is(e.statusCode, parseInt(responseCode, 10)) - t.is(e.statusMessage, responseCodes[responseCode]) - } - t.truthy(scope.isDone()) - } -}) + .reply(responseCode); + + try { + await onionoo[defaultEndpoint](); + } catch (err) { + t.is(err.message, `Response code ${responseCode} (${responseCodes[responseCode]})`); + t.is(err.statusCode, parseInt(responseCode, 10)); + t.is(err.statusMessage, responseCodes[responseCode]); + } + t.truthy(scope.isDone()); + }); +}); diff --git a/test/queries.js b/test/queries.js index 1d3bb9d..8039440 100644 --- a/test/queries.js +++ b/test/queries.js @@ -1,32 +1,32 @@ -import test from 'ava' -import nock from 'nock' -import data from './fixtures/data' -import Onionoo from '../' +import test from 'ava'; +import nock from 'nock'; +import Onionoo from '../'; +import data from './fixtures/data'; test('Query string is built correctly', async t => { - const onionoo = new Onionoo() + const onionoo = new Onionoo(); - const defaultEndpoint = data.defaultEndpoints[0] - const scope = nock(data.defaultBaseUrl) + const defaultEndpoint = data.defaultEndpoints[0]; + const scope = nock(data.defaultBaseUrl) .get(`/${defaultEndpoint}?foo=bar`) - .reply(200, data.dummyResponse) + .reply(200, data.dummyResponse); - const response = await onionoo[defaultEndpoint]({ foo: 'bar' }) + const response = await onionoo[defaultEndpoint]({foo: 'bar'}); - t.deepEqual(response.body, data.dummyResponse) - t.truthy(scope.isDone()) -}) + t.deepEqual(response.body, data.dummyResponse); + t.truthy(scope.isDone()); +}); test('":" char isn\'t url encoded so filters work', async t => { - const onionoo = new Onionoo() + const onionoo = new Onionoo(); - const defaultEndpoint = data.defaultEndpoints[0] - const scope = nock(data.defaultBaseUrl) + const defaultEndpoint = data.defaultEndpoints[0]; + const scope = nock(data.defaultBaseUrl) .get(`/${defaultEndpoint}?foo=key:value`) - .reply(200, data.dummyResponse) + .reply(200, data.dummyResponse); - const response = await onionoo[defaultEndpoint]({ foo: 'key:value' }) + const response = await onionoo[defaultEndpoint]({foo: 'key:value'}); - t.deepEqual(response.body, data.dummyResponse) - t.truthy(scope.isDone()) -}) + t.deepEqual(response.body, data.dummyResponse); + t.truthy(scope.isDone()); +}); diff --git a/test/types.js b/test/types.js index 068ae2d..7f0fbec 100644 --- a/test/types.js +++ b/test/types.js @@ -1,33 +1,33 @@ -import test from 'ava' -import Onionoo from '../' +import test from 'ava'; +import Onionoo from '../'; test('Onionoo is a function', t => { - t.is(typeof Onionoo, 'function') -}) + t.is(typeof Onionoo, 'function'); +}); test('Onionoo cannot be invoked without \'new\'', t => { - t.throws(() => Onionoo()) - t.notThrows(() => new Onionoo()) -}) + t.throws(() => Onionoo()); // eslint-disable-line new-cap + t.notThrows(() => new Onionoo()); +}); test('Onionoo instance is an object', t => { - const onionoo = new Onionoo() + const onionoo = new Onionoo(); - t.is(typeof onionoo, 'object') -}) + t.is(typeof onionoo, 'object'); +}); test('Onionoo instance contains endpoint methods', t => { - const onionoo = new Onionoo() + const onionoo = new Onionoo(); - Object.keys(onionoo).forEach(endpoint => { - t.is(typeof onionoo[endpoint], 'function') - }) -}) + Object.keys(onionoo).forEach(endpoint => { + t.is(typeof onionoo[endpoint], 'function'); + }); +}); test('Endpoint methods return promise', t => { - const onionoo = new Onionoo() + const onionoo = new Onionoo(); - Object.keys(onionoo).forEach(endpoint => { - t.true(onionoo[endpoint]() instanceof Promise) - }) -}) + Object.keys(onionoo).forEach(endpoint => { + t.true(onionoo[endpoint]() instanceof Promise); + }); +});