From ab694381d53f251d6d9bf97bdb18d7ec36028cd8 Mon Sep 17 00:00:00 2001 From: Ivan Tivonenko Date: Thu, 23 Jul 2015 06:14:01 +0300 Subject: [PATCH 1/6] fix some api functions to work with parsed version of transaction --- src/api/ledger/transactions.js | 3 ++- src/api/ledger/utils.js | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/api/ledger/transactions.js b/src/api/ledger/transactions.js index f7b89874..ab5a1d93 100644 --- a/src/api/ledger/transactions.js +++ b/src/api/ledger/transactions.js @@ -1,4 +1,5 @@ /* @flow */ +/* eslint-disable max-params */ 'use strict'; const _ = require('lodash'); const utils = require('./utils'); @@ -28,7 +29,7 @@ function transactionFilter(address, filters, tx) { return false; } if (filters.counterparty && tx.address !== filters.counterparty - && tx.Destination !== filters.counterparty) { + && tx.specification.destination.address !== filters.counterparty) { return false; } return true; diff --git a/src/api/ledger/utils.js b/src/api/ledger/utils.js index 83aa20c4..cf4614b5 100644 --- a/src/api/ledger/utils.js +++ b/src/api/ledger/utils.js @@ -71,10 +71,12 @@ function signum(num) { */ function compareTransactions(first, second) { - if (first.ledgerVersion === second.ledgerVersion) { - return signum(Number(first.indexInLedger) - Number(second.indexInLedger)); + if (first.outcome.ledgerVersion === second.outcome.ledgerVersion) { + return signum(Number(first.outcome.indexInLedger) - + Number(second.outcome.indexInLedger)); } - return Number(first.ledgerVersion) < Number(second.ledgerVersion) ? -1 : 1; + return Number(first.outcome.ledgerVersion) < + Number(second.outcome.ledgerVersion) ? -1 : 1; } function hasCompleteLedgerRange(remote, minLedgerVersion, maxLedgerVersion) { From 068bda0c9545cff51bc15496398271bfecda5cad Mon Sep 17 00:00:00 2001 From: Ivan Tivonenko Date: Thu, 23 Jul 2015 06:24:05 +0300 Subject: [PATCH 2/6] cover api/ledger/transactions.js with unit tests --- test/api-test.js | 79 +++++++++++++++++++ .../api/responses/get-transactions.json | 4 +- test/fixtures/api/rippled/account-tx.js | 32 +++++++- 3 files changed, 109 insertions(+), 6 deletions(-) diff --git a/test/api-test.js b/test/api-test.js index 4d38f2cd..aec7cdce 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -11,6 +11,8 @@ const hashes = require('./fixtures/hashes'); const MockPRNG = require('./mock-prng'); const sjcl = require('../src').sjcl; const address = addresses.ACCOUNT; +const RippleError = require('../src/core/rippleerror').RippleError; +const utils = require('../src/api/ledger/utils'); const schemaValidate = require('../src/api/common/schema-validator'); const orderbook = { @@ -209,6 +211,83 @@ describe('RippleAPI', function() { 'getTransactions', done)); }); + it('getTransactions - earliest first', function(done) { + const options = {types: ['payment', 'order'], initiated: true, limit: 2, + earliestFirst: true + }; + const expected = _.cloneDeep(responses.getTransactions) + .sort(utils.compareTransactions); + this.api.getTransactions(address, options, + _.partial(checkResult, expected, done)); + }); + + it('getTransactions - earliest first with start option', function(done) { + const options = {types: ['payment', 'order'], initiated: true, limit: 2, + start: hashes.VALID_TRANSACTION_HASH, + earliestFirst: true + }; + this.api.getTransactions(address, options, (error, data) => { + assert.strictEqual(data.length, 0); + done(error); + }); + }); + + it('getTransactions - gap', function(done) { + const options = {types: ['payment', 'order'], initiated: true, limit: 2, + maxLedgerVersion: 348858000 + }; + this.api.getTransactions(address, options, (error) => { + assert.ok(error instanceof this.errors.MissingLedgerHistoryError); + done(); + }); + }); + + it('getTransactions - tx not found', function(done) { + const options = {types: ['payment', 'order'], initiated: true, limit: 2, + start: hashes.NOTFOUND_TRANSACTION_HASH, + counterparty: address + }; + this.api.getTransactions(address, options, (error) => { + assert.ok(error instanceof RippleError); + assert.strictEqual(error.remote.error, 'txnNotFound'); + done(); + }); + }); + + it('getTransactions - filters', function(done) { + const options = {types: ['payment', 'order'], initiated: true, limit: 10, + excludeFailures: true, + counterparty: addresses.ISSUER + }; + this.api.getTransactions(address, options, (error, data) => { + assert.strictEqual(data.length, 10); + assert.ok(_.every(data, t => t.type === 'payment' || t.type === 'order')); + assert.ok(_.every(data, t => t.outcome.result === 'tesSUCCESS')); + done(); + }); + }); + + it('getTransactions - filters for incoming', function(done) { + const options = {types: ['payment', 'order'], initiated: false, limit: 10, + excludeFailures: true, + counterparty: addresses.ISSUER + }; + this.api.getTransactions(address, options, (error, data) => { + assert.strictEqual(data.length, 10); + assert.ok(_.every(data, t => t.type === 'payment' || t.type === 'order')); + assert.ok(_.every(data, t => t.outcome.result === 'tesSUCCESS')); + done(); + }); + }); + + it('getTransactions - error', function(done) { + const options = {types: ['payment', 'order'], initiated: true, limit: 13}; + this.api.getTransactions(address, options, (error) => { + assert.ok(error instanceof RippleError); + done(); + }); + }); + // TODO: this doesn't test much, just that it doesn't crash it('getTransactions with start option', function(done) { const options = { diff --git a/test/fixtures/api/responses/get-transactions.json b/test/fixtures/api/responses/get-transactions.json index e7b50041..ff36fa81 100644 --- a/test/fixtures/api/responses/get-transactions.json +++ b/test/fixtures/api/responses/get-transactions.json @@ -86,7 +86,7 @@ } ] }, - "ledgerVersion": 348860, + "ledgerVersion": 348859, "indexInLedger": 0 } }, @@ -177,7 +177,7 @@ } ] }, - "ledgerVersion": 348860, + "ledgerVersion": 348858, "indexInLedger": 0 } } diff --git a/test/fixtures/api/rippled/account-tx.js b/test/fixtures/api/rippled/account-tx.js index fc38aec1..55fa9fc6 100644 --- a/test/fixtures/api/rippled/account-tx.js +++ b/test/fixtures/api/rippled/account-tx.js @@ -4,6 +4,8 @@ const _ = require('lodash'); const hashes = require('../../hashes'); const addresses = require('../../addresses'); const SerializedObject = require('../../../../src/core').SerializedObject; +const AccountSet = require('./tx/account-set.json'); +const NotFound = require('./tx/not-found.json'); module.exports = function(request, options={}) { _.defaults(options, { @@ -17,7 +19,7 @@ module.exports = function(request, options={}) { validated: true }); - const tx = { + let tx = { Account: addresses.ACCOUNT, Amount: { currency: 'USD', @@ -52,7 +54,7 @@ module.exports = function(request, options={}) { TxnSignature: '304502204EE3E9D1B01D8959B08450FCA9E22025AF503DEF310E34A93863A85CAB3C0BC5022100B61F5B567F77026E8DEED89EED0B7CAF0E6C96C228A2A65216F0DC2D04D52083' }; - const meta = { + let meta = { AffectedNodes: [ { ModifiedNode: { @@ -196,15 +198,37 @@ module.exports = function(request, options={}) { TransactionResult: 'tesSUCCESS' }; + let marker = +request.marker || 0; + marker += 1; + if (marker === 5) { + meta.TransactionResult = 'tecINSUFFICIENT_RESERVE'; + } else if (marker === 6) { + tx = _.cloneDeep(AccountSet.result); + meta = tx.meta; + delete tx.meta; + } else if (marker === 7) { + tx.Account = addresses.OTHER_ACCOUNT; + } else if (marker === 8) { + tx.Destination = addresses.THIRD_ACCOUNT; + } else if (marker > 25) { + marker = undefined; + } else if (marker > 15) { + tx.Account = addresses.ISSUER; + tx.Destination = addresses.ACCOUNT; + } + if (request.limit === 13) { + const res = _.assign({}, NotFound, {id: request.id}); + return JSON.stringify(res); + } return JSON.stringify({ id: request.id, status: 'success', type: 'response', result: { - marker: request.marker === undefined ? 'ABC' : undefined, + marker: marker === undefined ? undefined : String(marker), transactions: [ { - ledger_index: 348860, + ledger_index: 348860 - +marker, tx_blob: SerializedObject.from_json(tx).to_hex(), meta: SerializedObject.from_json(meta).to_hex(), validated: options.validated From e8d0c1ae9567e316dd74b993b851dd940e755a10 Mon Sep 17 00:00:00 2001 From: Ivan Tivonenko Date: Thu, 23 Jul 2015 20:29:18 +0300 Subject: [PATCH 3/6] make api.getTransaction return NotFound error in case of transaction not found instead of core.RippleError --- src/api/ledger/transaction.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/api/ledger/transaction.js b/src/api/ledger/transaction.js index 7ccad107..d8d0e673 100644 --- a/src/api/ledger/transaction.js +++ b/src/api/ledger/transaction.js @@ -6,6 +6,7 @@ const utils = require('./utils'); const parseTransaction = require('./parse/transaction'); const validate = utils.common.validate; const errors = utils.common.errors; +const RippleError = require('../../core/rippleerror').RippleError; function attachTransactionDate(remote, tx, callback) { if (tx.date) { @@ -43,7 +44,13 @@ function getTransaction(identifier, options, callback) { const maxLedgerVersion = Math.min(options.maxLedgerVersion, remote.getLedgerSequence()); - function callbackWrapper(error, tx) { + function callbackWrapper(error_, tx) { + let error = error_; + if (error instanceof RippleError && error.remote && + error.remote.error === 'txnNotFound') { + error = new errors.NotFoundError('Transaction not found'); + } + if (error instanceof errors.NotFoundError && !utils.hasCompleteLedgerRange(remote, options.minLedgerVersion, maxLedgerVersion)) { From 6e180439d1c96a8e9a68ddd082ee2b18ba105e8a Mon Sep 17 00:00:00 2001 From: Ivan Tivonenko Date: Thu, 23 Jul 2015 20:31:13 +0300 Subject: [PATCH 4/6] cover api/ledger/transaction.js with unit tests --- test/api-test.js | 62 ++++++++++++++++++- test/fixtures/api/rippled/index.js | 7 ++- .../api/rippled/ledger-close-newer.json | 12 ++++ .../api/rippled/ledger-not-found.json | 13 ++++ .../rippled/ledger-without-close-time.json | 26 ++++++++ .../api/rippled/tx/ledger-without-time.json | 45 ++++++++++++++ .../api/rippled/tx/no-ledger-found.json | 45 ++++++++++++++ .../api/rippled/tx/no-ledger-index.json | 44 +++++++++++++ test/mock-rippled.js | 17 ++++- 9 files changed, 267 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/api/rippled/ledger-close-newer.json create mode 100644 test/fixtures/api/rippled/ledger-not-found.json create mode 100644 test/fixtures/api/rippled/ledger-without-close-time.json create mode 100644 test/fixtures/api/rippled/tx/ledger-without-time.json create mode 100644 test/fixtures/api/rippled/tx/no-ledger-found.json create mode 100644 test/fixtures/api/rippled/tx/no-ledger-index.json diff --git a/test/api-test.js b/test/api-test.js index aec7cdce..4e3d4ae3 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -13,6 +13,7 @@ const sjcl = require('../src').sjcl; const address = addresses.ACCOUNT; const RippleError = require('../src/core/rippleerror').RippleError; const utils = require('../src/api/ledger/utils'); +const ledgerClosed = require('./fixtures/api/rippled/ledger-close-newer'); const schemaValidate = require('../src/api/common/schema-validator'); const orderbook = { @@ -204,6 +205,64 @@ describe('RippleAPI', function() { 'getTransaction', done)); }); + it('getTransaction - not found in range', function(done) { + const hash = + '809335DD3B0B333865096217AA2F55A4DF168E0198080B3A090D12D88880FF0E'; + const options = { + minLedgerVersion: 32570, + maxLedgerVersion: 32571 + }; + this.api.getTransaction(hash, options, (error) => { + assert.ok(error instanceof errors.NotFoundError); + done(); + }); + }); + + it('getTransaction - not found by hash', function(done) { + this.api.getTransaction(hashes.NOTFOUND_TRANSACTION_HASH, {}, (error) => { + assert.ok(error instanceof errors.NotFoundError); + done(); + }); + }); + + it('getTransaction - missing ledger history', function(done) { + // make gaps in history + this.api.remote.getServer().emit('message', ledgerClosed); + this.api.getTransaction(hashes.NOTFOUND_TRANSACTION_HASH, {}, (error) => { + assert.ok(error instanceof errors.MissingLedgerHistoryError); + done(); + }); + }); + + it('getTransaction - ledger_index not found', function(done) { + const hash = + '4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA11'; + this.api.getTransaction(hash, {}, (error) => { + assert.ok(error instanceof errors.NotFoundError); + assert.ok(error.message.indexOf('ledger_index') !== -1); + done(); + }); + }); + + it('getTransaction - transaction ledger not found', function(done) { + const hash = + '4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA12'; + this.api.getTransaction(hash, {}, (error) => { + assert.ok(error instanceof errors.NotFoundError); + assert.ok(error.message.indexOf('ledger not found') !== -1); + done(); + }); + }); + + it('getTransaction - ledger missing close time', function(done) { + const hash = + '0F7ED9F40742D8A513AE86029462B7A6768325583DF8EE21B7EC663019DD6A04'; + this.api.getTransaction(hash, {}, (error) => { + assert.ok(error instanceof errors.ApiError); + done(); + }); + }); + it('getTransactions', function(done) { const options = {types: ['payment', 'order'], initiated: true, limit: 2}; this.api.getTransactions(address, options, @@ -248,8 +307,7 @@ describe('RippleAPI', function() { counterparty: address }; this.api.getTransactions(address, options, (error) => { - assert.ok(error instanceof RippleError); - assert.strictEqual(error.remote.error, 'txnNotFound'); + assert.ok(error instanceof errors.NotFoundError); done(); }); }); diff --git a/test/fixtures/api/rippled/index.js b/test/fixtures/api/rippled/index.js index 5c999e58..0b44270d 100644 --- a/test/fixtures/api/rippled/index.js +++ b/test/fixtures/api/rippled/index.js @@ -3,6 +3,8 @@ module.exports = { submit: require('./submit'), ledger: require('./ledger'), + ledgerNotFound: require('./ledger-not-found'), + ledgerWithoutCloseTime: require('./ledger-without-close-time'), subscribe: require('./subscribe'), unsubscribe: require('./unsubscribe'), account_info: { @@ -24,6 +26,9 @@ module.exports = { OfferCreate: require('./tx/offer-create.json'), OfferCancel: require('./tx/offer-cancel.json'), TrustSet: require('./tx/trust-set.json'), - NotFound: require('./tx/not-found.json') + NotFound: require('./tx/not-found.json'), + NoLedgerIndex: require('./tx/no-ledger-index.json'), + NoLedgerFound: require('./tx/no-ledger-found.json'), + LedgerWithoutTime: require('./tx/ledger-without-time.json') } }; diff --git a/test/fixtures/api/rippled/ledger-close-newer.json b/test/fixtures/api/rippled/ledger-close-newer.json new file mode 100644 index 00000000..8899692e --- /dev/null +++ b/test/fixtures/api/rippled/ledger-close-newer.json @@ -0,0 +1,12 @@ +{ + "fee_base": 10, + "fee_ref": 10, + "ledger_hash": "9141FA171F2C0CE63E609466AF728FF66C12F7ACD4B4B50B0947A7F3409D593A", + "ledger_index": 14804627, + "ledger_time": 490945840, + "reserve_base": 20000000, + "reserve_inc": 5000000, + "txn_count": 19, + "type": "ledgerClosed", + "validated_ledgers": "13983423-14804627" +} \ No newline at end of file diff --git a/test/fixtures/api/rippled/ledger-not-found.json b/test/fixtures/api/rippled/ledger-not-found.json new file mode 100644 index 00000000..89d1be47 --- /dev/null +++ b/test/fixtures/api/rippled/ledger-not-found.json @@ -0,0 +1,13 @@ +{ + "id": 0, + "status": "error", + "type": "response", + "error": "lgrNotFound", + "error_code": 20, + "error_message": "ledgerNotFound", + "request": { + "command": "ledger", + "id": 3, + "ledger_index": 34 + } +} \ No newline at end of file diff --git a/test/fixtures/api/rippled/ledger-without-close-time.json b/test/fixtures/api/rippled/ledger-without-close-time.json new file mode 100644 index 00000000..14324755 --- /dev/null +++ b/test/fixtures/api/rippled/ledger-without-close-time.json @@ -0,0 +1,26 @@ +{ + "id": 0, + "status": "success", + "type": "response", + "result": { + "ledger": { + "accepted": true, + "account_hash": "EC028EC32896D537ECCA18D18BEBE6AE99709FEFF9EF72DBD3A7819E918D8B96", + "close_time_human": "2014-Sep-24 21:21:50", + "close_time_resolution": 10, + "closed": true, + "hash": "0F7ED9F40742D8A513AE86029462B7A6768325583DF8EE21B7EC663019DD6A04", + "ledger_hash": "0F7ED9F40742D8A513AE86029462B7A6768325583DF8EE21B7EC663019DD6A01", + "ledger_index": "9038215", + "parent_hash": "4BB9CBE44C39DC67A1BE849C7467FE1A6D1F73949EA163C38A0121A15E04FFDE", + "seqNum": "9038214", + "totalCoins": "99999973964317514", + "total_coins": "99999973964317514", + "transaction_hash": "ECB730839EB55B1B114D5D1AD2CD9A932C35BA9AB6D3A8C2F08935EAC2BAC239", + "transactions": [ + "1FC4D12C30CE206A6E23F46FAC62BD393BE9A79A1C452C6F3A04A13BC7A5E5A3", + "E25C38FDB8DD4A2429649588638EE05D055EE6D839CABAF8ABFB4BD17CFE1F3E" + ] + } + } +} diff --git a/test/fixtures/api/rippled/tx/ledger-without-time.json b/test/fixtures/api/rippled/tx/ledger-without-time.json new file mode 100644 index 00000000..40798ee4 --- /dev/null +++ b/test/fixtures/api/rippled/tx/ledger-without-time.json @@ -0,0 +1,45 @@ +{ + "id": 0, + "status": "success", + "type": "response", + "result": { + "Account": "rLVKsA4F9iJBbA6rX2x4wCmkj6drgtqpQe", + "Fee": "10", + "Flags": 2147483648, + "Sequence": 1, + "SetFlag": 2, + "SigningPubKey": "03EA3ADCA632F125EC2CC4F7F6A82DE0DCE2B65290CAC1F22242C5163F0DA9652D", + "TransactionType": "AccountSet", + "TxnSignature": "3045022100DE8B666B1A31EA65011B0F32130AB91A5747E32FA49B3054CEE8E8362DBAB98A022040CF0CF254677A8E5CD04C59CA2ED7F6F15F7E184641BAE169C561650967B226", + "hash": "4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA12", + "inLedger": 8206418, + "ledger_index": 9038215, + "meta": { + "AffectedNodes": [ + { + "ModifiedNode": { + "FinalFields": { + "Account": "rLVKsA4F9iJBbA6rX2x4wCmkj6drgtqpQe", + "Balance": "29999990", + "Flags": 786432, + "OwnerCount": 0, + "Sequence": 2 + }, + "LedgerEntryType": "AccountRoot", + "LedgerIndex": "3F5072C4875F32ED770DAF3610A716600ED7C7BB0348FADC7A98E011BB2CD36F", + "PreviousFields": { + "Balance": "30000000", + "Flags": 4194304, + "Sequence": 1 + }, + "PreviousTxnID": "3FB0350A3742BBCC0D8AA3C5247D1AEC01177D0A24D9C34762BAA2FEA8AD88B3", + "PreviousTxnLgrSeq": 8206397 + } + } + ], + "TransactionIndex": 5, + "TransactionResult": "tesSUCCESS" + }, + "validated": true + } +} diff --git a/test/fixtures/api/rippled/tx/no-ledger-found.json b/test/fixtures/api/rippled/tx/no-ledger-found.json new file mode 100644 index 00000000..b78ebe1f --- /dev/null +++ b/test/fixtures/api/rippled/tx/no-ledger-found.json @@ -0,0 +1,45 @@ +{ + "id": 0, + "status": "success", + "type": "response", + "result": { + "Account": "rLVKsA4F9iJBbA6rX2x4wCmkj6drgtqpQe", + "Fee": "10", + "Flags": 2147483648, + "Sequence": 1, + "SetFlag": 2, + "SigningPubKey": "03EA3ADCA632F125EC2CC4F7F6A82DE0DCE2B65290CAC1F22242C5163F0DA9652D", + "TransactionType": "AccountSet", + "TxnSignature": "3045022100DE8B666B1A31EA65011B0F32130AB91A5747E32FA49B3054CEE8E8362DBAB98A022040CF0CF254677A8E5CD04C59CA2ED7F6F15F7E184641BAE169C561650967B226", + "hash": "4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA12", + "inLedger": 8206418, + "ledger_index": 34, + "meta": { + "AffectedNodes": [ + { + "ModifiedNode": { + "FinalFields": { + "Account": "rLVKsA4F9iJBbA6rX2x4wCmkj6drgtqpQe", + "Balance": "29999990", + "Flags": 786432, + "OwnerCount": 0, + "Sequence": 2 + }, + "LedgerEntryType": "AccountRoot", + "LedgerIndex": "3F5072C4875F32ED770DAF3610A716600ED7C7BB0348FADC7A98E011BB2CD36F", + "PreviousFields": { + "Balance": "30000000", + "Flags": 4194304, + "Sequence": 1 + }, + "PreviousTxnID": "3FB0350A3742BBCC0D8AA3C5247D1AEC01177D0A24D9C34762BAA2FEA8AD88B3", + "PreviousTxnLgrSeq": 8206397 + } + } + ], + "TransactionIndex": 5, + "TransactionResult": "tesSUCCESS" + }, + "validated": true + } +} diff --git a/test/fixtures/api/rippled/tx/no-ledger-index.json b/test/fixtures/api/rippled/tx/no-ledger-index.json new file mode 100644 index 00000000..cd15d672 --- /dev/null +++ b/test/fixtures/api/rippled/tx/no-ledger-index.json @@ -0,0 +1,44 @@ +{ + "id": 0, + "status": "success", + "type": "response", + "result": { + "Account": "rLVKsA4F9iJBbA6rX2x4wCmkj6drgtqpQe", + "Fee": "10", + "Flags": 2147483648, + "Sequence": 1, + "SetFlag": 2, + "SigningPubKey": "03EA3ADCA632F125EC2CC4F7F6A82DE0DCE2B65290CAC1F22242C5163F0DA9652D", + "TransactionType": "AccountSet", + "TxnSignature": "3045022100DE8B666B1A31EA65011B0F32130AB91A5747E32FA49B3054CEE8E8362DBAB98A022040CF0CF254677A8E5CD04C59CA2ED7F6F15F7E184641BAE169C561650967B226", + "hash": "4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA11", + "inLedger": 8206418, + "meta": { + "AffectedNodes": [ + { + "ModifiedNode": { + "FinalFields": { + "Account": "rLVKsA4F9iJBbA6rX2x4wCmkj6drgtqpQe", + "Balance": "29999990", + "Flags": 786432, + "OwnerCount": 0, + "Sequence": 2 + }, + "LedgerEntryType": "AccountRoot", + "LedgerIndex": "3F5072C4875F32ED770DAF3610A716600ED7C7BB0348FADC7A98E011BB2CD36F", + "PreviousFields": { + "Balance": "30000000", + "Flags": 4194304, + "Sequence": 1 + }, + "PreviousTxnID": "3FB0350A3742BBCC0D8AA3C5247D1AEC01177D0A24D9C34762BAA2FEA8AD88B3", + "PreviousTxnLgrSeq": 8206397 + } + } + ], + "TransactionIndex": 5, + "TransactionResult": "tesSUCCESS" + }, + "validated": true + } +} diff --git a/test/mock-rippled.js b/test/mock-rippled.js index f4e80f35..394d886b 100644 --- a/test/mock-rippled.js +++ b/test/mock-rippled.js @@ -111,7 +111,13 @@ module.exports = function(port) { mock.on('request_ledger', function(request, conn) { assert.strictEqual(request.command, 'ledger'); - conn.send(createResponse(request, fixtures.ledger)); + if (request.ledger_index === 34) { + conn.send(createResponse(request, fixtures.ledgerNotFound)); + } else if (request.ledger_index === 9038215) { + conn.send(createResponse(request, fixtures.ledgerWithoutCloseTime)); + } else { + conn.send(createResponse(request, fixtures.ledger)); + } }); mock.on('request_tx', function(request, conn) { @@ -130,6 +136,15 @@ module.exports = function(port) { } else if (request.transaction === '635A0769BD94710A1F6A76CDE65A3BC661B20B798807D1BBBDADCEA26420538D') { conn.send(createResponse(request, fixtures.tx.TrustSet)); + } else if (request.transaction === + '4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA11') { + conn.send(createResponse(request, fixtures.tx.NoLedgerIndex)); + } else if (request.transaction === + '4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA12') { + conn.send(createResponse(request, fixtures.tx.NoLedgerFound)); + } else if (request.transaction === + '0F7ED9F40742D8A513AE86029462B7A6768325583DF8EE21B7EC663019DD6A04') { + conn.send(createResponse(request, fixtures.tx.LedgerWithoutTime)); } else if (request.transaction === hashes.NOTFOUND_TRANSACTION_HASH) { conn.send(createResponse(request, fixtures.tx.NotFound)); } else { From 13dee36e935d026d236da35753cce62da5c8ee9a Mon Sep 17 00:00:00 2001 From: Ivan Tivonenko Date: Thu, 23 Jul 2015 22:44:28 +0300 Subject: [PATCH 5/6] propagate message from remote error to RippledNetworkError --- src/api/server/server.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/api/server/server.js b/src/api/server/server.js index ca5eb258..f2b5c9f7 100644 --- a/src/api/server/server.js +++ b/src/api/server/server.js @@ -28,7 +28,9 @@ function isConnected(): boolean { function getServerInfo(callback: (err: any, data: any) => void): void { this.remote.requestServerInfo((error, response) => { if (error) { - callback(new common.errors.RippledNetworkError(error.message)); + const message = error && error.remote && error.remote.error_message? + error.remote.error_message : error.message; + callback(new common.errors.RippledNetworkError(message)); } else { callback(null, response.info); } From 2c52e4aa696f4fdb012313650c84a60018eae390 Mon Sep 17 00:00:00 2001 From: Ivan Tivonenko Date: Thu, 23 Jul 2015 22:45:18 +0300 Subject: [PATCH 6/6] more unit tests coverage --- src/api/server/server.js | 4 +- test/api-test.js | 99 ++++++++++++++++--- test/fixtures/api/rippled/account-tx.js | 4 +- test/fixtures/api/rippled/index.js | 1 + .../api/rippled/server-info-error.json | 12 +++ test/mock-rippled.js | 6 +- 6 files changed, 105 insertions(+), 21 deletions(-) create mode 100644 test/fixtures/api/rippled/server-info-error.json diff --git a/src/api/server/server.js b/src/api/server/server.js index f2b5c9f7..e73b1c60 100644 --- a/src/api/server/server.js +++ b/src/api/server/server.js @@ -2,6 +2,7 @@ 'use strict'; +const _ = require('lodash'); const common = require('../common'); // If a ledger is not received in this time, consider the connection offline @@ -28,8 +29,7 @@ function isConnected(): boolean { function getServerInfo(callback: (err: any, data: any) => void): void { this.remote.requestServerInfo((error, response) => { if (error) { - const message = error && error.remote && error.remote.error_message? - error.remote.error_message : error.message; + const message = _.get(error, ['remote', 'error_message'], error.message); callback(new common.errors.RippledNetworkError(message)); } else { callback(null, response.info); diff --git a/test/api-test.js b/test/api-test.js index 4e3d4ae3..903b770f 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -11,6 +11,7 @@ const hashes = require('./fixtures/hashes'); const MockPRNG = require('./mock-prng'); const sjcl = require('../src').sjcl; const address = addresses.ACCOUNT; +const validate = require('../src/api/common/validate'); const RippleError = require('../src/core/rippleerror').RippleError; const utils = require('../src/api/ledger/utils'); const ledgerClosed = require('./fixtures/api/rippled/ledger-close-newer'); @@ -213,14 +214,14 @@ describe('RippleAPI', function() { maxLedgerVersion: 32571 }; this.api.getTransaction(hash, options, (error) => { - assert.ok(error instanceof errors.NotFoundError); + assert(error instanceof this.api.errors.NotFoundError); done(); }); }); it('getTransaction - not found by hash', function(done) { this.api.getTransaction(hashes.NOTFOUND_TRANSACTION_HASH, {}, (error) => { - assert.ok(error instanceof errors.NotFoundError); + assert(error instanceof this.api.errors.NotFoundError); done(); }); }); @@ -229,7 +230,7 @@ describe('RippleAPI', function() { // make gaps in history this.api.remote.getServer().emit('message', ledgerClosed); this.api.getTransaction(hashes.NOTFOUND_TRANSACTION_HASH, {}, (error) => { - assert.ok(error instanceof errors.MissingLedgerHistoryError); + assert(error instanceof this.api.errors.MissingLedgerHistoryError); done(); }); }); @@ -238,8 +239,8 @@ describe('RippleAPI', function() { const hash = '4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA11'; this.api.getTransaction(hash, {}, (error) => { - assert.ok(error instanceof errors.NotFoundError); - assert.ok(error.message.indexOf('ledger_index') !== -1); + assert(error instanceof this.api.errors.NotFoundError); + assert(error.message.indexOf('ledger_index') !== -1); done(); }); }); @@ -248,8 +249,8 @@ describe('RippleAPI', function() { const hash = '4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA12'; this.api.getTransaction(hash, {}, (error) => { - assert.ok(error instanceof errors.NotFoundError); - assert.ok(error.message.indexOf('ledger not found') !== -1); + assert(error instanceof this.api.errors.NotFoundError); + assert(error.message.indexOf('ledger not found') !== -1); done(); }); }); @@ -258,7 +259,7 @@ describe('RippleAPI', function() { const hash = '0F7ED9F40742D8A513AE86029462B7A6768325583DF8EE21B7EC663019DD6A04'; this.api.getTransaction(hash, {}, (error) => { - assert.ok(error instanceof errors.ApiError); + assert(error instanceof this.api.errors.ApiError); done(); }); }); @@ -277,7 +278,7 @@ describe('RippleAPI', function() { const expected = _.cloneDeep(responses.getTransactions) .sort(utils.compareTransactions); this.api.getTransactions(address, options, - _.partial(checkResult, expected, done)); + _.partial(checkResult, expected, 'getTransactions', done)); }); it('getTransactions - earliest first with start option', function(done) { @@ -296,7 +297,7 @@ describe('RippleAPI', function() { maxLedgerVersion: 348858000 }; this.api.getTransactions(address, options, (error) => { - assert.ok(error instanceof this.errors.MissingLedgerHistoryError); + assert(error instanceof this.api.errors.MissingLedgerHistoryError); done(); }); }); @@ -307,7 +308,7 @@ describe('RippleAPI', function() { counterparty: address }; this.api.getTransactions(address, options, (error) => { - assert.ok(error instanceof errors.NotFoundError); + assert(error instanceof this.api.errors.NotFoundError); done(); }); }); @@ -319,8 +320,8 @@ describe('RippleAPI', function() { }; this.api.getTransactions(address, options, (error, data) => { assert.strictEqual(data.length, 10); - assert.ok(_.every(data, t => t.type === 'payment' || t.type === 'order')); - assert.ok(_.every(data, t => t.outcome.result === 'tesSUCCESS')); + assert(_.every(data, t => t.type === 'payment' || t.type === 'order')); + assert(_.every(data, t => t.outcome.result === 'tesSUCCESS')); done(); }); }); @@ -332,16 +333,18 @@ describe('RippleAPI', function() { }; this.api.getTransactions(address, options, (error, data) => { assert.strictEqual(data.length, 10); - assert.ok(_.every(data, t => t.type === 'payment' || t.type === 'order')); - assert.ok(_.every(data, t => t.outcome.result === 'tesSUCCESS')); + assert(_.every(data, t => t.type === 'payment' || t.type === 'order')); + assert(_.every(data, t => t.outcome.result === 'tesSUCCESS')); done(); }); }); + // this is the case where core.RippleError just falls + // through the api to the user it('getTransactions - error', function(done) { const options = {types: ['payment', 'order'], initiated: true, limit: 13}; this.api.getTransactions(address, options, (error) => { - assert.ok(error instanceof RippleError); + assert(error instanceof RippleError); done(); }); }); @@ -435,6 +438,15 @@ describe('RippleAPI', function() { _.partial(checkResult, responses.getServerInfo, null, done)); }); + it('getServerInfo - error', function(done) { + this.mockRippled.returnErrorOnServerInfo = true; + this.api.getServerInfo((error) => { + assert(error instanceof this.api.errors.NetworkError); + assert(error.message.indexOf('too much load') !== -1); + done(); + }); + }); + it('getFee', function() { assert.strictEqual(this.api.getFee(), '0.000012'); }); @@ -497,4 +509,59 @@ describe('RippleAPI', function() { it('getLedgerVersion', function() { assert.strictEqual(this.api.getLedgerVersion(), 8819951); }); + + it('ledger utils - compareTransactions', function() { + let first = {outcome: {ledgerVersion: 1, indexInLedger: 100}}; + let second = {outcome: {ledgerVersion: 1, indexInLedger: 200}}; + + assert.strictEqual(utils.compareTransactions(first, second), -1); + + first = {outcome: {ledgerVersion: 1, indexInLedger: 100}}; + second = {outcome: {ledgerVersion: 1, indexInLedger: 100}}; + + assert.strictEqual(utils.compareTransactions(first, second), 0); + + first = {outcome: {ledgerVersion: 1, indexInLedger: 200}}; + second = {outcome: {ledgerVersion: 1, indexInLedger: 100}}; + + assert.strictEqual(utils.compareTransactions(first, second), 1); + }); + + it('ledger utils - renameCounterpartyToIssuer', function() { + assert.strictEqual(utils.renameCounterpartyToIssuer(undefined), undefined); + const amountArg = {issuer: '1'}; + assert.deepEqual(utils.renameCounterpartyToIssuer(amountArg), amountArg); + }); + + it('ledger utils - getRecursive', function(done) { + function getter(marker, limit, callback) { + if (marker === undefined) { + callback(null, {marker: 'A', results: [1]}); + } else { + callback(new Error(), null); + } + } + utils.getRecursive(getter, 10, (error) => { + assert(error instanceof Error); + done(); + }); + }); + + it('validator', function() { + const noSecret = {address: address}; + assert.throws(_.partial(validate.addressAndSecret, noSecret), + this.api.errors.ValidationError); + assert.throws(_.partial(validate.addressAndSecret, noSecret), + /Parameter missing/); + const badSecret = {address: address, secret: 'bad'}; + assert.throws(_.partial(validate.addressAndSecret, badSecret), + this.api.errors.ValidationError); + assert.throws(_.partial(validate.addressAndSecret, badSecret), + /not match/); + const goodWallet = {address: 'rpZMK8hwyrBvLorFNWHRCGt88nCJWbixur', + secret: 'shzjfakiK79YQdMjy4h8cGGfQSV6u' + }; + assert.doesNotThrow(_.partial(validate.addressAndSecret, goodWallet)); + }); + }); diff --git a/test/fixtures/api/rippled/account-tx.js b/test/fixtures/api/rippled/account-tx.js index 55fa9fc6..ac7d31e2 100644 --- a/test/fixtures/api/rippled/account-tx.js +++ b/test/fixtures/api/rippled/account-tx.js @@ -198,7 +198,7 @@ module.exports = function(request, options={}) { TransactionResult: 'tesSUCCESS' }; - let marker = +request.marker || 0; + let marker = Number(request.marker) || 0; marker += 1; if (marker === 5) { meta.TransactionResult = 'tecINSUFFICIENT_RESERVE'; @@ -228,7 +228,7 @@ module.exports = function(request, options={}) { marker: marker === undefined ? undefined : String(marker), transactions: [ { - ledger_index: 348860 - +marker, + ledger_index: 348860 - Number(marker), tx_blob: SerializedObject.from_json(tx).to_hex(), meta: SerializedObject.from_json(meta).to_hex(), validated: options.validated diff --git a/test/fixtures/api/rippled/index.js b/test/fixtures/api/rippled/index.js index 0b44270d..8eb0b483 100644 --- a/test/fixtures/api/rippled/index.js +++ b/test/fixtures/api/rippled/index.js @@ -15,6 +15,7 @@ module.exports = { account_tx: require('./account-tx'), book_offers: require('./book-offers'), server_info: require('./server-info'), + server_info_error: require('./server-info-error'), ripple_path_find: { generate: require('./ripple-path-find'), sendUSD: require('./ripple-path-find-send-usd'), diff --git a/test/fixtures/api/rippled/server-info-error.json b/test/fixtures/api/rippled/server-info-error.json new file mode 100644 index 00000000..d184f206 --- /dev/null +++ b/test/fixtures/api/rippled/server-info-error.json @@ -0,0 +1,12 @@ +{ + "id": 0, + "status": "error", + "type": "response", + "error": "slowDown", + "error_code": 15, + "error_message": "You are placing too much load on the server.", + "request": { + "command": "server_info", + "id": 0 + } +} diff --git a/test/mock-rippled.js b/test/mock-rippled.js index 394d886b..a1a35cb2 100644 --- a/test/mock-rippled.js +++ b/test/mock-rippled.js @@ -71,7 +71,11 @@ module.exports = function(port) { mock.on('request_server_info', function(request, conn) { assert.strictEqual(request.command, 'server_info'); - conn.send(createResponse(request, fixtures.server_info)); + if (mock.returnErrorOnServerInfo) { + conn.send(createResponse(request, fixtures.server_info_error)); + } else { + conn.send(createResponse(request, fixtures.server_info)); + } }); mock.on('request_subscribe', function(request, conn) {