divvy-rest
Version:
A RESTful API for submitting payments and monitoring accounts on the Divvy network.
1,539 lines (1,332 loc) • 62 kB
JavaScript
/* eslint-disable new-cap */
/* eslint-disable max-len */
'use strict';
var assert = require('assert');
var divvy = require('divvy-lib');
var testutils = require('./testutils');
var fixtures = require('./fixtures').orders;
var errors = require('./fixtures').errors;
var addresses = require('./fixtures').addresses;
var Currency = require('divvy-lib').Currency;
var HEX_CURRENCY = '0158415500000000C1F76FF6ECB0BAC600000000';
var ISSUER = 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B';
var VALUE = '0.00000001';
var DEFAULT_LIMIT = 200;
var LIMIT = 100;
var MARKER = '29F992CC252056BF690107D1E8F2D9FBAFF29FF107B62B1D1F4E4E11ADF2CC73';
var NEXT_MARKER =
'0C812C919D343EAE789B29E8027C62C5792C22172D37EA2B2C0121D2381F80E1';
var LEDGER = 9592219;
var LEDGER_HASH =
'FD22E2A8D665A01711C0147173ECC0A32466BA976DE697E95197933311267BE8';
suite('get orders', function() {
var self = this;
setup(testutils.setup.bind(self));
teardown(testutils.teardown.bind(self));
test('/accounts/:account/orders', function(done) {
self.wss.once('request_account_offers', function(message, conn) {
assert.strictEqual(message.command, 'account_offers');
assert.strictEqual(message.account, addresses.VALID);
assert.strictEqual(message.ledger_index, 'validated');
assert.strictEqual(message.limit, DEFAULT_LIMIT);
conn.send(fixtures.accountOrdersResponse(message, {
ledger: LEDGER,
marker: NEXT_MARKER,
limit: DEFAULT_LIMIT
}));
});
self.app
.get('/v1/accounts/' + addresses.VALID + '/orders')
.expect(testutils.checkStatus(200))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(fixtures.RESTAccountOrdersResponse({
ledger: LEDGER,
marker: NEXT_MARKER,
limit: DEFAULT_LIMIT
})))
.end(done);
});
test('/accounts/:account/orders -- with limit=all', function(done) {
self.wss.on('request_account_offers', function(message, conn) {
if (message.ledger_index === 'validated') {
assert.strictEqual(message.command, 'account_offers');
assert.strictEqual(message.account, addresses.VALID);
assert.strictEqual(message.ledger_index, 'validated');
assert.strictEqual(message.marker, undefined);
assert.notEqual(message.limit, 'all');
conn.send(fixtures.accountOrdersResponse(message, {
ledger: LEDGER,
marker: NEXT_MARKER
}));
} else {
assert.strictEqual(message.command, 'account_offers');
assert.strictEqual(message.account, addresses.VALID);
assert.strictEqual(message.ledger_index, LEDGER);
assert.strictEqual(message.marker, NEXT_MARKER);
assert.notEqual(message.limit, 'all');
conn.send(fixtures.accountOrdersResponse(message, {
ledger: LEDGER,
marker: undefined
}));
}
});
self.app
.get('/v1/accounts/' + addresses.VALID + '/orders?limit=all')
.expect(testutils.checkStatus(200))
.expect(testutils.checkHeaders)
.end(function(err, res) {
if (err) {
return done(err);
}
assert.strictEqual(res.body.orders.length, 34);
assert.strictEqual(res.body.limit, undefined);
assert.strictEqual(res.body.marker, undefined);
assert.strictEqual(res.body.ledger, LEDGER);
assert.strictEqual(res.body.validated, true);
done();
});
});
test('/accounts/:account/orders -- with invalid ledger', function(done) {
self.wss.once('request_account_offers', function(message, conn) {
assert.strictEqual(message.command, 'account_offers');
assert.strictEqual(message.account, addresses.VALID);
assert.strictEqual(message.ledger_index, 'validated');
conn.send(fixtures.accountOrdersResponse(message));
});
self.app
.get('/v1/accounts/' + addresses.VALID + '/orders?ledger=foo')
.expect(testutils.checkStatus(400))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.restInvalidParameter('ledger')))
.end(done);
});
test('/accounts/:account/orders -- with ledger (sequence)', function(done) {
self.wss.once('request_account_offers', function(message, conn) {
assert.strictEqual(message.command, 'account_offers');
assert.strictEqual(message.account, addresses.VALID);
assert.strictEqual(message.ledger_index, LEDGER);
conn.send(fixtures.accountOrdersResponse(message, {
marker: NEXT_MARKER,
ledger: LEDGER
}));
});
self.app
.get('/v1/accounts/' + addresses.VALID + '/orders?ledger=' + LEDGER)
.expect(testutils.checkStatus(200))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(fixtures.RESTAccountOrdersResponse({
marker: NEXT_MARKER,
ledger: LEDGER
})))
.end(done);
});
test('/accounts/:account/orders -- with ledger (hash)', function(done) {
self.wss.once('request_account_offers', function(message, conn) {
assert.strictEqual(message.command, 'account_offers');
assert.strictEqual(message.account, addresses.VALID);
assert.strictEqual(message.ledger_hash, LEDGER_HASH);
conn.send(fixtures.accountOrdersResponse(message, {
marker: NEXT_MARKER,
ledger: LEDGER
}));
});
self.app
.get('/v1/accounts/' + addresses.VALID + '/orders?ledger=' + LEDGER_HASH)
.expect(testutils.checkStatus(200))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(fixtures.RESTAccountOrdersResponse({
marker: NEXT_MARKER,
ledger: LEDGER
})))
.end(done);
});
test('/accounts/:account/orders -- with valid marker and invalid limit', function(done) {
self.wss.once('request_account_offers', function() {
assert(false);
});
self.app
.get('/v1/accounts/' + addresses.VALID + '/orders?marker=' + MARKER + '&limit=foo')
.expect(testutils.checkStatus(400))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.restInvalidParameter('limit')))
.end(done);
});
test('/accounts/:account/orders -- with valid marker, valid limit, missing ledger', function(done) {
self.wss.once('request_account_offers', function() {
assert(false);
});
self.app
.get('/v1/accounts/' + addresses.VALID + '/orders?marker=' + MARKER + '&limit=' + LIMIT)
.expect(testutils.checkStatus(400))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.restInvalidParameter('ledger')))
.end(done);
});
test('/accounts/:account/orders -- with valid marker and valid ledger', function(done) {
self.wss.once('request_account_offers', function(message, conn) {
assert.strictEqual(message.command, 'account_offers');
assert.strictEqual(message.account, addresses.VALID);
assert.strictEqual(message.ledger_index, LEDGER);
assert.strictEqual(message.marker, MARKER);
conn.send(fixtures.accountOrdersResponse(message, {
marker: NEXT_MARKER,
ledger: LEDGER
}));
});
self.app
.get('/v1/accounts/' + addresses.VALID + '/orders?marker=' + MARKER + '&ledger=' + LEDGER)
.expect(testutils.checkStatus(200))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(fixtures.RESTAccountOrdersResponse({
marker: NEXT_MARKER,
ledger: LEDGER
})))
.end(done);
});
test('/accounts/:account/orders -- valid ledger and valid limit', function(done) {
self.wss.once('request_account_offers', function(message, conn) {
assert.strictEqual(message.command, 'account_offers');
assert.strictEqual(message.account, addresses.VALID);
assert.strictEqual(message.ledger_index, 9592219);
assert.strictEqual(message.limit, LIMIT);
conn.send(fixtures.accountOrdersResponse(message, {
marker: NEXT_MARKER,
ledger: LEDGER
}));
});
self.app
.get('/v1/accounts/' + addresses.VALID + '/orders?ledger=' + LEDGER + '&limit=' + LIMIT)
.expect(testutils.checkStatus(200))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(fixtures.RESTAccountOrdersResponse({
marker: NEXT_MARKER,
ledger: LEDGER
})))
.end(done);
});
test('/accounts/:account/orders -- with valid marker, valid limit, and invalid ledger', function(done) {
self.wss.once('request_account_offers', function() {
assert(false);
});
self.app
.get('/v1/accounts/' + addresses.VALID + '/orders?marker=' + MARKER + '&limit=' + LIMIT + '&ledger=foo')
.expect(testutils.checkStatus(400))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.restInvalidParameter('ledger')))
.end(done);
});
test('/accounts/:account/orders -- with valid marker, valid limit, and ledger=validated', function(done) {
self.wss.once('request_account_offers', function() {
assert(false);
});
self.app
.get('/v1/accounts/' + addresses.VALID + '/orders?marker=' + MARKER + '&limit=' + LIMIT + '&ledger=validated')
.expect(testutils.checkStatus(400))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.restInvalidParameter('ledger')))
.end(done);
});
test('/accounts/:account/orders -- with valid marker, valid limit, and ledger=current', function(done) {
self.wss.once('request_account_offers', function() {
assert(false);
});
self.app
.get('/v1/accounts/' + addresses.VALID + '/orders?marker=' + MARKER + '&limit=' + LIMIT + '&ledger=current')
.expect(testutils.checkStatus(400))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.restInvalidParameter('ledger')))
.end(done);
});
test('/accounts/:account/orders -- with valid marker, valid limit, and ledger=closed', function(done) {
self.wss.once('request_account_offers', function() {
assert(false);
});
self.app
.get('/v1/accounts/' + addresses.VALID + '/orders?marker=' + MARKER + '&limit=' + LIMIT + '&ledger=closed')
.expect(testutils.checkStatus(400))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.restInvalidParameter('ledger')))
.end(done);
});
test('/accounts/:account/orders -- with valid marker, valid limit, and valid ledger', function(done) {
self.wss.once('request_account_offers', function(message, conn) {
assert.strictEqual(message.command, 'account_offers');
assert.strictEqual(message.account, addresses.VALID);
assert.strictEqual(message.ledger_index, 9592219);
assert.strictEqual(message.limit, LIMIT);
assert.strictEqual(message.marker, MARKER);
conn.send(fixtures.accountOrdersResponse(message, {
marker: NEXT_MARKER
}));
});
self.app
.get('/v1/accounts/' + addresses.VALID + '/orders?marker=' + MARKER + '&limit=' + LIMIT + '&ledger=' + LEDGER)
.expect(testutils.checkStatus(200))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(fixtures.RESTAccountOrdersResponse({
marker: NEXT_MARKER
})))
.end(done);
});
test('/accounts/:account/orders -- invalid account', function(done) {
self.wss.once('request_account_offers', function() {
assert(false, 'Should not request account lines');
});
self.app
.get('/v1/accounts/' + addresses.INVALID + '/orders')
.expect(testutils.checkStatus(400))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.RESTInvalidAccount))
.end(done);
});
});
suite('post orders', function() {
var self = this;
setup(testutils.setup.bind(self));
teardown(testutils.teardown.bind(self));
test('/orders?validated=true', function(done) {
var lastLedger = self.remote._ledger_current_index;
var hash = testutils.generateHash();
self.wss.once('request_account_info', function(message, conn) {
assert.strictEqual(message.command, 'account_info');
assert.strictEqual(message.account, addresses.VALID);
conn.send(fixtures.accountInfoResponse(message));
});
self.wss.once('request_submit', function(message, conn) {
assert.strictEqual(message.command, 'submit');
conn.send(fixtures.requestSubmitResponse(message, {
hash: hash
}));
process.nextTick(function() {
conn.send(fixtures.submitTransactionVerifiedResponse({
hash: hash
}));
});
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders?validated=true')
.send(fixtures.order())
.expect(testutils.checkStatus(200))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(fixtures.RESTSubmitTransactionResponse({
state: 'validated',
hash: hash,
last_ledger: lastLedger
})))
.end(done);
});
test('/orders?validated=true -- unfunded offer', function(done) {
var hash = testutils.generateHash();
self.wss.once('request_account_info', function(message, conn) {
assert.strictEqual(message.command, 'account_info');
assert.strictEqual(message.account, addresses.VALID);
conn.send(fixtures.accountInfoResponse(message));
});
self.wss.once('request_submit', function(message, conn) {
assert.strictEqual(message.command, 'submit');
conn.send(fixtures.divvydSubmitErrorResponse(message, {
engine_result: 'tecUNFUNDED_OFFER',
engine_result_code: 103,
engine_result_message: 'Insufficient balance to fund created offer.',
hash: hash
}));
process.nextTick(function() {
conn.send(fixtures.unfundedOrderFinalizedResponse({
hash: hash
}));
});
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders?validated=true')
.send(fixtures.order())
.expect(testutils.checkStatus(500))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.RESTErrorResponse({
type: 'transaction',
error: 'tecUNFUNDED_OFFER',
message: 'Insufficient balance to fund created offer.'
})))
.end(done);
});
test('/orders', function(done) {
var lastLedger = self.remote._ledger_current_index;
var hash = testutils.generateHash();
self.wss.once('request_account_info', function(message, conn) {
assert.strictEqual(message.command, 'account_info');
assert.strictEqual(message.account, addresses.VALID);
conn.send(fixtures.accountInfoResponse(message));
});
self.wss.once('request_submit', function(message, conn) {
var so = new divvy.SerializedObject(message.tx_blob).to_json();
assert.strictEqual(message.command, 'submit');
assert.strictEqual(so.Account, addresses.VALID);
assert.strictEqual(so.TransactionType, 'OfferCreate');
assert.deepEqual(so.TakerPays, {
value: '100',
currency: 'USD',
issuer: addresses.ISSUER
});
assert.deepEqual(so.TakerGets, {
value: '100',
currency: 'USD',
issuer: addresses.ISSUER
});
conn.send(fixtures.requestSubmitResponse(message, {
hash: hash
}));
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders')
.send(fixtures.order())
.expect(testutils.checkStatus(200))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(fixtures.RESTSubmitTransactionResponse({
hash: hash,
last_ledger: lastLedger
})))
.end(done);
});
test('/orders -- taker_gets -- hex currency', function(done) {
var lastLedger = self.remote._ledger_current_index;
var hash = testutils.generateHash();
var options = {
hash: hash,
last_ledger: lastLedger,
taker_gets: {
currency: HEX_CURRENCY,
value: VALUE,
issuer: ISSUER
}
};
self.wss.once('request_account_info', function(message, conn) {
assert.strictEqual(message.command, 'account_info');
assert.strictEqual(message.account, addresses.VALID);
conn.send(fixtures.accountInfoResponse(message));
});
self.wss.once('request_submit', function(message, conn) {
var so = new divvy.SerializedObject(message.tx_blob).to_json();
assert.strictEqual(so.TakerGets.value, VALUE);
assert.strictEqual(so.TakerGets.currency, HEX_CURRENCY);
assert.strictEqual(so.TakerGets.issuer, ISSUER);
assert.strictEqual(message.command, 'submit');
conn.send(fixtures.requestSubmitResponse(message, options));
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders')
.send(fixtures.order({
taker_gets: {
currency: HEX_CURRENCY,
value: VALUE,
counterparty: ISSUER
}
}))
.expect(testutils.checkStatus(200))
.expect(testutils.checkHeaders)
.end(function(err, res) {
if (err) {
return done(err);
}
assert.strictEqual(res.body.order.taker_gets.currency, HEX_CURRENCY);
assert.strictEqual(res.body.order.taker_gets.value, VALUE);
assert.strictEqual(res.body.order.taker_gets.counterparty, ISSUER);
done();
});
});
test('/orders -- taker_pays -- hex currency', function(done) {
var lastLedger = self.remote._ledger_current_index;
var hash = testutils.generateHash();
var options = {
hash: hash,
last_ledger: lastLedger,
taker_pays: {
currency: HEX_CURRENCY,
value: VALUE,
issuer: ISSUER
}
};
self.wss.once('request_account_info', function(message, conn) {
assert.strictEqual(message.command, 'account_info');
assert.strictEqual(message.account, addresses.VALID);
conn.send(fixtures.accountInfoResponse(message));
});
self.wss.once('request_submit', function(message, conn) {
var so = new divvy.SerializedObject(message.tx_blob).to_json();
assert.strictEqual(so.TakerPays.value, VALUE);
assert.strictEqual(so.TakerPays.currency, HEX_CURRENCY);
assert.strictEqual(so.TakerPays.issuer, ISSUER);
assert.strictEqual(message.command, 'submit');
conn.send(fixtures.requestSubmitResponse(message, options));
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders')
.send(fixtures.order({
taker_pays: {
currency: HEX_CURRENCY,
counterparty: ISSUER,
value: VALUE
}
}))
.expect(testutils.checkStatus(200))
.expect(testutils.checkHeaders)
.end(function(err, res) {
if (err) {
return done(err);
}
assert.strictEqual(res.body.order.taker_pays.currency, HEX_CURRENCY);
assert.strictEqual(res.body.order.taker_pays.value, VALUE);
assert.strictEqual(res.body.order.taker_pays.counterparty, ISSUER);
done();
});
});
test('/orders -- ledger sequence too high', function(done) {
self.wss.once('request_account_info', function(message, conn) {
assert.strictEqual(message.command, 'account_info');
assert.strictEqual(message.account, addresses.VALID);
conn.send(fixtures.accountInfoResponse(message));
});
self.wss.once('request_submit', function(message, conn) {
assert.strictEqual(message.command, 'submit');
conn.send(fixtures.ledgerSequenceTooHighResponse(message));
testutils.closeLedgers(conn);
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders')
.send(fixtures.order())
.expect(testutils.checkBody(errors.RESTResponseLedgerSequenceTooHigh))
.expect(testutils.checkStatus(500))
.expect(testutils.checkHeaders)
.end(done);
});
test('/orders -- secret invalid', function(done) {
self.wss.once('request_account_info', function(message, conn) {
assert.strictEqual(message.command, 'account_info');
assert.strictEqual(message.account, addresses.VALID);
conn.send(fixtures.accountInfoResponse(message));
});
self.wss.once('request_submit', function() {
assert(false);
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders')
.send(fixtures.order({
secret: 'foo'
}))
.expect(testutils.checkStatus(400))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.RESTRequestInvalidSecret))
.end(done);
});
test('/orders -- type sell', function(done) {
var lastLedger = self.remote._ledger_current_index;
var hash = testutils.generateHash();
self.wss.once('request_account_info', function(message, conn) {
assert.strictEqual(message.command, 'account_info');
assert.strictEqual(message.account, addresses.VALID);
conn.send(fixtures.accountInfoResponse(message));
});
self.wss.once('request_submit', function(message, conn) {
var so = new divvy.SerializedObject(message.tx_blob).to_json();
assert.strictEqual(message.command, 'submit');
assert((so.Flags & divvy.Transaction.flags.OfferCreate.Sell) > 0);
conn.send(fixtures.requestSubmitResponse(message, {
hash: hash
}));
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders')
.send(fixtures.order({
type: 'sell'
}))
.expect(testutils.checkStatus(200))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(fixtures.RESTSubmitTransactionResponse({
hash: hash,
last_ledger: lastLedger
})))
.end(done);
});
test('/orders -- passive true', function(done) {
var lastLedger = self.remote._ledger_current_index;
var hash = testutils.generateHash();
self.wss.once('request_account_info', function(message, conn) {
assert.strictEqual(message.command, 'account_info');
assert.strictEqual(message.account, addresses.VALID);
conn.send(fixtures.accountInfoResponse(message));
});
self.wss.once('request_submit', function(message, conn) {
var so = new divvy.SerializedObject(message.tx_blob).to_json();
assert.strictEqual(message.command, 'submit');
assert((so.Flags & divvy.Transaction.flags.OfferCreate.Passive) > 0);
conn.send(fixtures.requestSubmitResponse(message, {
hash: hash
}));
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders')
.send(fixtures.order({
passive: true
}))
.expect(testutils.checkStatus(200))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(fixtures.RESTSubmitTransactionResponse({
hash: hash,
last_ledger: lastLedger
})))
.end(done);
});
test('/orders -- fill_or_kill true', function(done) {
var lastLedger = self.remote._ledger_current_index;
var hash = testutils.generateHash();
self.wss.once('request_account_info', function(message, conn) {
assert.strictEqual(message.command, 'account_info');
assert.strictEqual(message.account, addresses.VALID);
conn.send(fixtures.accountInfoResponse(message));
});
self.wss.once('request_submit', function(message, conn) {
var so = new divvy.SerializedObject(message.tx_blob).to_json();
assert.strictEqual(message.command, 'submit');
assert((so.Flags & divvy.Transaction.flags.OfferCreate.FillOrKill) > 0);
conn.send(fixtures.requestSubmitResponse(message, {
hash: hash
}));
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders')
.send(fixtures.order({
fill_or_kill: true
}))
.expect(testutils.checkStatus(200))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(fixtures.RESTSubmitTransactionResponse({
hash: hash,
last_ledger: lastLedger
})))
.end(done);
});
test('/orders -- immediate_or_cancel true', function(done) {
var lastLedger = self.remote._ledger_current_index;
var hash = testutils.generateHash();
self.wss.once('request_account_info', function(message, conn) {
assert.strictEqual(message.command, 'account_info');
assert.strictEqual(message.account, addresses.VALID);
conn.send(fixtures.accountInfoResponse(message));
});
self.wss.once('request_submit', function(message, conn) {
var so = new divvy.SerializedObject(message.tx_blob).to_json();
assert.strictEqual(message.command, 'submit');
assert((so.Flags & divvy.Transaction.flags.OfferCreate.ImmediateOrCancel) > 0);
conn.send(fixtures.requestSubmitResponse(message, {
hash: hash
}));
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders')
.send(fixtures.order({
immediate_or_cancel: true
}))
.expect(testutils.checkStatus(200))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(fixtures.RESTSubmitTransactionResponse({
hash: hash,
last_ledger: lastLedger
})))
.end(done);
});
test('/orders -- passive false', function(done) {
var lastLedger = self.remote._ledger_current_index;
var hash = testutils.generateHash();
self.wss.once('request_account_info', function(message, conn) {
assert.strictEqual(message.command, 'account_info');
assert.strictEqual(message.account, addresses.VALID);
conn.send(fixtures.accountInfoResponse(message));
});
self.wss.once('request_submit', function(message, conn) {
var so = new divvy.SerializedObject(message.tx_blob).to_json();
assert.strictEqual(message.command, 'submit');
assert.strictEqual(so.Flags & divvy.Transaction.flags.OfferCreate.Passive, 0);
conn.send(fixtures.requestSubmitResponse(message, {
hash: hash
}));
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders')
.send(fixtures.order({
passive: false
}))
.expect(testutils.checkStatus(200))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(fixtures.RESTSubmitTransactionResponse({
hash: hash,
last_ledger: lastLedger
})))
.end(done);
});
test('/orders -- fill_or_kill false', function(done) {
var lastLedger = self.remote._ledger_current_index;
var hash = testutils.generateHash();
self.wss.once('request_account_info', function(message, conn) {
assert.strictEqual(message.command, 'account_info');
assert.strictEqual(message.account, addresses.VALID);
conn.send(fixtures.accountInfoResponse(message));
});
self.wss.once('request_submit', function(message, conn) {
var so = new divvy.SerializedObject(message.tx_blob).to_json();
assert.strictEqual(message.command, 'submit');
assert.strictEqual(so.Flags & divvy.Transaction.flags.OfferCreate.FillOrKill, 0);
conn.send(fixtures.requestSubmitResponse(message, {
hash: hash
}));
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders')
.send(fixtures.order({
fill_or_kill: false
}))
.expect(testutils.checkStatus(200))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(fixtures.RESTSubmitTransactionResponse({
hash: hash,
last_ledger: lastLedger
})))
.end(done);
});
test('/orders -- immediate_or_cancel false', function(done) {
var lastLedger = self.remote._ledger_current_index;
var hash = testutils.generateHash();
self.wss.once('request_account_info', function(message, conn) {
assert.strictEqual(message.command, 'account_info');
assert.strictEqual(message.account, addresses.VALID);
conn.send(fixtures.accountInfoResponse(message));
});
self.wss.once('request_submit', function(message, conn) {
var so = new divvy.SerializedObject(message.tx_blob).to_json();
assert.strictEqual(message.command, 'submit');
assert.strictEqual(so.Flags & divvy.Transaction.flags.OfferCreate.ImmediateOrCancel, 0);
conn.send(fixtures.requestSubmitResponse(message, {
hash: hash
}));
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders')
.send(fixtures.order({
immediate_or_cancel: false
}))
.expect(testutils.checkStatus(200))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(fixtures.RESTSubmitTransactionResponse({
hash: hash,
last_ledger: lastLedger
})))
.end(done);
});
test('/orders -- passive invalid', function(done) {
self.wss.once('request_account_info', function() {
assert(false, 'should not request account info');
});
self.wss.once('request_submit', function() {
assert(false, 'should not submit request');
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders')
.send(fixtures.order({
passive: 'test'
}))
.expect(testutils.checkStatus(400))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.RESTErrorResponse({
type: 'invalid_request',
error: 'restINVALID_PARAMETER',
message: 'Parameter must be a boolean: passive'
})))
.end(done);
});
test('/orders -- immediate_or_cancel invalid', function(done) {
self.wss.once('request_account_info', function() {
assert(false, 'should not request account info');
});
self.wss.once('request_submit', function() {
assert(false, 'should not submit request');
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders')
.send(fixtures.order({
immediate_or_cancel: 'test'
}))
.expect(testutils.checkStatus(400))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.RESTErrorResponse({
type: 'invalid_request',
error: 'restINVALID_PARAMETER',
message: 'Parameter must be a boolean: immediate_or_cancel'
})))
.end(done);
});
test('/orders -- fill_or_kill invalid', function(done) {
self.wss.once('request_account_info', function() {
assert(false, 'should not request account info');
});
self.wss.once('request_submit', function() {
assert(false, 'should not submit request');
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders')
.send(fixtures.order({
fill_or_kill: 'test'
}))
.expect(testutils.checkStatus(400))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.RESTErrorResponse({
type: 'invalid_request',
error: 'restINVALID_PARAMETER',
message: 'Parameter must be a boolean: fill_or_kill'
})))
.end(done);
});
test('/orders -- taker_gets -- xdv', function(done) {
var lastLedger = self.remote._ledger_current_index;
var hash = testutils.generateHash();
var options = {
hash: hash,
taker_gets: '100000000000'
};
self.wss.once('request_account_info', function(message, conn) {
assert.strictEqual(message.command, 'account_info');
assert.strictEqual(message.account, addresses.VALID);
conn.send(fixtures.accountInfoResponse(message));
});
self.wss.once('request_submit', function(message, conn) {
var so = new divvy.SerializedObject(message.tx_blob).to_json();
assert.strictEqual(message.command, 'submit');
assert.strictEqual(so.Account, addresses.VALID);
assert.strictEqual(so.TransactionType, 'OfferCreate');
assert.deepEqual(so.TakerGets, options.taker_gets);
conn.send(fixtures.requestSubmitResponse(message, options));
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders')
.send(fixtures.order({
taker_gets: {
currency: 'XDV',
value: '100000',
counterparty: ''
}
}))
.expect(testutils.checkBody(fixtures.RESTSubmitTransactionResponse({
hash: hash,
last_ledger: lastLedger,
taker_gets: {
currency: 'XDV',
counterparty: '',
value: '100000'
}
})))
.expect(testutils.checkStatus(200))
.expect(testutils.checkHeaders)
.end(done);
});
test('/orders -- taker_pays -- xdv', function(done) {
var lastLedger = self.remote._ledger_current_index;
var hash = testutils.generateHash();
var options = {
hash: hash,
taker_pays: '100000000000'
};
self.wss.once('request_account_info', function(message, conn) {
assert.strictEqual(message.command, 'account_info');
assert.strictEqual(message.account, addresses.VALID);
conn.send(fixtures.accountInfoResponse(message));
});
self.wss.once('request_submit', function(message, conn) {
var so = new divvy.SerializedObject(message.tx_blob).to_json();
assert.strictEqual(message.command, 'submit');
assert.strictEqual(so.Account, addresses.VALID);
assert.strictEqual(so.TransactionType, 'OfferCreate');
assert.deepEqual(so.TakerPays, options.taker_pays);
conn.send(fixtures.requestSubmitResponse(message, options));
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders')
.send(fixtures.order({
taker_pays: {
currency: 'XDV',
value: '100000'
}
}))
.expect(testutils.checkStatus(200))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(fixtures.RESTSubmitTransactionResponse({
hash: hash,
last_ledger: lastLedger,
taker_pays: {
currency: 'XDV',
counterparty: '',
value: '100000'
}
})))
.end(done);
});
test('/orders -- unfunded offer', function(done) {
var lastLedger = self.remote._ledger_current_index;
var hash = testutils.generateHash();
self.wss.once('request_account_info', function(message, conn) {
assert.strictEqual(message.command, 'account_info');
assert.strictEqual(message.account, addresses.VALID);
conn.send(fixtures.accountInfoResponse(message));
});
self.wss.once('request_submit', function(message, conn) {
assert.strictEqual(message.command, 'submit');
conn.send(fixtures.divvydSubmitErrorResponse(message, {
engine_result: 'tecUNFUNDED_OFFER',
engine_result_code: 103,
engine_result_message: 'Insufficient balance to fund created offer.',
hash: hash
}));
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders')
.send(fixtures.order())
.expect(testutils.checkStatus(200))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(fixtures.RESTSubmitTransactionResponse({
hash: hash,
last_ledger: lastLedger
})))
.end(done);
});
test('/orders -- secret missing', function(done) {
self.wss.once('request_account_info', function() {
assert(false, 'should not request account info');
});
self.wss.once('request_submit', function() {
assert(false, 'should not submit request');
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders')
.send({})
.expect(testutils.checkStatus(400))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.RESTMissingSecret))
.end(done);
});
test('/orders -- secret invalid', function(done) {
self.wss.once('request_account_info', function(message, conn) {
assert.strictEqual(message.command, 'account_info');
assert.strictEqual(message.account, addresses.VALID);
conn.send(fixtures.accountInfoResponse(message));
});
self.wss.once('request_submit', function() {
assert(false, 'should not submit request');
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders')
.send(fixtures.order({
secret: 'foo'
}))
.expect(testutils.checkStatus(400))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.RESTRequestInvalidSecret))
.end(done);
});
test('/orders -- order missing', function(done) {
self.wss.once('request_account_info', function() {
assert(false, 'should not request account info');
});
self.wss.once('request_submit', function() {
assert(false, 'should not submit request');
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders')
.send({
secret: addresses.SECRET
})
.expect(testutils.checkStatus(400))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.RESTErrorResponse({
type: 'invalid_request',
error: 'restINVALID_PARAMETER',
message: 'Parameter missing: order'
})))
.end(done);
});
test('/orders -- type missing', function(done) {
self.wss.once('request_account_info', function() {
assert(false, 'should not request account info');
});
self.wss.once('request_submit', function() {
assert(false, 'should not submit request');
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders')
.send(fixtures.order({
type: 'test'
}))
.expect(testutils.checkStatus(400))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.RESTErrorResponse({
type: 'invalid_request',
error: 'restINVALID_PARAMETER',
message: 'Parameter must be "buy" or "sell": type'
})))
.end(done);
});
test('/orders -- taker_gets invalid', function(done) {
self.wss.once('request_account_info', function() {
assert(false, 'should not request account info');
});
self.wss.once('request_submit', function() {
assert(false, 'should not submit request');
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders')
.send(fixtures.order({
taker_gets: 'test'
}))
.expect(testutils.checkStatus(400))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.RESTErrorResponse({
type: 'invalid_request',
error: 'restINVALID_PARAMETER',
message: 'Parameter must be a valid Amount object: taker_gets'
})))
.end(done);
});
test('/orders -- taker_gets -- currency without issuer', function(done) {
self.wss.once('request_account_info', function() {
assert(false, 'should not request account info');
});
self.wss.once('request_submit', function() {
assert(false, 'should not submit request');
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders')
.send(fixtures.order({
taker_gets: {
currency: 'USD',
value: '100'
}
}))
.expect(testutils.checkStatus(400))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.RESTErrorResponse({
type: 'invalid_request',
error: 'restINVALID_PARAMETER',
message: 'Parameter must be a valid Amount object: taker_gets'
})))
.end(done);
});
test('/orders -- taker_pays invalid', function(done) {
self.wss.once('request_account_info', function() {
assert(false, 'should not request account info');
});
self.wss.once('request_submit', function() {
assert(false, 'should not submit request');
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders')
.send(fixtures.order({
taker_pays: 'test'
}))
.expect(testutils.checkStatus(400))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.RESTErrorResponse({
type: 'invalid_request',
error: 'restINVALID_PARAMETER',
message: 'Parameter must be a valid Amount object: taker_pays'
})))
.end(done);
});
test('/orders -- taker_pays -- currency without issuer', function(done) {
self.wss.once('request_account_info', function() {
assert(false, 'should not request account info');
});
self.wss.once('request_submit', function() {
assert(false, 'should not submit request');
});
self.app
.post('/v1/accounts/' + addresses.VALID + '/orders')
.send(fixtures.order({
taker_pays: {
currency: 'USD',
value: '100'
}
}))
.expect(testutils.checkStatus(400))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.RESTErrorResponse({
type: 'invalid_request',
error: 'restINVALID_PARAMETER',
message: 'Parameter must be a valid Amount object: taker_pays'
})))
.end(done);
});
test('/orders -- account invalid', function(done) {
self.wss.once('request_account_info', function() {
assert(false, 'should not request account info');
});
self.wss.once('request_submit', function() {
assert(false, 'should not submit request');
});
self.app
.post('/v1/accounts/' + addresses.INVALID + '/orders?validated=true')
.send(fixtures.order())
.expect(testutils.checkStatus(400))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.RESTInvalidAccount))
.end(done);
});
});
suite('delete orders', function() {
var self = this;
setup(testutils.setup.bind(self));
teardown(testutils.teardown.bind(self));
test('/orders/:sequence?validated=true', function(done) {
var lastLedger = self.remote._ledger_current_index;
var hash = testutils.generateHash();
self.wss.once('request_account_info', function(message, conn) {
assert.strictEqual(message.command, 'account_info');
assert.strictEqual(message.account, addresses.VALID);
conn.send(fixtures.accountInfoResponse(message));
});
self.wss.once('request_submit', function(message, conn) {
var so = new divvy.SerializedObject(message.tx_blob).to_json();
assert.strictEqual(message.command, 'submit');
assert.strictEqual(so.TransactionType, 'OfferCancel');
assert.strictEqual(so.OfferSequence, 99);
conn.send(fixtures.requestCancelResponse(message, {
hash: hash
}));
process.nextTick(function() {
conn.send(fixtures.cancelTransactionVerifiedResponse({
hash: hash
}));
});
});
self.app
.del('/v1/accounts/' + addresses.VALID + '/orders/99?validated=true')
.send({
secret: addresses.SECRET
})
.expect(testutils.checkStatus(200))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(fixtures.RESTCancelTransactionResponse({
state: 'validated',
hash: hash,
last_ledger: lastLedger
})))
.end(done);
});
test('/orders/:sequence -- ledger sequence too high', function(done) {
self.wss.once('request_account_info', function(message, conn) {
assert.strictEqual(message.command, 'account_info');
assert.strictEqual(message.account, addresses.VALID);
conn.send(fixtures.accountInfoResponse(message));
});
self.wss.once('request_submit', function(message, conn) {
assert.strictEqual(message.command, 'submit');
conn.send(fixtures.ledgerSequenceTooHighResponse(message));
testutils.closeLedgers(conn);
});
self.app
.del('/v1/accounts/' + addresses.VALID + '/orders/99')
.send({
secret: addresses.SECRET
})
.expect(testutils.checkBody(errors.RESTResponseLedgerSequenceTooHigh))
.expect(testutils.checkStatus(500))
.expect(testutils.checkHeaders)
.end(done);
});
test('/orders/:sequence -- secret invalid', function(done) {
self.wss.once('request_account_info', function(message, conn) {
assert.strictEqual(message.command, 'account_info');
assert.strictEqual(message.account, addresses.VALID);
conn.send(fixtures.accountInfoResponse(message));
});
self.wss.once('request_submit', function() {
assert(false);
});
self.app
.del('/v1/accounts/' + addresses.VALID + '/orders/99')
.send(fixtures.order({
secret: 'foo'
}))
.expect(testutils.checkStatus(500))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.RESTInvalidSecret))
.end(done);
});
test('/orders/:sequence', function(done) {
var lastLedger = self.remote._ledger_current_index;
var hash = testutils.generateHash();
self.wss.once('request_account_info', function(message, conn) {
assert.strictEqual(message.command, 'account_info');
assert.strictEqual(message.account, addresses.VALID);
conn.send(fixtures.accountInfoResponse(message));
});
self.wss.once('request_submit', function(message, conn) {
var so = new divvy.SerializedObject(message.tx_blob).to_json();
assert.strictEqual(message.command, 'submit');
assert.strictEqual(so.TransactionType, 'OfferCancel');
assert.strictEqual(so.OfferSequence, 99);
conn.send(fixtures.requestCancelResponse(message, {
hash: hash
}));
});
self.app
.del('/v1/accounts/' + addresses.VALID + '/orders/99')
.send({
secret: addresses.SECRET
})
.expect(testutils.checkStatus(200))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(fixtures.RESTCancelTransactionResponse({
hash: hash,
last_ledger: lastLedger
})))
.end(done);
});
test('/orders/:sequence -- bad sequence', function(done) {
var hash = testutils.generateHash();
self.wss.once('request_account_info', function(message, conn) {
assert.strictEqual(message.command, 'account_info');
assert.strictEqual(message.account, addresses.VALID);
conn.send(fixtures.accountInfoResponse(message));
});
self.wss.once('request_submit', function(message, conn) {
var so = new divvy.SerializedObject(message.tx_blob).to_json();
assert.strictEqual(message.command, 'submit');
assert.strictEqual(so.TransactionType, 'OfferCancel');
assert.strictEqual(so.OfferSequence, 99);
conn.send(fixtures.divvydCancelErrorResponse(message, {
engine_result: 'temBAD_SEQUENCE',
engine_result_code: -283,
engine_result_message: 'Malformed: Sequence is not in the past.',
hash: hash
}));
});
self.app
.del('/v1/accounts/' + addresses.VALID + '/orders/99')
.send({
secret: addresses.SECRET
})
.expect(testutils.checkStatus(500))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.RESTErrorResponse({
type: 'transaction',
error: 'temBAD_SEQUENCE',
message: 'Malformed: Sequence is not in the past.'
})))
.end(done);
});
test('/orders/:sequence -- sequence invalid', function(done) {
self.wss.once('request_account_info', function() {
assert(false, 'should not request account info');
});
self.wss.once('request_submit', function() {
assert(false, 'should not submit request');
});
self.app
.del('/v1/accounts/' + addresses.VALID + '/orders/foo')
.send({
secret: addresses.SECRET
})
.expect(testutils.checkStatus(400))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.RESTErrorResponse({
type: 'invalid_request',
error: 'restINVALID_PARAMETER',
message: 'Invalid parameter: sequence. Sequence must be a positive number'
})))
.end(done);
});
test('/orders/:sequence -- secret missing', function(done) {
self.wss.once('request_account_info', function() {
assert(false, 'should not request account info');
});
self.wss.once('request_submit', function() {
assert(false, 'should not submit request');
});
self.app
.del('/v1/accounts/' + addresses.VALID + '/orders/99')
.send({})
.expect(testutils.checkStatus(400))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.RESTMissingSecret))
.end(done);
});
test('/orders/:sequence -- secret invalid', function(done) {
self.wss.once('request_account_info', function(message, conn) {
assert.strictEqual(message.command, 'account_info');
assert.strictEqual(message.account, addresses.VALID);
conn.send(fixtures.accountInfoResponse(message));
});
self.wss.once('request_submit', function() {
assert(false, 'should not submit request');
});
self.app
.del('/v1/accounts/' + addresses.VALID + '/orders/99')
.send({
secret: 'foo'
})
.expect(testutils.checkStatus(500))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.RESTInvalidSecret))
.end(done);
});
test('/orders/:sequence -- account invalid', function(done) {
self.wss.once('request_account_info', function() {
assert(false, 'should not request account info');
});
self.wss.once('request_submit', function() {
assert(false, 'should not submit request');
});
self.app
.del('/v1/accounts/' + addresses.INVALID + '/orders/99?validated=true')
.send({
secret: addresses.SECRET
})
.expect(testutils.checkStatus(400))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(errors.RESTInvalidAccount))
.end(done);
});
});
suite('get order book', function() {
var self = this;
setup(testutils.setup.bind(self));
teardown(testutils.teardown.bind(self));
test('v1/accounts/:account/order_book/:base/:counter', function(done) {
self.wss.on('request_ledger', function(message, conn) {
assert.strictEqual(message.ledger_index, 'validated');
conn.send(fixtures.requestLedgerResponse(message, {
ledger: LEDGER
}));
});
self.wss.on('request_book_offers', function(message, conn) {
assert.strictEqual(message.command, 'book_offers');
assert.strictEqual(message.ledger_index, LEDGER);
assert.strictEqual(message.taker, addresses.VALID);
if (message.taker_gets.currency === Currency.from_human('USD').to_hex()) {
// Bids
conn.send(fixtures.requestBookOffersBidsResponse(message));
} else {
// Asks
conn.send(fixtures.requestBookOffersAsksResponse(message));
}
});
self.app
.get('/v1/accounts/' + addresses.VALID + '/order_book/BTC+' + addresses.ISSUER + '/USD+' + addresses.ISSUER)
.expect(testutils.checkStatus(200))
.expect(testutils.checkHeaders)
.expect(testutils.checkBody(fixtures.RESTOrderBookResponse({
ledger: LEDGER
})))
.end(done);
});
test('v1/accounts/:account/order_book/:base/:counter -- with partially funded ask', function(done) {
self.wss.on('request_ledger', function(message, conn) {
assert.strictEqual(message.ledger_index, 'validated');
conn.send(fixtures.requestLedgerResponse(message, {
ledger: LEDGER
}));
});