Skip to content

Commit

Permalink
Convert getOrders and add unit test
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Clark committed Jun 25, 2015
1 parent 3960b4e commit 84bc7dd
Show file tree
Hide file tree
Showing 14 changed files with 662 additions and 147 deletions.
4 changes: 2 additions & 2 deletions src/api/index.js
Expand Up @@ -10,7 +10,7 @@ const getAccountTransactions = require('./ledger/transactions');
const getTrustlines = require('./ledger/trustlines');
const getBalances = require('./ledger/balances');
// const getPathFind = require('./ledger/pathfind');
// const getOrders = require('./ledger/orders');
const getOrders = require('./ledger/orders');
// const getOrderBook = require('./ledger/orderbook');
const getSettings = require('./ledger/settings');
const preparePayment = require('./transaction/payment');
Expand Down Expand Up @@ -38,7 +38,7 @@ RippleAPI.prototype = {
getTrustlines,
getBalances,
// getPathFind,
// getOrders,
getOrders,
// getOrderBook,
getSettings,

Expand Down
136 changes: 27 additions & 109 deletions src/api/ledger/orders.js
@@ -1,119 +1,37 @@
/* eslint-disable valid-jsdoc */
'use strict';
const _ = require('lodash');
const utils = require('./utils');
const ripple = utils.common.core;
const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync;
const parseAccountOrder = require('./parse/account-order');

function requestAccountOffers(remote, address, ledgerVersion, options,
marker, limit, callback) {
remote.requestAccountOffers({
account: address,
marker: marker,
limit: limit,
ledger: ledgerVersion
},
composeAsync((data) => ({
marker: data.marker,
results: data.offers.map(parseAccountOrder)
}), callback));
}

const DefaultPageLimit = 200;

/**
* Get orders from the ripple network
*
* @query
* @param {String} [request.query.limit]
* - Set a limit to the number of results returned
* @param {String} [request.query.marker]
* - Used to paginate results
* @param {String} [request.query.ledger]
* - The ledger index to query against
* - (required if request.query.marker is present)
*
* @url
* @param {RippleAddress} request.params.account
* - The ripple address to query orders
*
*/
function getOrders(account, options, callback) {
const self = this;

function getAccountOrders(account, options, callback) {
validate.address(account);
validate.options(options);

function getAccountOrders(prevResult) {
const isAggregate = options.limit === undefined;
if (prevResult && (!isAggregate || !prevResult.marker)) {
return Promise.resolve(prevResult);
}

const promise = new Promise(function(resolve, reject) {
let accountOrdersRequest;
let marker;
let ledger;
let limit;

if (prevResult) {
marker = prevResult.marker;
limit = prevResult.limit;
ledger = prevResult.ledger_index;
} else {
marker = options.marker;
limit = options.limit || DefaultPageLimit;
ledger = utils.parseLedger(options.ledger);
}

accountOrdersRequest = self.remote.requestAccountOffers({
account: account,
marker: marker,
limit: limit,
ledger: ledger
});

accountOrdersRequest.once('error', reject);
accountOrdersRequest.once('success', function(nextResult) {
nextResult.offers = prevResult ?
nextResult.offers.concat(prevResult.offers) : nextResult.offers;
resolve(nextResult);
});
accountOrdersRequest.request();
});

return promise.then(getAccountOrders);
}

function getParsedOrders(offers) {
return _.reduce(offers, function(orders, off) {
const sequence = off.seq;
const type = off.flags & ripple.Remote.flags.offer.Sell ? 'sell' : 'buy';
const passive = (off.flags & ripple.Remote.flags.offer.Passive) !== 0;

const taker_gets = utils.parseCurrencyAmount(off.taker_gets);
const taker_pays = utils.parseCurrencyAmount(off.taker_pays);

orders.push({
type: type,
taker_gets: taker_gets,
taker_pays: taker_pays,
sequence: sequence,
passive: passive
});

return orders;
}, []);
}

function respondWithOrders(result) {
const promise = new Promise(function(resolve) {
const orders = {};

if (result.marker) {
orders.marker = result.marker;
}

orders.limit = result.limit;
orders.ledger = result.ledger_index;
orders.validated = result.validated;
orders.orders = getParsedOrders(result.offers);

resolve(callback(null, orders));
});

return promise;
}

getAccountOrders()
.then(respondWithOrders)
.catch(callback);
const defaultLimit = 100;
const limit = options.limit || defaultLimit;
const ledgerVersion = options.ledgerVersion
|| this.remote.getLedgerSequence();
const getter = _.partial(requestAccountOffers, this.remote, account,
ledgerVersion, options);
utils.getRecursive(getter, limit,
composeAsync((orders) => _.sortBy(orders, (order) => order.state.sequence),
callback));
}

module.exports = getOrders;
module.exports = getAccountOrders;
14 changes: 14 additions & 0 deletions src/api/ledger/parse/account-order.js
@@ -0,0 +1,14 @@
'use strict';
const parseOrderBase = require('./order-base');

// rippled 'account_offers' returns a different format for orders than 'tx'
function parseAccountOrder(order) {
const specification = parseOrderBase(
order.taker_gets, order.taker_pays, order.flags);
const state = {
sequence: order.seq
};
return {specification, state};
}

module.exports = parseAccountOrder;
27 changes: 27 additions & 0 deletions src/api/ledger/parse/order-base.js
@@ -0,0 +1,27 @@
/* @flow */
'use strict';
const utils = require('./utils');
const parseAmount = require('./amount');
const orderFlags = utils.core.Transaction.flags.OfferCreate;

/*:: type Amount = string | {currency: string, issuer: string, value: string} */
function parseOrder(takerGets: Amount, takerPays: Amount, flags: number):
Object {
const direction = (flags & orderFlags.Sell) === 0 ? 'buy' : 'sell';
const takerGetsAmount = parseAmount(takerGets);
const takerPaysAmount = parseAmount(takerPays);
const quantity = (direction === 'buy') ? takerPaysAmount : takerGetsAmount;
const totalPrice = (direction === 'buy') ? takerGetsAmount : takerPaysAmount;

return utils.removeUndefined({
direction: direction,
quantity: quantity,
totalPrice: totalPrice,
passive: ((flags & orderFlags.Passive) !== 0) || undefined,
immediateOrCancel: ((flags & orderFlags.ImmediateOrCancel) !== 0)
|| undefined,
fillOrKill: ((flags & orderFlags.FillOrKill) !== 0) || undefined
});
}

module.exports = parseOrder;
20 changes: 2 additions & 18 deletions src/api/ledger/parse/order.js
@@ -1,27 +1,11 @@
/* @flow */
'use strict';
const assert = require('assert');
const utils = require('./utils');
const parseAmount = require('./amount');
const flags = utils.core.Transaction.flags.OfferCreate;
const parseOrderBase = require('./order-base');

function parseOrder(tx: Object): Object {
assert(tx.TransactionType === 'OfferCreate');

const direction = (tx.Flags & flags.Sell) === 0 ? 'buy' : 'sell';
const takerGets = parseAmount(tx.TakerGets);
const takerPays = parseAmount(tx.TakerPays);
const quantity = (direction === 'buy') ? takerPays : takerGets;
const totalPrice = (direction === 'buy') ? takerGets : takerPays;

return {
direction: direction,
quantity: quantity,
totalPrice: totalPrice,
passive: (tx.Flags & flags.Passive) !== 0,
immediateOrCancel: (tx.Flags & flags.ImmediateOrCancel) !== 0,
fillOrKill: (tx.Flags & flags.FillOrKill) !== 0
};
return parseOrderBase(tx.TakerGets, tx.TakerPays, tx.Flags);
}

module.exports = parseOrder;
10 changes: 5 additions & 5 deletions src/api/ledger/parse/payment.js
Expand Up @@ -35,15 +35,15 @@ function parsePayment(tx: Object): Object {
tag: tx.DestinationTag
};

return {
return utils.removeUndefined({
source: utils.removeUndefined(source),
destination: utils.removeUndefined(destination),
memos: parsePaymentMemos(tx),
invoiceID: tx.InvoiceID,
paths: JSON.stringify(tx.Paths || []),
allowPartialPayment: isPartialPayment(tx),
noDirectRipple: isNoDirectRipple(tx)
};
paths: tx.Paths ? JSON.stringify(tx.Paths) : undefined,
allowPartialPayment: isPartialPayment(tx) || undefined,
noDirectRipple: isNoDirectRipple(tx) || undefined
});
}

module.exports = parsePayment;
5 changes: 3 additions & 2 deletions src/api/ledger/parse/transaction.js
Expand Up @@ -20,7 +20,7 @@ function parseTransactionType(type) {
return mapping[type] || null;
}

function parseTransaction(tx: Object): ?Object {
function parseTransaction(tx: Object): Object {
const type = parseTransactionType(tx.TransactionType);
const mapping = {
'payment': parsePayment,
Expand All @@ -32,11 +32,12 @@ function parseTransaction(tx: Object): ?Object {
const parser = mapping[type];
assert(parser !== undefined, 'Unrecognized transaction type');
const specification = parser(tx);
const outcome = utils.parseOutcome(tx);
return utils.removeUndefined({
type: type,
address: tx.Account,
specification: utils.removeUndefined(specification),
outcome: utils.removeUndefined(utils.parseOutcome(tx))
outcome: outcome ? utils.removeUndefined(outcome) : undefined
});
}

Expand Down
4 changes: 2 additions & 2 deletions src/api/ledger/parse/utils.js
Expand Up @@ -9,8 +9,8 @@ function parseTimestamp(tx: {date: string}): string | void {
return tx.date ? (new Date(toTimestamp(tx.date))).toISOString() : undefined;
}

function removeUndefined(obj: ?Object): ?Object {
return obj ? _.omit(obj, _.isUndefined) : obj;
function removeUndefined(obj: Object): Object {
return _.omit(obj, _.isUndefined);
}

function removeEmptyCounterparty(amount) {
Expand Down
6 changes: 6 additions & 0 deletions test/api-test.js
Expand Up @@ -27,6 +27,7 @@ const accountTransactionsResponse =
const trustlinesResponse = require('./fixtures/trustlines-response');
const walletResponse = require('./fixtures/wallet.json');
const getSettingsResponse = require('./fixtures/get-settings-response');
const getOrdersResponse = require('./fixtures/get-orders-response');

function checkResult(expected, done, error, response) {
if (error) {
Expand Down Expand Up @@ -131,4 +132,9 @@ describe('RippleAPI', function() {
_.partial(checkResult, getSettingsResponse, done));
});

it('getOrders', function(done) {
this.api.getOrders(address, {},
_.partial(checkResult, getOrdersResponse, done));
});

});
8 changes: 2 additions & 6 deletions test/fixtures/account-transactions-response.json
Expand Up @@ -18,9 +18,7 @@
"counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"
}
},
"paths": "[[{\"currency\":\"USD\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"type\":48,\"type_hex\":\"0000000000000030\"},{\"account\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"currency\":\"USD\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"type\":49,\"type_hex\":\"0000000000000031\"}]]",
"allowPartialPayment": false,
"noDirectRipple": false
"paths": "[[{\"currency\":\"USD\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"type\":48,\"type_hex\":\"0000000000000030\"},{\"account\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"currency\":\"USD\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"type\":49,\"type_hex\":\"0000000000000031\"}]]"
},
"outcome": {
"result": "tesSUCCESS",
Expand Down Expand Up @@ -104,9 +102,7 @@
"counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"
}
},
"paths": "[[{\"currency\":\"USD\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"type\":48,\"type_hex\":\"0000000000000030\"},{\"account\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"currency\":\"USD\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"type\":49,\"type_hex\":\"0000000000000031\"}]]",
"allowPartialPayment": false,
"noDirectRipple": false
"paths": "[[{\"currency\":\"USD\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"type\":48,\"type_hex\":\"0000000000000030\"},{\"account\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"currency\":\"USD\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"type\":49,\"type_hex\":\"0000000000000031\"}]]"
},
"outcome": {
"result": "tesSUCCESS",
Expand Down

0 comments on commit 84bc7dd

Please sign in to comment.