Add unit tests for prepareTrustline, prepareOrderCancellation, and sign

This commit is contained in:
Chris Clark
2015-06-08 15:48:40 -07:00
parent 9c14fb2379
commit fb7021abcc
22 changed files with 228 additions and 156 deletions

View File

@@ -37,32 +37,27 @@ const AccountRootFlags = {
};
const AccountRootFields = {
Sequence: {name: 'transaction_sequence'},
EmailHash: {name: 'email_hash', encoding: 'hex', length: 32, defaults: '0'},
WalletLocator: {name: 'wallet_locator', encoding: 'hex',
Sequence: {name: 'sequence'},
EmailHash: {name: 'emailHash', encoding: 'hex', length: 32, defaults: '0'},
WalletLocator: {name: 'walletLocator', encoding: 'hex',
length: 64, defaults: '0'},
WalletSize: {name: 'wallet_size', defaults: 0},
MessageKey: {name: 'message_key'},
WalletSize: {name: 'walletSize', defaults: 0},
MessageKey: {name: 'messageKey'},
Domain: {name: 'domain', encoding: 'hex'},
TransferRate: {name: 'transfer_rate', defaults: 0},
TransferRate: {name: 'transferRate', defaults: 0},
Signers: {name: 'signers'}
};
const AccountSetIntFlags = {
NoFreeze: {name: 'no_freeze',
value: ripple.Transaction.set_clear_flags.AccountSet.asfNoFreeze},
GlobalFreeze: {name: 'global_freeze',
value: ripple.Transaction.set_clear_flags.AccountSet.asfGlobalFreeze},
DefaultRipple: {name: 'default_ripple',
value: ripple.Transaction.set_clear_flags.AccountSet.asfDefaultRipple}
noFreeze: ripple.Transaction.set_clear_flags.AccountSet.asfNoFreeze,
globalFreeze: ripple.Transaction.set_clear_flags.AccountSet.asfGlobalFreeze,
defaultRipple: ripple.Transaction.set_clear_flags.AccountSet.asfDefaultRipple
};
const AccountSetFlags = {
RequireDestTag: {name: 'require_destination_tag', set: 'RequireDestTag',
unset: 'OptionalDestTag'},
RequireAuth: {name: 'require_authorization', set: 'RequireAuth',
unset: 'OptionalAuth'},
DisallowXRP: {name: 'disallow_xrp', set: 'DisallowXRP', unset: 'AllowXRP'}
requireDestinationTag: {set: 'RequireDestTag', unset: 'OptionalDestTag'},
requireAuthorization: {set: 'RequireAuth', unset: 'OptionalAuth'},
disallowIncomingXRP: {set: 'DisallowXRP', unset: 'AllowXRP'}
};
const AccountSetResponseFlags = {

View File

@@ -3,10 +3,7 @@
"title": "cancellation",
"type": "object",
"properties": {
"orderSequence": {
"type": "integer",
"minimum": 0
}
"orderSequence": {"$ref": "sequence"}
},
"required": ["orderSequence"],
"additionalProperties": false

View File

@@ -6,8 +6,7 @@
"properties": {
"sequence": {
"description": "The sequence number, relative to the initiating account, of this transaction.",
"type": "integer",
"minimum": 1
"$ref": "sequence"
},
"fee": {
"description": "Fixed Fee",

View File

@@ -3,5 +3,5 @@
"title": "ledgerVersion",
"description": "A ledger version number",
"type": "integer",
"minimum": 0
"minimum": 1
}

View File

@@ -0,0 +1,7 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "sequence",
"description": "An account transaction sequence number",
"type": "integer",
"minimum": 1
}

View File

@@ -3,9 +3,22 @@
"title": "settings",
"type": "object",
"properties": {
"name": {"type": "string"},
"value": {"type": "string"}
"passwordSpent": {"type": "boolean"},
"requireDestinationTag": {"type": "boolean"},
"requireAuthorization": {"type": "boolean"},
"disallowIncomingXRP": {"type": "boolean"},
"disableMasterKey": {"type": "boolean"},
"noFreeze": {"type": "boolean"},
"globalFreeze": {"type": "boolean"},
"defaultRipple": {"type": "boolean"},
"emailHash": {"type": "string"},
"walletLocator": {"$ref": "hash256"},
"walletSize": {"type": "integer"},
"messageKey": {"type": "string"},
"domain": {"type": "string"},
"transferRate": {"type": "integer"},
"signers": {"type": "string"}
},
"required": ["name", "value"],
"minProperties": 1,
"additionalProperties": false
}

View File

@@ -5,5 +5,6 @@
"type": "object",
"properties": {
"Account": {"$ref": "address"}
}
},
"required": ["Account"]
}

View File

@@ -42,7 +42,7 @@ module.exports = {
addressAndSecret: validateAddressAndSecret,
currency: _.partial(schemaValidate, 'currency'),
identifier: _.partial(schemaValidate, 'hash256'),
sequence: _.partial(schemaValidate, 'natural'),
sequence: _.partial(schemaValidate, 'sequence'),
order: _.partial(schemaValidate, 'order'),
orderbook: _.partial(schemaValidate, 'orderbook'),
payment: _.partial(schemaValidate, 'payment'),

View File

@@ -345,13 +345,11 @@ function parseTrustLineResponse(message, meta) {
function parseSettingsResponse(settings, message, meta) {
const _settings = {};
for (let flagName in constants.AccountSetIntFlags) {
const flag = constants.AccountSetIntFlags[flagName];
_settings[flag.name] = settings[flag.name];
_settings[flagName] = settings[flagName];
}
for (let fieldName in constants.AccountRootFields) {
const field = constants.AccountRootFields[fieldName];
_settings[field.name] = settings[field.name];
_settings[fieldName] = settings[fieldName];
}
_.assign(_settings, parseFlagsFromResponse(message.tx_json.Flags,

View File

@@ -17,9 +17,9 @@ function renameCounterpartyToIssuer(amount) {
}
const OfferCreateFlags = {
Passive: {name: 'passive', set: 'Passive'},
ImmediateOrCancel: {name: 'immediateOrCancel', set: 'ImmediateOrCancel'},
FillOrKill: {name: 'fillOrKill', set: 'FillOrKill'}
passive: {set: 'Passive'},
immediateOrCancel: {set: 'ImmediateOrCancel'},
fillOrKill: {set: 'FillOrKill'}
};
function toRippledAmount(amount) {
@@ -37,17 +37,13 @@ function createOrderTransaction(account, order) {
const takerGets = toRippledAmount(order.direction === 'buy' ?
order.totalPrice : order.quantity);
transaction.offerCreate(account, ripple.Amount.from_json(takerPays),
ripple.Amount.from_json(takerGets));
utils.setTransactionBitFlags(transaction, {
input: order,
flags: OfferCreateFlags
});
transaction.offerCreate(account, takerPays, takerGets);
utils.setTransactionBitFlags(transaction, order, OfferCreateFlags);
if (order.direction === 'sell') {
transaction.setFlags('Sell');
}
return transaction;
}

View File

@@ -2,7 +2,6 @@
/* eslint-disable valid-jsdoc */
'use strict';
const _ = require('lodash');
const assert = require('assert');
const utils = require('./utils');
const ripple = utils.common.core;
const validate = utils.common.validate;
@@ -12,26 +11,6 @@ const InvalidRequestError = utils.common.errors.InvalidRequestError;
// Emptry string passed to setting will clear it
const CLEAR_SETTING = '';
/**
* Pad the value of a fixed-length field
*
* @param {String} value
* @param {Number} length
* @return {String}
*/
function padValue(value, length) {
assert.strictEqual(typeof value, 'string');
assert.strictEqual(typeof length, 'number');
let result = value;
while (result.length < length) {
result = '0' + result;
}
return result;
}
/**
* Set integer flags on a transaction based on input and a flag map
*
@@ -43,20 +22,15 @@ function padValue(value, length) {
*
* @returns undefined
*/
function setTransactionIntFlags(transaction, input, flags) {
function setTransactionIntFlags(transaction, values, flags) {
for (let flagName in flags) {
const flag = flags[flagName];
const value = values[flagName];
const code = flags[flagName];
if (!input.hasOwnProperty(flag.name)) {
continue;
}
const value = input[flag.name];
if (value) {
transaction.tx_json.SetFlag = flag.value;
} else {
transaction.tx_json.ClearFlag = flag.value;
if (value === true) {
transaction.tx_json.SetFlag = code;
} else if (value === false) {
transaction.tx_json.ClearFlag = code;
}
}
}
@@ -95,7 +69,7 @@ function setTransactionFields(transaction, input, fieldSchema) {
throw new InvalidRequestError(
'Parameter length exceeded: ' + fieldName);
} else if (value.length < field.length) {
value = padValue(value, field.length);
value = _.padLeft(value, field.length);
}
} else {
// Field is variable length. Expecting an ascii string as input.
@@ -139,16 +113,15 @@ function createSettingsTransaction(account, settings) {
const transaction = new ripple.Transaction();
transaction.accountSet(account);
utils.setTransactionBitFlags(transaction, {
input: settings,
flags: constants.AccountSetFlags,
clear_setting: CLEAR_SETTING
});
utils.setTransactionBitFlags(transaction, settings,
constants.AccountSetFlags);
setTransactionIntFlags(transaction, settings, constants.AccountSetIntFlags);
setTransactionFields(transaction, settings, constants.AccountRootFields);
transaction.tx_json.TransferRate = convertTransferRate(
transaction.tx_json.TransferRate);
if (transaction.tx_json.TransferRate !== undefined) {
transaction.tx_json.TransferRate = convertTransferRate(
transaction.tx_json.TransferRate);
}
return transaction;
}

View File

@@ -5,42 +5,25 @@ const ripple = utils.common.core;
const validate = utils.common.validate;
const TrustSetFlags = {
SetAuth: {name: 'authorized', set: 'SetAuth'},
ClearNoRipple: {name: 'account_allows_rippling', set: 'ClearNoRipple',
unset: 'NoRipple'},
SetFreeze: {name: 'account_trustline_frozen', set: 'SetFreeze',
unset: 'ClearFreeze'}
authorized: {set: 'SetAuth'},
allowRippling: {set: 'ClearNoRipple', unset: 'NoRipple'},
frozed: {set: 'SetFreeze', unset: 'ClearFreeze'}
};
function createTrustLineTransaction(account, trustline) {
validate.address(account);
validate.trustline(trustline);
if (trustline && trustline.limit) {
trustline.limit = String(trustline.limit);
}
const limit = {
currency: trustline.currency,
issuer: trustline.counterparty,
value: trustline.limit
};
const transaction = new ripple.Transaction();
const limit = [
trustline.limit,
trustline.currency,
trustline.counterparty
].join('/');
transaction.trustSet(account, limit);
if (typeof trustline.quality_in === 'number') {
transaction.tx_json.QualityIn = trustline.quality_in;
}
if (typeof trustline.quality_out === 'number') {
transaction.tx_json.QualityOut = trustline.quality_out;
}
utils.setTransactionBitFlags(transaction, {
input: trustline,
flags: TrustSetFlags,
clear_setting: ''
});
transaction.trustSet(account, limit,
trustline.qualityIn, trustline.qualityOut);
utils.setTransactionBitFlags(transaction, trustline, TrustSetFlags);
return transaction;
}

View File

@@ -19,26 +19,17 @@ const common = require('../common');
*
* @returns undefined
*/
/*:: type FlagOptions = {flags: any; input: any; clear_setting?: string} */
function setTransactionBitFlags(transaction: any, options: FlagOptions): void {
for (let flagName in options.flags) {
const flag = options.flags[flagName];
function setTransactionBitFlags(transaction: any, values: any, flags: any):
void {
for (let flagName in flags) {
const flagValue = values[flagName];
const flagConversions = flags[flagName];
// Set transaction flags
if (!(flag.name in options.input)) {
continue;
if (flagValue === true && flagConversions.set !== undefined) {
transaction.setFlags(flagConversions.set);
}
let value = options.input[flag.name];
if (value === options.clear_setting) {
value = false;
}
if (flag.unset) {
transaction.setFlags(value ? flag.set : flag.unset);
} else if (flag.set && value) {
transaction.setFlags(flag.set);
if (flagValue === false && flagConversions.unset !== undefined) {
transaction.setFlags(flagConversions.unset);
}
}
}

View File

@@ -1,51 +1,81 @@
'use strict';
const assert = require('assert');
const _ = require('lodash');
const assert = require('assert-diff');
const setupAPI = require('./setup-api');
const address = require('./fixtures/addresses').ACCOUNT;
const paymentSpecification = require('./fixtures/payment-specification');
const paymentResponse = require('./fixtures/payment-response');
const orderSpecification = require('./fixtures/order-specification');
const orderResponse = require('./fixtures/order-response');
const trustlineSpecification =
require('./fixtures/trustline-specification');
const trustlineResponse = require('./fixtures/trustline-response');
const balancesResponse = require('./fixtures/balances-response');
const orderCancellationResponse =
require('./fixtures/ordercancellation-response');
const settingsSpecification = require('./fixtures/settings-specification');
const settingsResponse = require('./fixtures/settings-response');
const signInput = require('./fixtures/sign-input');
const signOutput = require('./fixtures/sign-output');
const MockPRNG = require('./mock-prng');
const sjcl = require('../src').sjcl;
function checkResult(expected, done, error, response) {
if (error) {
done(error);
return;
}
assert.deepEqual(response, expected);
done();
}
function withDeterministicPRNG(f) {
const prng = sjcl.random;
sjcl.random = new MockPRNG();
f();
sjcl.random = prng;
}
describe('RippleAPI', function() {
const instructions = {maxLedgerVersionOffset: 100};
beforeEach(setupAPI.setup);
afterEach(setupAPI.teardown);
it('preparePayment', function(done) {
const instructions = {maxLedgerVersionOffset: 100};
this.api.preparePayment(address, paymentSpecification, instructions,
(error, response) => {
if (error) {
done(error);
return;
}
assert.deepEqual(response, paymentResponse);
done();
});
_.partial(checkResult, paymentResponse, done));
});
it('prepareOrder', function(done) {
const instructions = {maxLedgerVersionOffset: 100};
this.api.prepareOrder(address, orderSpecification, instructions,
(error, response) => {
if (error) {
done(error);
return;
}
assert.deepEqual(response, orderResponse);
done();
_.partial(checkResult, orderResponse, done));
});
it('prepareOrderCancellation', function(done) {
this.api.prepareOrderCancellation(address, 23, instructions,
_.partial(checkResult, orderCancellationResponse, done));
});
it('prepareTrustline', function(done) {
this.api.prepareTrustline(address, trustlineSpecification,
instructions, _.partial(checkResult, trustlineResponse, done));
});
it('prepareSettings', function(done) {
this.api.prepareSettings(address, settingsSpecification,
instructions, _.partial(checkResult, settingsResponse, done));
});
it('sign', function() {
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
withDeterministicPRNG(() => {
const result = this.api.sign(signInput, secret);
assert.deepEqual(result, signOutput);
});
});
it('getBalances', function(done) {
this.api.getBalances(address, {}, (error, response) => {
if (error) {
done(error);
return;
}
assert.deepEqual(response, balancesResponse);
done();
});
this.api.getBalances(address, {},
_.partial(checkResult, balancesResponse, done));
});
});

View File

@@ -0,0 +1,9 @@
{
"Flags": 0,
"TransactionType": "OfferCancel",
"Account": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"OfferSequence": 23,
"LastLedgerSequence": 8820052,
"Fee": "12",
"Sequence": 23
}

11
test/fixtures/settings-response.json vendored Normal file
View File

@@ -0,0 +1,11 @@
{
"Flags": 1048576,
"TransactionType": "AccountSet",
"Account": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"SetFlag": 6,
"EmailHash": "98B4375E1D753E5B91627516F6D70977",
"Domain": "726970706C652E636F6D",
"LastLedgerSequence": 8820052,
"Fee": "12",
"Sequence": 23
}

View File

@@ -0,0 +1,6 @@
{
"domain": "ripple.com",
"emailHash": "98B4375E1D753E5B91627516F6D70977",
"noFreeze": true,
"disallowIncomingXRP": true
}

9
test/fixtures/sign-input.json vendored Normal file
View File

@@ -0,0 +1,9 @@
{
"Flags": 0,
"TransactionType": "AccountSet",
"Account": "r3GgMwvgvP8h4yVWvjH1dPZNvC37TjzBBE",
"Domain": "726970706C652E636F6D",
"LastLedgerSequence": 8820052,
"Fee": "12",
"Sequence": 23
}

4
test/fixtures/sign-output.json vendored Normal file
View File

@@ -0,0 +1,4 @@
{
"tx_blob": "12000322000000002400000017201B0086955468400000000000000C732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D87446304402207660BDEF67105CE1EBA9AD35DC7156BAB43FF1D47633199EE257D70B6B9AAFBF02207F5517BC8AEF2ADC1325897ECDBA8C673838048BCA62F4E98B252F19BE88796D770A726970706C652E636F6D81144FBFF73DA4ECF9B701940F27341FA8020C313443",
"hash": "DB44C111583A95AF973A0B0A40348D90512FCBCDDCA3315A286D2BF4FAC100F1"
}

15
test/fixtures/trustline-response.json vendored Normal file
View File

@@ -0,0 +1,15 @@
{
"Flags": 131072,
"TransactionType": "TrustSet",
"Account": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"LimitAmount": {
"value": "10000",
"currency": "USD",
"issuer": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"
},
"QualityIn": 500000000,
"QualityOut": 500000000,
"LastLedgerSequence": 8820052,
"Fee": "12",
"Sequence": 23
}

View File

@@ -0,0 +1,9 @@
{
"currency": "USD",
"counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM",
"limit": "10000",
"qualityIn": 500000000,
"qualityOut": 500000000,
"allowRippling": false,
"frozen": false
}

26
test/mock-prng.js Normal file
View File

@@ -0,0 +1,26 @@
'use strict';
const _ = require('lodash');
const SEED =
'3045022100A58B0460BC5092CB4F96155C19125A4E079C870663F1D5E8BBC9BD0';
function MockPRNG(seed) {
if (seed && seed.length < 8) {
throw new Error('seed must be a hex string of at least 8 characters');
}
this.position = 0;
this.seed = seed || SEED;
}
MockPRNG.prototype.randomWord = function() {
const i = this.position;
this.position = (i + 8) % this.seed.length;
const data = this.seed + this.seed.slice(8);
return parseInt(data.slice(i, i + 8), 16);
};
MockPRNG.prototype.randomWords = function(n) {
return _.times(n, () => this.randomWord());
};
module.exports = MockPRNG;