diff --git a/test/websockets/bookTicker.js b/test/websockets/bookTicker.js new file mode 100644 index 00000000..b6e96c15 --- /dev/null +++ b/test/websockets/bookTicker.js @@ -0,0 +1,90 @@ +import test from 'ava' + +import Binance from 'index' + +import { checkFields } from '../utils' + +const client = Binance() + +test('[WS] bookTicker - single symbol', t => { + return new Promise(resolve => { + const clean = client.ws.bookTicker('ETHBTC', ticker => { + checkFields(t, ticker, ['updateId', 'symbol', 'bestBid', 'bestBidQnt', 'bestAsk', 'bestAskQnt']) + t.is(ticker.symbol, 'ETHBTC') + t.truthy(typeof ticker.bestBid === 'string') + t.truthy(typeof ticker.bestAsk === 'string') + t.truthy(typeof ticker.bestBidQnt === 'string') + t.truthy(typeof ticker.bestAskQnt === 'string') + clean() + resolve() + }) + }) +}) + +test('[WS] bookTicker - multiple symbols', t => { + return new Promise(resolve => { + const symbols = ['ETHBTC', 'BTCUSDT', 'BNBBTC'] + + const clean = client.ws.bookTicker(symbols, ticker => { + checkFields(t, ticker, ['updateId', 'symbol', 'bestBid', 'bestBidQnt', 'bestAsk', 'bestAskQnt']) + t.truthy(symbols.includes(ticker.symbol)) + clean() + resolve() + }) + }) +}) + +test('[WS] bookTicker - raw data without transform', t => { + return new Promise(resolve => { + const clean = client.ws.bookTicker('ETHBTC', ticker => { + // Raw data should have lowercase field names + t.truthy(ticker.u) + t.truthy(ticker.s) + t.truthy(ticker.b) + t.truthy(ticker.B) + t.truthy(ticker.a) + t.truthy(ticker.A) + clean() + resolve() + }, false) + }) +}) + +test('[WS] bookTicker - transformed data', t => { + return new Promise(resolve => { + const clean = client.ws.bookTicker('ETHBTC', ticker => { + // Transformed data should have camelCase field names + t.truthy(ticker.updateId) + t.truthy(ticker.symbol) + t.truthy(ticker.bestBid) + t.truthy(ticker.bestBidQnt) + t.truthy(ticker.bestAsk) + t.truthy(ticker.bestAskQnt) + // Should NOT have raw field names + t.falsy(ticker.u) + t.falsy(ticker.s) + clean() + resolve() + }, true) + }) +}) + +test('[WS] bookTicker - cleanup function', t => { + const clean = client.ws.bookTicker('ETHBTC', () => { + // Callback implementation + }) + + // Verify clean is a function + t.is(typeof clean, 'function') + + // Clean up immediately + clean() + + // Give it a moment and verify cleanup executed properly + return new Promise(resolve => { + setTimeout(() => { + t.pass('Cleanup function executed without errors') + resolve() + }, 100) + }) +}) diff --git a/test/websockets/candles.js b/test/websockets/candles.js new file mode 100644 index 00000000..aa19d0b4 --- /dev/null +++ b/test/websockets/candles.js @@ -0,0 +1,99 @@ +import test from 'ava' + +import Binance from 'index' + +import { checkFields } from '../utils' + +const client = Binance() + +test('[WS] candles - missing parameters', t => { + try { + client.ws.candles('ETHBTC', d => d) + t.fail('Should have thrown an error') + } catch (e) { + t.is(e.message, 'Please pass a symbol, interval and callback.') + } +}) + +test('[WS] candles - single symbol', t => { + return new Promise(resolve => { + const clean = client.ws.candles('ETHBTC', '5m', candle => { + checkFields(t, candle, ['open', 'high', 'low', 'close', 'volume', 'trades', 'quoteVolume']) + t.is(candle.symbol, 'ETHBTC') + t.is(candle.interval, '5m') + clean() + resolve() + }) + }) +}) + +test('[WS] candles - multiple symbols', t => { + return new Promise(resolve => { + const symbols = ['ETHBTC', 'BNBBTC', 'BNTBTC'] + + const clean = client.ws.candles(symbols, '5m', candle => { + checkFields(t, candle, ['open', 'high', 'low', 'close', 'volume', 'trades', 'quoteVolume']) + t.truthy(symbols.includes(candle.symbol)) + clean() + resolve() + }) + }) +}) + +test('[WS] candles - raw data without transform', t => { + return new Promise(resolve => { + const clean = client.ws.candles('ETHBTC', '1m', candle => { + // Raw data should have the structure with 'k' key + t.truthy(candle.e) + t.truthy(candle.E) + t.truthy(candle.s) + t.truthy(candle.k) + clean() + resolve() + }, false) + }) +}) + +test('[WS] candles - transformed data', t => { + return new Promise(resolve => { + const clean = client.ws.candles('ETHBTC', '1m', candle => { + // Transformed data should have camelCase field names + t.truthy(candle.eventType) + t.truthy(candle.eventTime) + t.truthy(candle.symbol) + t.truthy(candle.open) + t.truthy(candle.close) + // Should NOT have raw field names + t.falsy(candle.k) + clean() + resolve() + }, true) + }) +}) + +test('[WS] futuresCandles - single symbol', t => { + return new Promise(resolve => { + const clean = client.ws.futuresCandles('BTCUSDT', '5m', candle => { + checkFields(t, candle, ['open', 'high', 'low', 'close', 'volume', 'trades', 'quoteVolume']) + t.is(candle.symbol, 'BTCUSDT') + t.is(candle.interval, '5m') + clean() + resolve() + }) + }) +}) + +test.skip('[WS] deliveryCandles - single symbol', t => { + return new Promise(resolve => { + const clean = client.ws.deliveryCandles('TRXUSD_PERP', '5m', candle => { + checkFields(t, candle, ['open', 'high', 'low', 'close', 'volume', 'trades', 'baseVolume']) + t.is(candle.symbol, 'TRXUSD_PERP') + t.is(candle.interval, '5m') + // Delivery candles have baseVolume instead of quoteVolume + t.truthy(candle.baseVolume) + t.falsy(candle.quoteVolume) + clean() + resolve() + }) + }) +}) diff --git a/test/websockets/customSubStream.js b/test/websockets/customSubStream.js new file mode 100644 index 00000000..32748291 --- /dev/null +++ b/test/websockets/customSubStream.js @@ -0,0 +1,134 @@ +import test from 'ava' + +import Binance from 'index' + +const client = Binance() + +test('[WS] customSubStream - single stream', t => { + return new Promise(resolve => { + const clean = client.ws.customSubStream('ethbtc@ticker', data => { + t.truthy(data) + t.truthy(data.e || data.stream) + clean() + resolve() + }) + }) +}) + +test('[WS] customSubStream - multiple streams', t => { + return new Promise(resolve => { + const streams = ['ethbtc@ticker', 'btcusdt@ticker'] + + const clean = client.ws.customSubStream(streams, data => { + t.truthy(data) + clean() + resolve() + }) + }) +}) + +test('[WS] customSubStream - depth stream', t => { + return new Promise(resolve => { + const clean = client.ws.customSubStream('ethbtc@depth', data => { + t.truthy(data) + t.truthy(data.e || data.lastUpdateId) + clean() + resolve() + }) + }) +}) + +test('[WS] customSubStream - aggTrade stream', t => { + return new Promise(resolve => { + const clean = client.ws.customSubStream('ethbtc@aggTrade', data => { + t.truthy(data) + t.truthy(data.e || data.a) + clean() + resolve() + }) + }) +}) + +test('[WS] customSubStream - kline stream', t => { + return new Promise(resolve => { + const clean = client.ws.customSubStream('ethbtc@kline_1m', data => { + t.truthy(data) + t.truthy(data.e || data.k) + clean() + resolve() + }) + }) +}) + +test('[WS] customSubStream - cleanup function', t => { + const clean = client.ws.customSubStream('ethbtc@ticker', () => { + // Callback implementation + }) + + // Verify clean is a function + t.is(typeof clean, 'function') + + // Clean up immediately + clean() + + // Give it a moment and verify cleanup executed properly + return new Promise(resolve => { + setTimeout(() => { + t.pass('Cleanup function executed without errors') + resolve() + }, 100) + }) +}) + +test('[WS] futuresCustomSubStream - single stream', t => { + return new Promise(resolve => { + const clean = client.ws.futuresCustomSubStream('btcusdt@ticker', data => { + t.truthy(data) + t.truthy(data.e || data.stream) + clean() + resolve() + }) + }) +}) + +test('[WS] futuresCustomSubStream - aggTrade stream', t => { + return new Promise(resolve => { + const clean = client.ws.futuresCustomSubStream('btcusdt@aggTrade', data => { + t.truthy(data) + t.truthy(data.e || data.a) + clean() + resolve() + }) + }) +}) + +test.skip('[WS] deliveryCustomSubStream - single stream', t => { + return new Promise(resolve => { + const clean = client.ws.deliveryCustomSubStream('trxusd_perp@ticker', data => { + t.truthy(data) + t.truthy(data.e || data.stream) + clean() + resolve() + }) + }) +}) + +test('[WS] futuresCustomSubStream - cleanup function', t => { + const clean = client.ws.futuresCustomSubStream('btcusdt@ticker', () => { + // Callback implementation + }) + + // Verify clean is a function + t.is(typeof clean, 'function') + + // Clean up immediately + clean() + + // Give it a moment and verify cleanup executed properly + return new Promise(resolve => { + setTimeout(() => { + t.pass('Cleanup function executed without errors') + resolve() + }, 100) + }) +}) diff --git a/test/websockets/depth.js b/test/websockets/depth.js new file mode 100644 index 00000000..dabdc547 --- /dev/null +++ b/test/websockets/depth.js @@ -0,0 +1,167 @@ +import test from 'ava' + +import Binance from 'index' + +import { checkFields } from '../utils' + +const client = Binance() + +test('[WS] depth - single symbol', t => { + return new Promise(resolve => { + const clean = client.ws.depth('ETHBTC', depth => { + checkFields(t, depth, [ + 'eventType', + 'eventTime', + 'firstUpdateId', + 'finalUpdateId', + 'symbol', + 'bidDepth', + 'askDepth', + ]) + t.is(depth.symbol, 'ETHBTC') + clean() + resolve() + }) + }) +}) + +test('[WS] depth - with update speed', t => { + return new Promise(resolve => { + const clean = client.ws.depth('ETHBTC@100ms', depth => { + checkFields(t, depth, [ + 'eventType', + 'eventTime', + 'firstUpdateId', + 'finalUpdateId', + 'symbol', + 'bidDepth', + 'askDepth', + ]) + t.is(depth.symbol, 'ETHBTC') + clean() + resolve() + }) + }) +}) + +test('[WS] partialDepth - single symbol', t => { + return new Promise(resolve => { + const clean = client.ws.partialDepth({ symbol: 'ETHBTC', level: 10 }, depth => { + checkFields(t, depth, ['lastUpdateId', 'bids', 'asks', 'symbol', 'level']) + t.is(depth.symbol, 'ETHBTC') + t.is(depth.level, 10) + t.truthy(Array.isArray(depth.bids)) + t.truthy(Array.isArray(depth.asks)) + clean() + resolve() + }) + }) +}) + +test('[WS] partialDepth - with update speed', t => { + return new Promise(resolve => { + const clean = client.ws.partialDepth({ symbol: 'ETHBTC@100ms', level: 10 }, depth => { + checkFields(t, depth, ['lastUpdateId', 'bids', 'asks']) + t.truthy(Array.isArray(depth.bids)) + t.truthy(Array.isArray(depth.asks)) + clean() + resolve() + }) + }) +}) + +test('[WS] futuresDepth - single symbol', t => { + return new Promise(resolve => { + const clean = client.ws.futuresDepth('BTCUSDT', depth => { + checkFields(t, depth, [ + 'eventType', + 'eventTime', + 'transactionTime', + 'symbol', + 'firstUpdateId', + 'finalUpdateId', + 'prevFinalUpdateId', + 'bidDepth', + 'askDepth', + ]) + t.is(depth.symbol, 'BTCUSDT') + t.truthy(depth.prevFinalUpdateId !== undefined) + clean() + resolve() + }) + }) +}) + +test('[WS] futuresPartialDepth - single symbol', t => { + return new Promise(resolve => { + const clean = client.ws.futuresPartialDepth({ symbol: 'BTCUSDT', level: 10 }, depth => { + checkFields(t, depth, [ + 'level', + 'eventType', + 'eventTime', + 'transactionTime', + 'symbol', + 'firstUpdateId', + 'finalUpdateId', + 'prevFinalUpdateId', + 'bidDepth', + 'askDepth', + ]) + t.is(depth.symbol, 'BTCUSDT') + t.is(depth.level, 10) + clean() + resolve() + }) + }) +}) + +test.skip('[WS] deliveryDepth - single symbol', t => { + return new Promise(resolve => { + const clean = client.ws.deliveryDepth('TRXUSD_PERP', depth => { + checkFields(t, depth, [ + 'eventType', + 'eventTime', + 'transactionTime', + 'symbol', + 'pair', + 'firstUpdateId', + 'finalUpdateId', + 'prevFinalUpdateId', + 'bidDepth', + 'askDepth', + ]) + t.is(depth.symbol, 'TRXUSD_PERP') + t.truthy(depth.pair) + clean() + resolve() + }) + }) +}) + +test.skip('[WS] deliveryPartialDepth - single symbol', t => { + return new Promise(resolve => { + const clean = client.ws.deliveryPartialDepth( + { symbol: 'TRXUSD_PERP', level: 10 }, + depth => { + checkFields(t, depth, [ + 'level', + 'eventType', + 'eventTime', + 'transactionTime', + 'symbol', + 'pair', + 'firstUpdateId', + 'finalUpdateId', + 'prevFinalUpdateId', + 'bidDepth', + 'askDepth', + ]) + t.is(depth.symbol, 'TRXUSD_PERP') + t.is(depth.level, 10) + t.truthy(depth.pair) + clean() + resolve() + }, + ) + }) +}) diff --git a/test/websockets/liquidations.js b/test/websockets/liquidations.js new file mode 100644 index 00000000..1815299d --- /dev/null +++ b/test/websockets/liquidations.js @@ -0,0 +1,129 @@ +import test from 'ava' + +import Binance from 'index' + +import { checkFields } from '../utils' + +const client = Binance() + +// Note: Liquidation streams are skipped as they may not always have active liquidations to test + +test.skip('[WS] futuresLiquidations - single symbol', t => { + return new Promise(resolve => { + const clean = client.ws.futuresLiquidations('BTCUSDT', liquidation => { + checkFields(t, liquidation, [ + 'symbol', + 'price', + 'origQty', + 'lastFilledQty', + 'accumulatedQty', + 'averagePrice', + 'status', + 'timeInForce', + 'type', + 'side', + 'time', + ]) + t.is(liquidation.symbol, 'BTCUSDT') + t.truthy(typeof liquidation.price === 'string') + t.truthy(typeof liquidation.origQty === 'string') + clean() + resolve() + }) + }) +}) + +test.skip('[WS] futuresLiquidations - multiple symbols', t => { + return new Promise(resolve => { + const symbols = ['BTCUSDT', 'ETHUSDT'] + + const clean = client.ws.futuresLiquidations(symbols, liquidation => { + checkFields(t, liquidation, [ + 'symbol', + 'price', + 'origQty', + 'lastFilledQty', + 'accumulatedQty', + 'averagePrice', + 'status', + 'timeInForce', + 'type', + 'side', + 'time', + ]) + t.truthy(symbols.includes(liquidation.symbol)) + clean() + resolve() + }) + }) +}) + +test.skip('[WS] futuresLiquidations - raw data', t => { + return new Promise(resolve => { + const clean = client.ws.futuresLiquidations('BTCUSDT', liquidation => { + // Raw data should contain the 'o' object wrapper + t.truthy(liquidation.o) + t.truthy(liquidation.o.s) + t.truthy(liquidation.o.p) + t.truthy(liquidation.o.q) + clean() + resolve() + }, false) + }) +}) + +test.skip('[WS] futuresAllLiquidations', t => { + return new Promise(resolve => { + const clean = client.ws.futuresAllLiquidations(liquidation => { + checkFields(t, liquidation, [ + 'symbol', + 'price', + 'origQty', + 'lastFilledQty', + 'accumulatedQty', + 'averagePrice', + 'status', + 'timeInForce', + 'type', + 'side', + 'time', + ]) + t.truthy(liquidation.symbol) + clean() + resolve() + }) + }) +}) + +test.skip('[WS] futuresAllLiquidations - raw data', t => { + return new Promise(resolve => { + const clean = client.ws.futuresAllLiquidations(liquidation => { + // Raw data should contain the 'o' object wrapper + t.truthy(liquidation.o) + t.truthy(liquidation.o.s) + t.truthy(liquidation.o.p) + clean() + resolve() + }, false) + }) +}) + +test.skip('[WS] futuresAllLiquidations - cleanup function', t => { + const clean = client.ws.futuresAllLiquidations(() => { + // Callback implementation + }) + + // Verify clean is a function + t.is(typeof clean, 'function') + + // Clean up immediately + clean() + + // Give it a moment and verify cleanup executed properly + return new Promise(resolve => { + setTimeout(() => { + t.pass('Cleanup function executed without errors') + resolve() + }, 100) + }) +}) diff --git a/test/websockets/markPrices.js b/test/websockets/markPrices.js new file mode 100644 index 00000000..68487732 --- /dev/null +++ b/test/websockets/markPrices.js @@ -0,0 +1,136 @@ +import test from 'ava' + +import Binance from 'index' + +import { checkFields } from '../utils' + +const client = Binance() + +test('[WS] futuresAllMarkPrices - default speed', t => { + return new Promise(resolve => { + const clean = client.ws.futuresAllMarkPrices({}, markPrices => { + t.truthy(Array.isArray(markPrices)) + t.truthy(markPrices.length > 0) + + const [firstPrice] = markPrices + checkFields(t, firstPrice, [ + 'eventType', + 'eventTime', + 'symbol', + 'markPrice', + 'indexPrice', + 'settlePrice', + 'fundingRate', + 'nextFundingRate', + ]) + + clean() + resolve() + }) + }) +}) + +test('[WS] futuresAllMarkPrices - 1s update speed', t => { + return new Promise(resolve => { + const clean = client.ws.futuresAllMarkPrices({ updateSpeed: '1s' }, markPrices => { + t.truthy(Array.isArray(markPrices)) + t.truthy(markPrices.length > 0) + + const [firstPrice] = markPrices + checkFields(t, firstPrice, [ + 'eventType', + 'eventTime', + 'symbol', + 'markPrice', + 'indexPrice', + 'settlePrice', + 'fundingRate', + 'nextFundingRate', + ]) + + t.is(firstPrice.eventType, 'markPriceUpdate') + + clean() + resolve() + }) + }) +}) + +test.skip('[WS] futuresAllMarkPrices - raw data without transform', t => { + return new Promise(resolve => { + const clean = client.ws.futuresAllMarkPrices({}, markPrices => { + t.truthy(Array.isArray(markPrices)) + t.truthy(markPrices.length > 0) + + // Note: Raw data test - implementation needs verification + clean() + resolve() + }, false) + }) +}) + +test('[WS] futuresAllMarkPrices - transformed data', t => { + return new Promise(resolve => { + const clean = client.ws.futuresAllMarkPrices({}, markPrices => { + t.truthy(Array.isArray(markPrices)) + t.truthy(markPrices.length > 0) + + const [firstPrice] = markPrices + // Transformed data should have camelCase field names + t.truthy(firstPrice.eventType) + t.truthy(firstPrice.eventTime) + t.truthy(firstPrice.symbol) + t.truthy(firstPrice.markPrice) + t.truthy(firstPrice.indexPrice) + t.truthy(firstPrice.settlePrice) + t.truthy(firstPrice.fundingRate) + t.truthy(firstPrice.nextFundingRate) + + // Should NOT have raw field names + t.falsy(firstPrice.e) + t.falsy(firstPrice.E) + + clean() + resolve() + }, true) + }) +}) + +test('[WS] futuresAllMarkPrices - verify data types', t => { + return new Promise(resolve => { + const clean = client.ws.futuresAllMarkPrices({}, markPrices => { + t.truthy(Array.isArray(markPrices)) + t.truthy(markPrices.length > 0) + + const [firstPrice] = markPrices + t.truthy(typeof firstPrice.eventTime === 'number') + t.truthy(typeof firstPrice.symbol === 'string') + t.truthy(typeof firstPrice.markPrice === 'string') + t.truthy(typeof firstPrice.indexPrice === 'string') + t.truthy(typeof firstPrice.fundingRate === 'string') + + clean() + resolve() + }) + }) +}) + +test('[WS] futuresAllMarkPrices - cleanup function', t => { + const clean = client.ws.futuresAllMarkPrices({}, () => { + // Callback implementation + }) + + // Verify clean is a function + t.is(typeof clean, 'function') + + // Clean up immediately + clean() + + // Give it a moment and verify cleanup executed properly + return new Promise(resolve => { + setTimeout(() => { + t.pass('Cleanup function executed without errors') + resolve() + }, 100) + }) +}) diff --git a/test/websockets/ticker.js b/test/websockets/ticker.js new file mode 100644 index 00000000..631c3854 --- /dev/null +++ b/test/websockets/ticker.js @@ -0,0 +1,237 @@ +import test from 'ava' + +import Binance from 'index' + +import { checkFields } from '../utils' + +const client = Binance() + +test('[WS] ticker - single symbol', t => { + return new Promise(resolve => { + const clean = client.ws.ticker('ETHBTC', ticker => { + checkFields(t, ticker, [ + 'eventType', + 'eventTime', + 'symbol', + 'priceChange', + 'priceChangePercent', + 'weightedAvg', + 'prevDayClose', + 'curDayClose', + 'closeTradeQuantity', + 'bestBid', + 'bestBidQnt', + 'bestAsk', + 'bestAskQnt', + 'open', + 'high', + 'low', + 'volume', + 'volumeQuote', + 'openTime', + 'closeTime', + 'firstTradeId', + 'lastTradeId', + 'totalTrades', + ]) + t.is(ticker.symbol, 'ETHBTC') + clean() + resolve() + }) + }) +}) + +test('[WS] ticker - multiple symbols', t => { + return new Promise(resolve => { + let count = 0 + const symbols = ['ETHBTC', 'BTCUSDT', 'BNBBTC'] + const receivedSymbols = {} + + const clean = client.ws.ticker(symbols, ticker => { + checkFields(t, ticker, ['symbol', 'eventType', 'eventTime', 'priceChange', 'open', 'high']) + t.truthy(symbols.includes(ticker.symbol)) + receivedSymbols[ticker.symbol] = true + count++ + + // Once we've received at least one message for each symbol, we're done + if (Object.keys(receivedSymbols).length === symbols.length || count >= 10) { + clean() + resolve() + } + }) + }) +}) + +test('[WS] ticker - raw data without transform', t => { + return new Promise(resolve => { + const clean = client.ws.ticker('ETHBTC', ticker => { + // Raw data should have lowercase field names (e, E, s, p, etc.) + t.truthy(ticker.e) + t.truthy(ticker.E) + t.truthy(ticker.s) + t.is(ticker.s, 'ETHBTC') + clean() + resolve() + }, false) + }) +}) + +test('[WS] ticker - transformed data', t => { + return new Promise(resolve => { + const clean = client.ws.ticker('ETHBTC', ticker => { + // Transformed data should have camelCase field names + t.truthy(ticker.eventType) + t.truthy(ticker.eventTime) + t.truthy(ticker.symbol) + t.is(ticker.eventType, '24hrTicker') + t.is(ticker.symbol, 'ETHBTC') + // Should NOT have raw field names + t.falsy(ticker.e) + t.falsy(ticker.E) + t.falsy(ticker.s) + clean() + resolve() + }, true) + }) +}) + +test('[WS] ticker - cleanup function', t => { + const clean = client.ws.ticker('ETHBTC', () => { + // Callback implementation + }) + + // Verify clean is a function + t.is(typeof clean, 'function') + + // Clean up immediately + clean() + + // Give it a moment and verify cleanup executed properly + return new Promise(resolve => { + setTimeout(() => { + t.pass('Cleanup function executed without errors') + resolve() + }, 100) + }) +}) + +test('[WS] allTickers', t => { + return new Promise(resolve => { + const clean = client.ws.allTickers(tickers => { + t.truthy(Array.isArray(tickers)) + t.is(tickers[0].eventType, '24hrTicker') + checkFields(t, tickers[0], ['symbol', 'priceChange', 'priceChangePercent']) + clean() + resolve() + }) + }) +}) + +test('[WS] miniTicker - single symbol', t => { + return new Promise(resolve => { + const clean = client.ws.miniTicker('ETHBTC', ticker => { + checkFields(t, ticker, [ + 'open', + 'high', + 'low', + 'curDayClose', + 'eventTime', + 'symbol', + 'volume', + 'volumeQuote', + ]) + t.is(ticker.symbol, 'ETHBTC') + clean() + resolve() + }) + }) +}) + +test('[WS] allMiniTickers', t => { + return new Promise(resolve => { + const clean = client.ws.allMiniTickers(tickers => { + t.truthy(Array.isArray(tickers)) + t.is(tickers[0].eventType, '24hrMiniTicker') + checkFields(t, tickers[0], [ + 'open', + 'high', + 'low', + 'curDayClose', + 'eventTime', + 'symbol', + 'volume', + 'volumeQuote', + ]) + clean() + resolve() + }) + }) +}) + +test('[WS] futuresTicker - single symbol', t => { + return new Promise(resolve => { + const clean = client.ws.futuresTicker('BTCUSDT', ticker => { + checkFields(t, ticker, [ + 'eventType', + 'eventTime', + 'symbol', + 'priceChange', + 'priceChangePercent', + 'weightedAvg', + 'curDayClose', + 'closeTradeQuantity', + 'open', + 'high', + 'low', + 'volume', + 'volumeQuote', + 'openTime', + 'closeTime', + 'firstTradeId', + 'lastTradeId', + 'totalTrades', + ]) + t.is(ticker.symbol, 'BTCUSDT') + // Futures ticker should NOT have prevDayClose, bestBid, bestBidQnt, bestAsk, bestAskQnt + t.falsy(ticker.prevDayClose) + t.falsy(ticker.bestBid) + clean() + resolve() + }) + }) +}) + +test.skip('[WS] deliveryTicker - single symbol', t => { + return new Promise(resolve => { + const clean = client.ws.deliveryTicker('TRXUSD_PERP', ticker => { + checkFields(t, ticker, [ + 'eventType', + 'eventTime', + 'symbol', + 'pair', + 'priceChange', + 'priceChangePercent', + 'weightedAvg', + 'curDayClose', + 'closeTradeQuantity', + 'open', + 'high', + 'low', + 'volume', + 'volumeBase', + 'openTime', + 'closeTime', + 'firstTradeId', + 'lastTradeId', + 'totalTrades', + ]) + t.is(ticker.symbol, 'TRXUSD_PERP') + t.truthy(ticker.pair) + // Delivery ticker has volumeBase instead of volumeQuote + t.truthy(ticker.volumeBase) + t.falsy(ticker.volumeQuote) + clean() + resolve() + }) + }) +}) diff --git a/test/websockets/trades.js b/test/websockets/trades.js new file mode 100644 index 00000000..acf1e6f5 --- /dev/null +++ b/test/websockets/trades.js @@ -0,0 +1,162 @@ +import test from 'ava' + +import Binance from 'index' + +import { checkFields } from '../utils' + +const client = Binance() + +test('[WS] trades - single symbol', t => { + return new Promise(resolve => { + const clean = client.ws.trades('ETHBTC', trade => { + checkFields(t, trade, [ + 'eventType', + 'tradeId', + 'tradeTime', + 'quantity', + 'price', + 'symbol', + ]) + t.is(trade.symbol, 'ETHBTC') + clean() + resolve() + }) + }) +}) + +test('[WS] trades - multiple symbols', t => { + return new Promise(resolve => { + const symbols = ['BNBBTC', 'ETHBTC', 'BNTBTC'] + + const clean = client.ws.trades(symbols, trade => { + checkFields(t, trade, [ + 'eventType', + 'tradeId', + 'tradeTime', + 'quantity', + 'price', + 'symbol', + ]) + t.truthy(symbols.includes(trade.symbol)) + clean() + resolve() + }) + }) +}) + +test('[WS] trades - raw data without transform', t => { + return new Promise(resolve => { + const clean = client.ws.trades('ETHBTC', trade => { + // Raw data should have lowercase field names + t.truthy(trade.e) + t.truthy(trade.E) + t.truthy(trade.s) + t.truthy(trade.t) + t.truthy(trade.p) + t.truthy(trade.q) + clean() + resolve() + }, false) + }) +}) + +test('[WS] aggTrades - single symbol', t => { + return new Promise(resolve => { + const clean = client.ws.aggTrades(['ETHBTC'], trade => { + checkFields(t, trade, [ + 'eventType', + 'aggId', + 'timestamp', + 'quantity', + 'price', + 'symbol', + 'firstId', + 'lastId', + ]) + t.is(trade.symbol, 'ETHBTC') + clean() + resolve() + }) + }) +}) + +test('[WS] aggTrades - multiple symbols', t => { + return new Promise(resolve => { + const symbols = ['BNBBTC', 'ETHBTC', 'BNTBTC'] + + const clean = client.ws.aggTrades(symbols, trade => { + checkFields(t, trade, [ + 'eventType', + 'aggId', + 'timestamp', + 'quantity', + 'price', + 'symbol', + 'firstId', + 'lastId', + ]) + t.truthy(symbols.includes(trade.symbol)) + clean() + resolve() + }) + }) +}) + +test('[WS] aggTrades - raw data without transform', t => { + return new Promise(resolve => { + const clean = client.ws.aggTrades('ETHBTC', trade => { + // Raw data should have lowercase field names + t.truthy(trade.e) + t.truthy(trade.E) + t.truthy(trade.s) + t.truthy(trade.a) + t.truthy(trade.p) + t.truthy(trade.q) + clean() + resolve() + }, false) + }) +}) + +test.skip('[WS] futuresAggTrades - single symbol', t => { + return new Promise(resolve => { + const clean = client.ws.futuresAggTrades(['BTCUSDT'], trade => { + checkFields(t, trade, [ + 'eventType', + 'symbol', + 'aggId', + 'price', + 'quantity', + 'firstId', + 'lastId', + 'timestamp', + 'isBuyerMaker', + ]) + t.is(trade.symbol, 'BTCUSDT') + clean() + resolve() + }) + }) +}) + +test.skip('[WS] deliveryAggTrades - single symbol', t => { + return new Promise(resolve => { + const clean = client.ws.deliveryAggTrades('TRXUSD_PERP', trade => { + checkFields(t, trade, [ + 'eventType', + 'eventTime', + 'symbol', + 'aggId', + 'price', + 'quantity', + 'firstId', + 'lastId', + 'timestamp', + 'isBuyerMaker', + ]) + t.is(trade.symbol, 'TRXUSD_PERP') + clean() + resolve() + }) + }) +}) diff --git a/test/websockets/user.js b/test/websockets/user.js new file mode 100644 index 00000000..2c823406 --- /dev/null +++ b/test/websockets/user.js @@ -0,0 +1,331 @@ +import test from 'ava' + +import { userEventHandler } from 'websocket' + +// TODO: add testnet to be able to test private ws endpoints +// Note: User data stream tests require API credentials +// These tests use userEventHandler to test event transformations without needing live connections + +test('[WS] userEvents - outboundAccountInfo', t => { + const accountPayload = { + e: 'outboundAccountInfo', + E: 1499405658849, + m: 0, + t: 0, + b: 0, + s: 0, + T: true, + W: true, + D: true, + u: 1499405658849, + B: [ + { + a: 'LTC', + f: '17366.18538083', + l: '0.00000000', + }, + { + a: 'BTC', + f: '10537.85314051', + l: '2.19464093', + }, + { + a: 'ETH', + f: '17902.35190619', + l: '0.00000000', + }, + { + a: 'BNC', + f: '1114503.29769312', + l: '0.00000000', + }, + { + a: 'NEO', + f: '0.00000000', + l: '0.00000000', + }, + ], + } + + userEventHandler(res => { + t.deepEqual(res, { + eventType: 'account', + eventTime: 1499405658849, + makerCommissionRate: 0, + takerCommissionRate: 0, + buyerCommissionRate: 0, + sellerCommissionRate: 0, + canTrade: true, + canWithdraw: true, + canDeposit: true, + lastAccountUpdate: 1499405658849, + balances: { + LTC: { available: '17366.18538083', locked: '0.00000000' }, + BTC: { available: '10537.85314051', locked: '2.19464093' }, + ETH: { available: '17902.35190619', locked: '0.00000000' }, + BNC: { available: '1114503.29769312', locked: '0.00000000' }, + NEO: { available: '0.00000000', locked: '0.00000000' }, + }, + }) + })({ data: JSON.stringify(accountPayload) }) +}) + +test('[WS] userEvents - executionReport NEW', t => { + const orderPayload = { + e: 'executionReport', + E: 1499405658658, + s: 'ETHBTC', + c: 'mUvoqJxFIILMdfAW5iGSOW', + S: 'BUY', + o: 'LIMIT', + f: 'GTC', + q: '1.00000000', + p: '0.10264410', + P: '0.10285410', + F: '0.00000000', + g: -1, + C: 'null', + x: 'NEW', + X: 'NEW', + r: 'NONE', + i: 4293153, + l: '0.00000000', + z: '0.00000000', + L: '0.00000000', + n: '0', + N: null, + T: 1499405658657, + t: -1, + I: 8641984, + w: true, + m: false, + M: false, + O: 1499405658657, + Q: 0, + Y: 0, + Z: '0.00000000', + } + + userEventHandler(res => { + t.deepEqual(res, { + commission: '0', + commissionAsset: null, + creationTime: 1499405658657, + eventTime: 1499405658658, + eventType: 'executionReport', + executionType: 'NEW', + icebergQuantity: '0.00000000', + isBuyerMaker: false, + isOrderWorking: true, + lastQuoteTransacted: 0, + lastTradeQuantity: '0.00000000', + newClientOrderId: 'mUvoqJxFIILMdfAW5iGSOW', + orderId: 4293153, + orderListId: -1, + orderRejectReason: 'NONE', + orderStatus: 'NEW', + orderTime: 1499405658657, + orderType: 'LIMIT', + originalClientOrderId: 'null', + price: '0.10264410', + priceLastTrade: '0.00000000', + quantity: '1.00000000', + quoteOrderQuantity: 0, + side: 'BUY', + stopPrice: '0.10285410', + symbol: 'ETHBTC', + timeInForce: 'GTC', + totalQuoteTradeQuantity: '0.00000000', + totalTradeQuantity: '0.00000000', + tradeId: -1, + trailingDelta: undefined, + trailingTime: undefined, + }) + })({ data: JSON.stringify(orderPayload) }) +}) + +test('[WS] userEvents - executionReport TRADE', t => { + const tradePayload = { + e: 'executionReport', + E: 1499406026404, + s: 'ETHBTC', + c: '1hRLKJhTRsXy2ilYdSzhkk', + S: 'BUY', + o: 'LIMIT', + f: 'GTC', + q: '22.42906458', + p: '0.10279999', + P: '0.10280001', + F: '0.00000000', + g: -1, + C: 'null', + x: 'TRADE', + X: 'FILLED', + r: 'NONE', + i: 4294220, + l: '17.42906458', + z: '22.42906458', + L: '0.10279999', + n: '0.00000001', + N: 'BNC', + T: 1499406026402, + t: 77517, + I: 8644124, + w: false, + m: false, + M: true, + O: 1499405658657, + Q: 0, + Y: 0, + Z: '2.30570761', + } + + userEventHandler(res => { + t.deepEqual(res, { + eventType: 'executionReport', + eventTime: 1499406026404, + symbol: 'ETHBTC', + newClientOrderId: '1hRLKJhTRsXy2ilYdSzhkk', + originalClientOrderId: 'null', + side: 'BUY', + orderType: 'LIMIT', + timeInForce: 'GTC', + quantity: '22.42906458', + price: '0.10279999', + stopPrice: '0.10280001', + executionType: 'TRADE', + icebergQuantity: '0.00000000', + orderStatus: 'FILLED', + orderRejectReason: 'NONE', + orderId: 4294220, + orderTime: 1499406026402, + lastTradeQuantity: '17.42906458', + totalTradeQuantity: '22.42906458', + priceLastTrade: '0.10279999', + commission: '0.00000001', + commissionAsset: 'BNC', + tradeId: 77517, + isOrderWorking: false, + isBuyerMaker: false, + creationTime: 1499405658657, + totalQuoteTradeQuantity: '2.30570761', + lastQuoteTransacted: 0, + orderListId: -1, + quoteOrderQuantity: 0, + trailingDelta: undefined, + trailingTime: undefined, + }) + })({ data: JSON.stringify(tradePayload) }) +}) + +test('[WS] userEvents - listStatus', t => { + const listStatusPayload = { + e: 'listStatus', + E: 1661588112531, + s: 'TWTUSDT', + g: 73129826, + c: 'OCO', + l: 'EXEC_STARTED', + L: 'EXECUTING', + r: 'NONE', + C: 'Y3ZgLMRPHZFNqEVSZwoJI7', + T: 1661588112530, + O: [ + { + s: 'TWTUSDT', + i: 209259206, + c: 'electron_f675d1bdea454cd4afeac5664be', + }, + { + s: 'TWTUSDT', + i: 209259207, + c: 'electron_38d852a65a89486c98e59879327', + }, + ], + } + + userEventHandler(res => { + t.deepEqual(res, { + eventType: 'listStatus', + eventTime: 1661588112531, + symbol: 'TWTUSDT', + orderListId: 73129826, + contingencyType: 'OCO', + listStatusType: 'EXEC_STARTED', + listOrderStatus: 'EXECUTING', + listRejectReason: 'NONE', + listClientOrderId: 'Y3ZgLMRPHZFNqEVSZwoJI7', + transactionTime: 1661588112530, + orders: [ + { + symbol: 'TWTUSDT', + orderId: 209259206, + clientOrderId: 'electron_f675d1bdea454cd4afeac5664be', + }, + { + symbol: 'TWTUSDT', + orderId: 209259207, + clientOrderId: 'electron_38d852a65a89486c98e59879327', + }, + ], + }) + })({ data: JSON.stringify(listStatusPayload) }) +}) + +test('[WS] userEvents - unknown event type', t => { + const newEvent = { e: 'totallyNewEvent', yolo: 42 } + + userEventHandler(res => { + t.deepEqual(res, { type: 'totallyNewEvent', yolo: 42 }) + })({ data: JSON.stringify(newEvent) }) +}) + +test('[WS] userEvents - balanceUpdate', t => { + const balancePayload = { + e: 'balanceUpdate', + E: 1573200697110, + a: 'BTC', + d: '100.00000000', + T: 1573200697068, + } + + userEventHandler(res => { + t.deepEqual(res, { + eventType: 'balanceUpdate', + eventTime: 1573200697110, + asset: 'BTC', + balanceDelta: '100.00000000', + clearTime: 1573200697068, + }) + })({ data: JSON.stringify(balancePayload) }) +}) + +test('[WS] userEvents - outboundAccountPosition', t => { + const positionPayload = { + e: 'outboundAccountPosition', + E: 1564034571105, + u: 1564034571073, + B: [ + { + a: 'ETH', + f: '10000.000000', + l: '0.000000', + }, + ], + } + + userEventHandler(res => { + t.deepEqual(res, { + eventType: 'outboundAccountPosition', + eventTime: 1564034571105, + lastAccountUpdate: 1564034571073, + balances: [ + { + asset: 'ETH', + free: '10000.000000', + locked: '0.000000', + }, + ], + }) + })({ data: JSON.stringify(positionPayload) }) +})