From 7c536d91614e6686abd9cdb6479c8f513089324b Mon Sep 17 00:00:00 2001 From: Ryan McGeary Date: Tue, 26 Dec 2017 21:11:03 -0700 Subject: [PATCH] Normalize PublicClient with AuthenticatedClient (#178) * Deprecate productID argument in PublicClient constructor PublicClient should no longer keep state about a particular product ID. * Require productID as first argument to getProductOrderBook() - Deprecate old usage of a default productID per PublicClient - Remove redundant AuthenticatedClient#getProductOrderBook() override Closes #46 - Simplify OrderbookSync#loadOrderbook() * Require productID as first argument to getProductTicker() - Deprecate old usage of a default productID per PublicClient * Require productID as first argument to getProductTrades() - Deprecate old usage of a default productID per PublicClient * Require productID as first argument to getProductTradeStream() - Deprecate old usage of a default productID per PublicClient * Require productID as first argument to getProductHistoricRates() - Deprecate old usage of a default productID per PublicClient * Require productID as first argument to getProduct24HrStats() - Deprecate old usage of a default productID per PublicClient * Consolidate deprecation & argument normalization logic in PublicClient --- README.md | 34 ++--- index.d.ts | 26 ++-- lib/clients/authenticated.js | 14 +- lib/clients/public.js | 133 ++++++++++++----- lib/orderbook_sync.js | 29 +--- tests/authenticated.spec.js | 32 ---- tests/public_client.spec.js | 280 ++++++++++++++++++++++++++++++++--- 7 files changed, 396 insertions(+), 152 deletions(-) diff --git a/README.md b/README.md index 282437a2..696e6788 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ Some methods accept optional parameters, e.g. ```js publicClient - .getProductOrderBook({ level: 3 }) + .getProductOrderBook('BTC-USD', { level: 3 }) .then(book => { /* ... */ }); ``` @@ -126,13 +126,13 @@ parameter(s) and the callback as the last parameter: ```js publicClient - .getProductOrderBook({ level: 3 }, (error, response, book) => { /* ... */ }); + .getProductOrderBook('ETH-USD', { level: 3 }, (error, response, book) => { /* ... */ }); ``` ### The Public API Client ```js -const publicClient = new Gdax.PublicClient(productID, endpoint); +const publicClient = new Gdax.PublicClient(endpoint); ``` - `productID` *optional* - defaults to 'BTC-USD' if not specified. @@ -150,25 +150,25 @@ publicClient.getProducts(callback); ```js // Get the order book at the default level of detail. -publicClient.getProductOrderBook(callback); +publicClient.getProductOrderBook('BTC-USD', callback); // Get the order book at a specific level of detail. -publicClient.getProductOrderBook({'level': 3}, callback); +publicClient.getProductOrderBook('LTC-USD', { level: 3 }, callback); ``` * [`getProductTicker`](https://docs.gdax.com/#get-product-ticker) ```js -publicClient.getProductTicker(callback); +publicClient.getProductTicker('ETH-USD', callback); ``` * [`getProductTrades`](https://docs.gdax.com/#get-trades) ```js -publicClient.getProductTrades(callback); +publicClient.getProductTrades('BTC-USD', callback); // To make paginated requests, include page parameters -publicClient.getProductTrades({'after': 1000}, callback); +publicClient.getProductTrades('BTC-USD', { after: 1000 }, callback); ``` * [`getProductTradeStream`](https://docs.gdax.com/#get-trades) @@ -177,25 +177,25 @@ Wraps around `getProductTrades`, fetches all trades with IDs `>= tradesFrom` and `<= tradesTo`. Handles pagination and rate limits. ```js -const trades = publicClient.getProductTradeStream(8408000, 8409000); +const trades = publicClient.getProductTradeStream('BTC-USD', 8408000, 8409000); // tradesTo can also be a function -const trades = publicClient.getProductTradeStream(8408000, trade => Date.parse(trade.time) >= 1463068e6); +const trades = publicClient.getProductTradeStream('BTC-USD', 8408000, trade => Date.parse(trade.time) >= 1463068e6); ``` * [`getProductHistoricRates`](https://docs.gdax.com/#get-historic-rates) ```js -publicClient.getProductHistoricRates(callback); +publicClient.getProductHistoricRates('BTC-USD', callback); // To include extra parameters: -publicClient.getProductHistoricRates({'granularity': 3000}, callback); +publicClient.getProductHistoricRates('BTC-USD', { granularity: 3000 }, callback); ``` * [`getProduct24HrStats`](https://docs.gdax.com/#get-24hr-stats) ```js -publicClient.getProduct24HrStats(callback); +publicClient.getProduct24HrStats('BTC-USD', callback); ``` * [`getCurrencies`](https://docs.gdax.com/#get-currencies) @@ -269,7 +269,7 @@ const accountID = '7d0f7d8e-dd34-4d9c-a846-06f431c381ba'; authedClient.getAccountHistory(accountID, callback); // For pagination, you can include extra page arguments -authedClient.getAccountHistory(accountID, {'before': 3000}, callback); +authedClient.getAccountHistory(accountID, { before: 3000 }, callback); ``` * [`getAccountHolds`](https://docs.gdax.com/#get-holds) @@ -279,7 +279,7 @@ const accountID = '7d0f7d8e-dd34-4d9c-a846-06f431c381ba'; authedClient.getAccountHolds(accountID, callback); // For pagination, you can include extra page arguments -authedClient.getAccountHolds(accountID, {'before': 3000}, callback); +authedClient.getAccountHolds(accountID, { before: 3000 }, callback); ``` * [`buy`, `sell`](https://docs.gdax.com/#place-a-new-order) @@ -346,7 +346,7 @@ authedClient.cancelAllOrders({product_id: 'BTC-USD'}, callback); ```js authedClient.getOrders(callback); // For pagination, you can include extra page arguments -authedClient.getOrders({'after': 3000}, callback); +authedClient.getOrders({ after: 3000 }, callback); ``` * [`getOrder`](https://docs.gdax.com/#get-an-order) @@ -361,7 +361,7 @@ authedClient.getOrder(orderID, callback); ```js authedClient.getFills(callback); // For pagination, you can include extra page arguments -authedClient.getFills({'before': 3000}, callback); +authedClient.getFills({ before: 3000 }, callback); ``` * [`getFundings`](https://docs.gdax.com/#list-fundings) diff --git a/index.d.ts b/index.d.ts index 028d23a7..5cdc4b65 100644 --- a/index.d.ts +++ b/index.d.ts @@ -133,28 +133,28 @@ declare module 'gdax' { } export class PublicClient { - constructor(productId?: string, apiURI?: string); + constructor(apiURI?: string); getProducts(callback: callback); getProducts(): Promise; - getProductOrderBook(options: any, callback: callback); - getProductOrderBook(options: any): Promise; + getProductOrderBook(productID: string, options: any, callback: callback); + getProductOrderBook(productID: string, options: any): Promise; - getProductTicker(callback: callback); - getProductTicker(): Promise; + getProductTicker(productID: string, callback: callback); + getProductTicker(productID: string, ): Promise; - getProductTrades(callback: callback); - getProductTrades(): Promise; + getProductTrades(productID: string, callback: callback); + getProductTrades(productID: string, ): Promise; - getProductTradeStream(callback: callback); - getProductTradeStream(): Promise; + getProductTradeStream(productID: string, tradesFrom: number, tradesTo: any, callback: callback); + getProductTradeStream(productID: string, tradesFrom: number, tradesTo: any): Promise; - getProductHistoricRates(args: any, callback: callback); - getProductHistoricRates(args: any): Promise; + getProductHistoricRates(productID: string, args: any, callback: callback); + getProductHistoricRates(productID: string, args: any): Promise; - getProduct24HrStats(callback: callback); - getProduct24HrStats(): Promise; + getProduct24HrStats(productID: string, callback: callback); + getProduct24HrStats(productID: string): Promise; getCurrencies(callback: callback); getCurrencies(): Promise; diff --git a/lib/clients/authenticated.js b/lib/clients/authenticated.js index 148eb880..a54c2f56 100644 --- a/lib/clients/authenticated.js +++ b/lib/clients/authenticated.js @@ -4,7 +4,7 @@ const PublicClient = require('./public.js'); class AuthenticatedClient extends PublicClient { constructor(key, secret, passphrase, apiURI) { - super('', apiURI); + super(apiURI); this.key = key; this.secret = secret; this.passphrase = passphrase; @@ -45,7 +45,7 @@ class AuthenticatedClient extends PublicClient { getCoinbaseAccounts(callback) { return this.get(['coinbase-accounts'], callback); } - + getPaymentMethods(callback) { return this.get(['payment-methods'], callback); } @@ -127,16 +127,6 @@ class AuthenticatedClient extends PublicClient { return this.delete(['orders'], callback); } - // temp over ride public call to get Product Orderbook - getProductOrderBook(args = {}, productId, callback) { - if (!callback && typeof args === 'function') { - callback = args; - args = {}; - } - - return this.get(['products', productId, 'book'], { qs: args }, callback); - } - cancelAllOrders(args = {}, callback) { if (!callback && typeof args === 'function') { callback = args; diff --git a/lib/clients/public.js b/lib/clients/public.js index 81d02085..1a0ed448 100644 --- a/lib/clients/public.js +++ b/lib/clients/public.js @@ -2,8 +2,17 @@ const request = require('request'); const { Readable } = require('stream'); class PublicClient { - constructor(productID = 'BTC-USD', apiURI = 'https://api.gdax.com') { - this.productID = productID; + constructor(apiURI = 'https://api.gdax.com') { + this.productID = 'BTC-USD'; + if (apiURI && !apiURI.startsWith('http')) { + process.emitWarning( + '`new PublicClient()` no longer accepts a product ID as the first argument. ', + 'DeprecationWarning' + ); + this.productID = apiURI; + apiURI = arguments[1] || 'https://api.gdax.com'; + } + this.apiURI = apiURI; this.API_LIMIT = 100; } @@ -95,36 +104,54 @@ class PublicClient { return this.get(['products'], callback); } - getProductOrderBook(args = {}, callback) { - if (!callback && typeof args === 'function') { - callback = args; - args = {}; - } + getProductOrderBook(productID, args, callback) { + [productID, args, callback] = this._normalizeProductArgs( + productID, + args, + callback, + 'getProductOrderBook' + ); - return this.get( - ['products', this.productID, 'book'], - { qs: args }, - callback + const path = ['products', productID, 'book']; + return this.get(path, { qs: args }, callback); + } + + getProductTicker(productID, callback) { + [productID, , callback] = this._normalizeProductArgs( + productID, + null, + callback, + 'getProductTicker' ); + + const path = ['products', productID, 'ticker']; + return this.get(path, callback); } - getProductTicker(callback) { - return this.get(['products', this.productID, 'ticker'], callback); + getProductTrades(productID, args, callback) { + [productID, args, callback] = this._normalizeProductArgs( + productID, + args, + callback, + 'getProductTrades' + ); + + const path = ['products', productID, 'trades']; + return this.get(path, { qs: args }, callback); } - getProductTrades(args = {}, callback) { - if (!callback && typeof args === 'function') { - callback = args; - args = {}; + getProductTradeStream(productID, tradesFrom, tradesTo) { + if (!productID || typeof productID !== 'string') { + [tradesFrom, tradesTo] = Array.prototype.slice.call(arguments); } - return this.get( - ['products', this.productID, 'trades'], - { qs: args }, - callback + + [productID] = this._normalizeProductArgs( + productID, + null, + null, + 'getProductTradeStream' ); - } - getProductTradeStream(tradesFrom, tradesTo) { let shouldStop = null; if (typeof tradesTo === 'function') { @@ -155,7 +182,7 @@ class PublicClient { let opts = { before: tradesFrom, after: after, limit: this.API_LIMIT }; - this.getProductTrades(opts, (err, resp, data) => { + this.getProductTrades(productID, opts, (err, resp, data) => { if (err) { stream.emit('error', err); return; @@ -201,20 +228,28 @@ class PublicClient { } } - getProductHistoricRates(args = {}, callback) { - if (!callback && typeof args === 'function') { - callback = args; - args = {}; - } - return this.get( - ['products', this.productID, 'candles'], - { qs: args }, - callback + getProductHistoricRates(productID, args, callback) { + [productID, args, callback] = this._normalizeProductArgs( + productID, + args, + callback, + 'getProductHistoricRates' ); + + const path = ['products', productID, 'candles']; + return this.get(path, { qs: args }, callback); } - getProduct24HrStats(callback) { - return this.get(['products', this.productID, 'stats'], callback); + getProduct24HrStats(productID, callback) { + [productID, , callback] = this._normalizeProductArgs( + productID, + null, + callback, + 'getProduct24HrStats' + ); + + const path = ['products', productID, 'stats']; + return this.get(path, callback); } getCurrencies(callback) { @@ -224,6 +259,34 @@ class PublicClient { getTime(callback) { return this.get(['time'], callback); } + + _normalizeProductArgs(productID, args, callback, caller) { + this._deprecationWarningIfProductIdMissing(productID, caller); + + callback = [callback, args, productID].find(byType('function')); + args = [args, productID, {}].find(byType('object')); + productID = [productID, this.productID].find(byType('string')); + + if (!productID) { + throw new Error('No productID specified.'); + } + + return [productID, args, callback]; + } + + _deprecationWarningIfProductIdMissing(productID, caller) { + if (!productID || typeof productID !== 'string') { + process.emitWarning( + `\`${caller}()\` now requires a product ID as the first argument. ` + + `Attempting to use PublicClient#productID (${ + this.productID + }) instead.`, + 'DeprecationWarning' + ); + } + } } +const byType = type => o => o !== null && typeof o === type; + module.exports = exports = PublicClient; diff --git a/lib/orderbook_sync.js b/lib/orderbook_sync.js index 3334dd7b..1ac06c8d 100644 --- a/lib/orderbook_sync.js +++ b/lib/orderbook_sync.js @@ -18,16 +18,17 @@ class OrderbookSync extends WebsocketClient { this._queues = {}; // [] this._sequences = {}; // -1 - this._public_clients = {}; this.books = {}; if (this.auth.secret) { - this._authenticatedClient = new AuthenticatedClient( + this._client = new AuthenticatedClient( this.auth.key, this.auth.secret, this.auth.passphrase, this.apiURI ); + } else { + this._client = new PublicClient(this.apiURI); } this.productIDs.forEach(productID => { @@ -62,26 +63,10 @@ class OrderbookSync extends WebsocketClient { return; } - const bookLevel = 3; - const args = { level: bookLevel }; - - if (this._authenticatedClient) { - this._authenticatedClient - .getProductOrderBook(args, productID) - .then(onData.bind(this)) - .catch(onError.bind(this)); - } else { - if (!this._public_clients[productID]) { - this._public_clients[productID] = new PublicClient( - productID, - this.apiURI - ); - } - this._public_clients[productID] - .getProductOrderBook(args) - .then(onData.bind(this)) - .catch(onError.bind(this)); - } + this._client + .getProductOrderBook(productID, { level: 3 }) + .then(onData.bind(this)) + .catch(onError.bind(this)); function onData(data) { this.books[productID].state(data); diff --git a/tests/authenticated.spec.js b/tests/authenticated.spec.js index e1589dbc..099bed91 100644 --- a/tests/authenticated.spec.js +++ b/tests/authenticated.spec.js @@ -343,38 +343,6 @@ suite('AuthenticatedClient', () => { .catch(err => assert.ifError(err) || assert.fail()); }); - test('.getProductOrderBook()', done => { - nock(EXCHANGE_API_URL) - .get('/products/BTC-USD/book?level=3') - .times(2) - .reply(200, { - asks: [], - bids: [], - }); - - let cbtest = new Promise((resolve, reject) => { - authClient.getProductOrderBook( - { level: 3 }, - 'BTC-USD', - (err, resp, data) => { - if (err) { - reject(err); - } - assert(data); - resolve(); - } - ); - }); - - let promisetest = authClient - .getProductOrderBook({ level: 3 }, 'BTC-USD') - .then(data => assert(data)); - - Promise.all([cbtest, promisetest]) - .then(() => done()) - .catch(err => assert.ifError(err) || assert.fail()); - }); - suite('.cancelAllOrders()', () => { test('cancels all orders', () => { const cancelledOrdersOne = [ diff --git a/tests/public_client.spec.js b/tests/public_client.spec.js index 1ee14997..fe390e01 100644 --- a/tests/public_client.spec.js +++ b/tests/public_client.spec.js @@ -9,7 +9,68 @@ const EXCHANGE_API_URL = 'https://api.gdax.com'; suite('PublicClient', () => { afterEach(() => nock.cleanAll()); - test('.getProductTrades()', done => { + test('.constructor()', () => { + let client = new Gdax.PublicClient(); + assert.equal(client.apiURI, EXCHANGE_API_URL); + assert.equal(client.API_LIMIT, 100); + assert.equal(client.productID, 'BTC-USD'); // deprecated + + client = new Gdax.PublicClient('https://api-public.sandbox.gdax.com'); + assert.equal(client.apiURI, 'https://api-public.sandbox.gdax.com'); + }); + + // Delete this test when the deprecation is final + test('.constructor() (with deprecated signature accepting a product ID)', () => { + let client = new Gdax.PublicClient('LTC-USD'); + assert.equal(client.apiURI, EXCHANGE_API_URL); + assert.equal(client.productID, 'LTC-USD'); + }); + + test('.getProductOrderBook()', () => { + nock(EXCHANGE_API_URL) + .get('/products/LTC-USD/book?level=3') + .times(2) + .reply(200, { + asks: [], + bids: [], + }); + + const cbtest = new Promise((resolve, reject) => { + publicClient.getProductOrderBook( + 'LTC-USD', + { level: 3 }, + (err, resp, data) => { + if (err) { + reject(err); + } + assert(data); + resolve(); + } + ); + }); + + const promisetest = publicClient + .getProductOrderBook('LTC-USD', { level: 3 }) + .then(data => assert(data)); + + return Promise.all([cbtest, promisetest]); + }); + + // Delete this test when the deprecation is final + test('.getProductOrderBook() (with deprecated signature implying default product ID)', () => { + nock(EXCHANGE_API_URL) + .get('/products/BTC-USD/book?level=2') + .reply(200, { + asks: [], + bids: [], + }); + + return publicClient + .getProductOrderBook({ level: 2 }) + .then(data => assert(data)); + }); + + test('.getProductTrades()', () => { const expectedResponse = [ { time: '2014-11-07T22:19:28.578544Z', @@ -28,12 +89,12 @@ suite('PublicClient', () => { ]; nock(EXCHANGE_API_URL) - .get('/products/BTC-USD/trades') + .get('/products/LTC-USD/trades') .times(2) .reply(200, expectedResponse); - let cbtest = new Promise((resolve, reject) => { - publicClient.getProductTrades((err, resp, data) => { + const cbtest = new Promise((resolve, reject) => { + publicClient.getProductTrades('LTC-USD', (err, resp, data) => { if (err) { reject(err); } @@ -42,24 +103,46 @@ suite('PublicClient', () => { }); }); - let promisetest = publicClient - .getProductTrades() + const promisetest = publicClient + .getProductTrades('LTC-USD') .then(data => assert.deepEqual(data, expectedResponse)); - Promise.all([cbtest, promisetest]) - .then(() => done()) - .catch(err => assert.isError(err) || assert.fail()); + return Promise.all([cbtest, promisetest]); }); - test('.getProductTicker() should return values', done => { - nock(EXCHANGE_API_URL).get('/products/BTC-USD/ticker').times(2).reply(200, { - trade_id: 'test-id', - price: '9.00', - size: '5', - }); + // Delete this test when the deprecation is final + test('.getProductTrades() (with deprecated signature implying default product ID)', () => { + const expectedResponse = [ + { + time: '2014-11-07T22:19:28.578544Z', + trade_id: 74, + price: '10.00000000', + size: '0.01000000', + side: 'buy', + }, + ]; + + nock(EXCHANGE_API_URL) + .get('/products/BTC-USD/trades') + .reply(200, expectedResponse); - let cbtest = new Promise((resolve, reject) => { - publicClient.getProductTicker((err, resp, data) => { + return publicClient + .getProductTrades() + .then(data => assert.deepEqual(data, expectedResponse)); + }); + + test('.getProductTicker()', () => { + nock(EXCHANGE_API_URL) + .get('/products/ETH-USD/ticker') + .times(2) + .reply(200, { + trade_id: 'test-id', + price: '9.00', + size: '5', + }); + + const cbtest = new Promise((resolve, reject) => { + publicClient.getProductTicker('ETH-USD', (err, resp, data) => { if (err) { reject(err); } @@ -72,15 +155,30 @@ suite('PublicClient', () => { }); }); - let promisetest = publicClient.getProductTicker().then(data => { + const promisetest = publicClient.getProductTicker('ETH-USD').then(data => { assert.equal(data.trade_id, 'test-id'); assert.equal(data.price, '9.00'); assert.equal(data.size, '5'); }); - Promise.all([cbtest, promisetest]) - .then(() => done()) - .catch(err => assert.isError(err) || assert.fail()); + return Promise.all([cbtest, promisetest]); + }); + + // Delete this test when the deprecation is final + test('.getProductTicker() (with deprecated signature implying default product ID)', () => { + nock(EXCHANGE_API_URL) + .get('/products/BTC-USD/ticker') + .reply(200, { + trade_id: 'test-id', + price: '90.00', + size: '2', + }); + + return publicClient.getProductTicker().then(data => { + assert.equal(data.trade_id, 'test-id'); + assert.equal(data.price, '90.00'); + assert.equal(data.size, '2'); + }); }); suite('.getProductTradeStream()', () => { @@ -93,6 +191,34 @@ suite('PublicClient', () => { let last = from; let current; + publicClient + .getProductTradeStream('BTC-USD', from, to) + .on('data', data => { + current = data.trade_id; + assert.equal(typeof current, 'number'); + assert.equal( + current, + last + 1, + current + ' is next in series, last: ' + last + ); + last = current; + }) + .on('end', () => { + assert((current = to - 1)); + done(); + }) + .on('error', err => { + assert.fail(err); + }); + }); + + // Delete this test when the deprecation is final + test('streams trades (with deprecated signature implying default product ID)', done => { + nock.load('./tests/mocks/pubclient_stream_trades.json'); + + let last = from; + let current; + publicClient .getProductTradeStream(from, to) .on('data', data => { @@ -121,6 +247,7 @@ suite('PublicClient', () => { publicClient .getProductTradeStream( + 'BTC-USD', from, trade => Date.parse(trade.time) >= 1463068800000 ) @@ -147,6 +274,7 @@ suite('PublicClient', () => { publicClient .getProductTradeStream( + 'BTC-USD', from, trade => Date.parse(trade.time) >= Date.now() ) @@ -166,4 +294,114 @@ suite('PublicClient', () => { }); }); }); + + test('.getProductHistoricRates()', () => { + nock(EXCHANGE_API_URL) + .get('/products/ETH-USD/candles') + .times(2) + .reply(200, [ + [1514273340, 759.75, 759.97, 759.75, 759.97, 8.03891157], + [1514273280, 758.99, 759.74, 758.99, 759.74, 17.36616621], + [1514273220, 758.99, 759, 759, 759, 10.6524787], + ]); + + const cbtest = new Promise((resolve, reject) => { + publicClient.getProductHistoricRates('ETH-USD', (err, resp, data) => { + if (err) { + reject(err); + } + + assert.equal(data[0][0], 1514273340); + assert.equal(data[0][1], 759.75); + assert.equal(data[2][0], 1514273220); + + resolve(); + }); + }); + + const promisetest = publicClient + .getProductHistoricRates('ETH-USD') + .then(data => { + assert.equal(data[0][0], 1514273340); + assert.equal(data[0][1], 759.75); + assert.equal(data[2][0], 1514273220); + }); + + return Promise.all([cbtest, promisetest]); + }); + + // Delete this test when the deprecation is final + test('.getProductHistoricRates() (with deprecated signature implying default product ID)', () => { + nock(EXCHANGE_API_URL) + .get('/products/BTC-USD/candles') + .reply(200, [ + [1514273220, 15399.99, 15400, 15399, 15399, 0.369797], + [1514273160, 15399.99, 15400, 15400, 15400, 0.673643], + [1514273100, 15399.99, 15400, 15400, 15400, 0.849436], + ]); + + return publicClient.getProductHistoricRates().then(data => { + assert.equal(data[0][0], 1514273220); + assert.equal(data[0][1], 15399.99); + assert.equal(data[2][0], 1514273100); + }); + }); + + test('.getProduct24HrStats()', () => { + nock(EXCHANGE_API_URL) + .get('/products/ETH-USD/stats') + .times(2) + .reply(200, { + open: '720', + high: '770', + low: '710', + volume: '110000', + last: '760', + volume_30day: '9800000', + }); + + const cbtest = new Promise((resolve, reject) => { + publicClient.getProduct24HrStats('ETH-USD', (err, resp, data) => { + if (err) { + reject(err); + } + + assert.equal(data.open, 720); + assert.equal(data.high, 770); + assert.equal(data.volume, 110000); + + resolve(); + }); + }); + + const promisetest = publicClient + .getProduct24HrStats('ETH-USD') + .then(data => { + assert.equal(data.open, 720); + assert.equal(data.high, 770); + assert.equal(data.volume, 110000); + }); + + return Promise.all([cbtest, promisetest]); + }); + + // Delete this test when the deprecation is final + test('.getProduct24HrStats() (with deprecated signature implying default product ID)', () => { + nock(EXCHANGE_API_URL) + .get('/products/BTC-USD/stats') + .reply(200, { + open: '14000', + high: '15700', + low: '13800', + volume: '17400', + last: '15300', + volume_30day: '1100000', + }); + + return publicClient.getProduct24HrStats().then(data => { + assert.equal(data.open, 14000); + assert.equal(data.high, 15700); + assert.equal(data.volume, 17400); + }); + }); });