From dfe65c817c35f065811466c7aaefcd8b173eecac Mon Sep 17 00:00:00 2001 From: Shusetsu Toda Date: Wed, 5 Sep 2018 16:40:53 +0200 Subject: [PATCH 1/6] :seelding: Migrate transaction util commands --- package.json | 3 + src/commands/transaction/broadcast.js | 72 +++++ src/commands/transaction/get.js | 57 ++++ src/commands/transaction/sign.js | 98 +++++++ src/commands/transaction/verify.js | 96 +++++++ src/commands_old/broadcast_transaction.js | 71 ----- src/commands_old/get.js | 52 ---- src/commands_old/list.js | 56 ---- src/commands_old/verify_transaction.js | 103 ------- test/commands/transaction/broadcast.test.js | 134 +++++++++ test/commands/transaction/get.test.js | 93 +++++++ test/commands/transaction/sign.test.js | 287 ++++++++++++++++++++ test/commands/transaction/verify.test.js | 194 +++++++++++++ 13 files changed, 1034 insertions(+), 282 deletions(-) create mode 100644 src/commands/transaction/broadcast.js create mode 100644 src/commands/transaction/get.js create mode 100644 src/commands/transaction/sign.js create mode 100644 src/commands/transaction/verify.js delete mode 100644 src/commands_old/broadcast_transaction.js delete mode 100644 src/commands_old/get.js delete mode 100644 src/commands_old/list.js delete mode 100644 src/commands_old/verify_transaction.js create mode 100644 test/commands/transaction/broadcast.test.js create mode 100644 test/commands/transaction/get.test.js create mode 100644 test/commands/transaction/sign.test.js create mode 100644 test/commands/transaction/verify.test.js diff --git a/package.json b/package.json index 978a3c09..eb2f5b7b 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,9 @@ "signature": { "description": "Commands relating to signatures for Lisk transactions from multisignature accounts." }, + "transaction": { + "description": "Commands relating to Lisk transactions." + }, "warranty": { "description": "Displays warranty notice." } diff --git a/src/commands/transaction/broadcast.js b/src/commands/transaction/broadcast.js new file mode 100644 index 00000000..bdf7853b --- /dev/null +++ b/src/commands/transaction/broadcast.js @@ -0,0 +1,72 @@ +/* + * LiskHQ/lisk-commander + * Copyright © 2017–2018 Lisk Foundation + * + * See the LICENSE file at the top-level directory of this distribution + * for licensing information. + * + * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, + * no part of this software, including this file, may be copied, modified, + * propagated, or distributed except according to the terms contained in the + * LICENSE file. + * + * Removal or modification of this copyright notice is prohibited. + * + */ +import BaseCommand from '../../base'; +import { ValidationError } from '../../utils/error'; +import { getRawStdIn } from '../../utils/input/utils'; +import getAPIClient from '../../utils/api'; + +const getTransactionInput = async () => + getRawStdIn() + .then(rawStdIn => { + if (rawStdIn.length <= 0) { + throw new ValidationError('No transaction was provided.'); + } + return rawStdIn[0]; + }) + .catch(() => { + throw new ValidationError('No transaction was provided.'); + }); + +export default class BroadcastCommand extends BaseCommand { + async run() { + const { args: { transaction } } = this.parse(BroadcastCommand); + const transactionInput = + transaction || (await getTransactionInput(transaction)); + let transactionObject; + try { + transactionObject = JSON.parse(transactionInput); + } catch (error) { + throw new ValidationError( + 'Could not parse transaction JSON. Did you use the `--json` option?', + ); + } + const client = getAPIClient(this.userConfig.api); + const response = await client.transactions.broadcast(transactionObject); + this.print(response.data); + } +} + +BroadcastCommand.args = [ + { + name: 'transaction', + description: 'Transaction to broadcast.', + }, +]; + +BroadcastCommand.flags = { + ...BaseCommand.flags, +}; + +BroadcastCommand.description = ` +Broadcasts a transaction to the network via the node specified in the current config. +Accepts a stringified JSON transaction as an argument, or a transaction can be piped from a previous command. +If piping make sure to quote out the entire command chain to avoid piping-related conflicts in your shell. +`; + +BroadcastCommand.examples = [ + 'broadcast transaction \'{"type":0,"amount":"100",...}\'', + 'echo \'{"type":0,"amount":"100",...}\' | lisk transaction:broadcast', +]; diff --git a/src/commands/transaction/get.js b/src/commands/transaction/get.js new file mode 100644 index 00000000..2dfe13b2 --- /dev/null +++ b/src/commands/transaction/get.js @@ -0,0 +1,57 @@ +/* + * LiskHQ/lisk-commander + * Copyright © 2017–2018 Lisk Foundation + * + * See the LICENSE file at the top-level directory of this distribution + * for licensing information. + * + * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, + * no part of this software, including this file, may be copied, modified, + * propagated, or distributed except according to the terms contained in the + * LICENSE file. + * + * Removal or modification of this copyright notice is prohibited. + * + */ +import BaseCommand from '../../base'; +import getAPIClient from '../../utils/api'; +import query from '../../utils/query'; + +export default class GetCommand extends BaseCommand { + async run() { + const { args: { ids } } = this.parse(GetCommand); + const req = + ids.length === 1 + ? { limit: 1, id: ids[0] } + : ids.map(id => ({ + limit: 1, + id, + })); + const client = getAPIClient(this.userConfig.api); + const results = await query(client, 'transactions', req); + this.print(results); + } +} + +GetCommand.args = [ + { + name: 'ids', + required: true, + description: + 'Comma separated transaction id(s) which you want to get the information of.', + parse: input => input.split(','), + }, +]; + +GetCommand.flags = { + ...BaseCommand.flags, +}; + +GetCommand.description = ` +Gets transaction information from the blockchain. +`; + +GetCommand.examples = [ + 'transaction:get 10041151099734832021', + 'transaction:get 10041151099734832021,1260076503909567890', +]; diff --git a/src/commands/transaction/sign.js b/src/commands/transaction/sign.js new file mode 100644 index 00000000..c720a033 --- /dev/null +++ b/src/commands/transaction/sign.js @@ -0,0 +1,98 @@ +/* + * LiskHQ/lisk-commander + * Copyright © 2017–2018 Lisk Foundation + * + * See the LICENSE file at the top-level directory of this distribution + * for licensing information. + * + * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, + * no part of this software, including this file, may be copied, modified, + * propagated, or distributed except according to the terms contained in the + * LICENSE file. + * + * Removal or modification of this copyright notice is prohibited. + * + */ +import elements from 'lisk-elements'; +import { flags as flagParser } from '@oclif/command'; +import BaseCommand from '../../base'; +import { getRawStdIn } from '../../utils/input/utils'; +import { ValidationError } from '../../utils/error'; +import getInputsFromSources from '../../utils/input'; +import commonFlags from '../../utils/flags'; + +const getTransactionInput = async () => + getRawStdIn() + .then(rawStdIn => { + if (rawStdIn.length <= 0) { + throw new ValidationError('No transaction was provided.'); + } + return rawStdIn[0]; + }) + .catch(() => { + throw new ValidationError('No transaction was provided.'); + }); + +export default class SignCommand extends BaseCommand { + async run() { + const { + args: { transaction }, + flags: { + passphrase: passphraseSource, + 'second-passphrase': secondPassphraseSource, + }, + } = this.parse(SignCommand); + + const transactionInput = + transaction || (await getTransactionInput(transaction)); + + let transactionObject; + try { + transactionObject = JSON.parse(transactionInput); + } catch (error) { + throw new ValidationError('Could not parse transaction JSON.'); + } + + const { passphrase, secondPassphrase } = await getInputsFromSources({ + passphrase: { + source: passphraseSource, + repeatPrompt: true, + }, + secondPassphrase: !secondPassphraseSource + ? null + : { + source: secondPassphraseSource, + repeatPrompt: true, + }, + }); + + const result = elements.transaction.utils.prepareTransaction( + transactionObject, + passphrase, + secondPassphrase, + ); + + this.print(result); + } +} + +SignCommand.args = [ + { + name: 'transaction', + description: 'Transaction to sign.', + }, +]; + +SignCommand.flags = { + ...BaseCommand.flags, + passphrase: flagParser.string(commonFlags.passphrase), + 'second-passphrase': flagParser.string(commonFlags.secondPassphrase), +}; + +SignCommand.description = ` +Sign a transaction using your secret passphrase. +`; + +SignCommand.examples = [ + 'transaction:sign \'{"amount":"100","recipientId":"13356260975429434553L","senderPublicKey":null,"timestamp":52871598,"type":0,"fee":"10000000","recipientPublicKey":null,"asset":{}}\'', +]; diff --git a/src/commands/transaction/verify.js b/src/commands/transaction/verify.js new file mode 100644 index 00000000..57d4753b --- /dev/null +++ b/src/commands/transaction/verify.js @@ -0,0 +1,96 @@ +/* + * LiskHQ/lisk-commander + * Copyright © 2017–2018 Lisk Foundation + * + * See the LICENSE file at the top-level directory of this distribution + * for licensing information. + * + * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, + * no part of this software, including this file, may be copied, modified, + * propagated, or distributed except according to the terms contained in the + * LICENSE file. + * + * Removal or modification of this copyright notice is prohibited. + * + */ +import elements from 'lisk-elements'; +import { flags as flagParser } from '@oclif/command'; +import BaseCommand from '../../base'; +import { getRawStdIn, getData } from '../../utils/input/utils'; +import { ValidationError } from '../../utils/error'; + +const secondPublicKeyDescription = `Specifies a source for providing a second public key to the command. The second public key must be provided via this option. Sources must be one of \`file\` or \`stdin\`. In the case of \`file\`, a corresponding identifier must also be provided. + + Note: if both transaction and second public key are passed via stdin, the transaction must be the first line. + + Examples: + - --second-public-key file:/path/to/my/message.txt + - --second-public-key 790049f919979d5ea42cca7b7aa0812cbae8f0db3ee39c1fe3cef18e25b67951 +`; + +const getTransactionInput = async () => + getRawStdIn() + .then(rawStdIn => { + if (rawStdIn.length <= 0) { + throw new ValidationError('No transaction was provided.'); + } + return rawStdIn[0]; + }) + .catch(() => { + throw new ValidationError('No transaction was provided.'); + }); + +const processSecondPublicKey = async secondPublicKey => + secondPublicKey.includes(':') ? getData(secondPublicKey) : secondPublicKey; + +export default class VerifyCommand extends BaseCommand { + async run() { + const { + args: { transaction }, + flags: { 'second-public-key': secondPublicKeySource }, + } = this.parse(VerifyCommand); + + const transactionInput = transaction || (await getTransactionInput()); + + let transactionObject; + try { + transactionObject = JSON.parse(transactionInput); + } catch (error) { + throw new ValidationError('Could not parse transaction JSON.'); + } + + const secondPublicKey = secondPublicKeySource + ? await processSecondPublicKey(secondPublicKeySource) + : null; + + const verified = elements.transaction.utils.verifyTransaction( + transactionObject, + secondPublicKey, + ); + this.print({ verified }); + } +} + +VerifyCommand.args = [ + { + name: 'transaction', + description: 'Transaction to verify.', + }, +]; + +VerifyCommand.flags = { + ...BaseCommand.flags, + 'second-public-key': flagParser.string({ + name: 'Second public key', + description: secondPublicKeyDescription, + }), +}; + +VerifyCommand.description = ` +Verifies a transaction has a valid signature. +`; + +VerifyCommand.examples = [ + 'transaction:verify \'{"type":0,"amount":"100",...}\'', + 'transaction:verify \'{"type":0,"amount":"100",...}\' --second-public-key=647aac1e2df8a5c870499d7ddc82236b1e10936977537a3844a6b05ea33f9ef6', +]; diff --git a/src/commands_old/broadcast_transaction.js b/src/commands_old/broadcast_transaction.js deleted file mode 100644 index 07c893b7..00000000 --- a/src/commands_old/broadcast_transaction.js +++ /dev/null @@ -1,71 +0,0 @@ -/* - * LiskHQ/lisk-commander - * Copyright © 2017–2018 Lisk Foundation - * - * See the LICENSE file at the top-level directory of this distribution - * for licensing information. - * - * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, - * no part of this software, including this file, may be copied, modified, - * propagated, or distributed except according to the terms contained in the - * LICENSE file. - * - * Removal or modification of this copyright notice is prohibited. - * - */ -import getAPIClient from '../utils/api'; -import { ValidationError } from '../utils/error'; -import { createCommand } from '../utils/helpers'; - -const description = `Broadcasts a transaction to the network via the node -specified in the current config. Accepts a stringified JSON transaction as an -argument, or a transaction can be piped from a previous command. If piping in -non-interactive mode make sure to quote out the entire command chain to avoid -piping-related conflicts in your shell. - - Examples: - - Interactive mode: - - broadcast transaction '{"type":0,"amount":"100",...}' - - create transaction transfer 100 13356260975429434553L --json | broadcast transaction - - Non-interactive mode: - - lisk "create transaction transfer 100 13356260975429434553L --json | broadcast transaction" -`; - -const getTransactionInput = ({ transaction, stdin, shouldUseStdIn }) => { - const hasStdIn = stdin && stdin[0]; - if (shouldUseStdIn && !hasStdIn) { - throw new ValidationError('No transaction was provided.'); - } - return shouldUseStdIn ? stdin[0] : transaction; -}; - -export const actionCreator = () => async ({ transaction, stdin }) => { - const shouldUseStdIn = !transaction; - const transactionInput = getTransactionInput({ - transaction, - stdin, - shouldUseStdIn, - }); - - let transactionObject; - try { - transactionObject = JSON.parse(transactionInput); - } catch (error) { - throw new ValidationError( - 'Could not parse transaction JSON. Did you use the `--json` option?', - ); - } - - return shouldUseStdIn && transactionObject.error - ? transactionObject - : getAPIClient().transactions.broadcast(transactionObject); -}; - -const broadcastTransaction = createCommand({ - command: 'broadcast transaction [transaction]', - description, - actionCreator, - errorPrefix: 'Could not broadcast transaction', -}); - -export default broadcastTransaction; diff --git a/src/commands_old/get.js b/src/commands_old/get.js deleted file mode 100644 index fb96e640..00000000 --- a/src/commands_old/get.js +++ /dev/null @@ -1,52 +0,0 @@ -/* - * LiskHQ/lisk-commander - * Copyright © 2017–2018 Lisk Foundation - * - * See the LICENSE file at the top-level directory of this distribution - * for licensing information. - * - * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, - * no part of this software, including this file, may be copied, modified, - * propagated, or distributed except according to the terms contained in the - * LICENSE file. - * - * Removal or modification of this copyright notice is prohibited. - * - */ -import { COMMAND_TYPES, PLURALS, QUERY_INPUT_MAP } from '../utils/constants'; -import { ValidationError } from '../utils/error'; -import { createCommand, deAlias } from '../utils/helpers'; -import query from '../utils/query'; - -const description = `Gets information from the blockchain. Types available: account, address, block, delegate, transaction. - - Examples: - - get delegate lightcurve - - get block 5510510593472232540 -`; - -export const actionCreator = () => async ({ type, input }) => { - const pluralType = Object.keys(PLURALS).includes(type) ? PLURALS[type] : type; - - if (!COMMAND_TYPES.includes(pluralType)) { - throw new ValidationError('Unsupported type.'); - } - - const endpoint = deAlias(pluralType); - const req = { - limit: 1, - [QUERY_INPUT_MAP[endpoint]]: input, - }; - - return query(endpoint, req); -}; - -const get = createCommand({ - command: 'get ', - autocomplete: COMMAND_TYPES, - description, - actionCreator, - errorPrefix: 'Could not get', -}); - -export default get; diff --git a/src/commands_old/list.js b/src/commands_old/list.js deleted file mode 100644 index 2a767492..00000000 --- a/src/commands_old/list.js +++ /dev/null @@ -1,56 +0,0 @@ -/* - * LiskHQ/lisk-commander - * Copyright © 2017–2018 Lisk Foundation - * - * See the LICENSE file at the top-level directory of this distribution - * for licensing information. - * - * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, - * no part of this software, including this file, may be copied, modified, - * propagated, or distributed except according to the terms contained in the - * LICENSE file. - * - * Removal or modification of this copyright notice is prohibited. - * - */ -import { COMMAND_TYPES, PLURALS, QUERY_INPUT_MAP } from '../utils/constants'; -import { ValidationError } from '../utils/error'; -import { createCommand, deAlias } from '../utils/helpers'; -import query from '../utils/query'; - -const description = `Gets an array of information from the blockchain. Types available: accounts, addresses, blocks, delegates, transactions. - - Examples: - - list delegates lightcurve tosch - - list blocks 5510510593472232540 16450842638530591789 -`; - -export const actionCreator = () => async ({ type, inputs }) => { - const pluralType = Object.keys(PLURALS).includes(type) ? PLURALS[type] : type; - - if (!COMMAND_TYPES.includes(pluralType)) { - throw new ValidationError('Unsupported type.'); - } - - const endpoint = deAlias(pluralType); - - const queries = inputs.map(input => { - const req = { - limit: 1, - [QUERY_INPUT_MAP[endpoint]]: input, - }; - return query(endpoint, req); - }); - - return Promise.all(queries); -}; - -const list = createCommand({ - command: 'list ', - autocomplete: COMMAND_TYPES, - description, - actionCreator, - errorPrefix: 'Could not list', -}); - -export default list; diff --git a/src/commands_old/verify_transaction.js b/src/commands_old/verify_transaction.js deleted file mode 100644 index e1317e99..00000000 --- a/src/commands_old/verify_transaction.js +++ /dev/null @@ -1,103 +0,0 @@ -/* - * LiskHQ/lisk-commander - * Copyright © 2017–2018 Lisk Foundation - * - * See the LICENSE file at the top-level directory of this distribution - * for licensing information. - * - * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, - * no part of this software, including this file, may be copied, modified, - * propagated, or distributed except according to the terms contained in the - * LICENSE file. - * - * Removal or modification of this copyright notice is prohibited. - * - */ -import transactions from '../utils/transactions'; -import { ValidationError } from '../utils/error'; -import { createCommand } from '../utils/helpers'; -import { getFirstLineFromString } from '../utils/input'; -import { getData, getStdIn } from '../utils/input/utils'; - -const description = `Verifies a transaction has a valid signature. - - Examples: - - verify transaction '{"type":0,"amount":"100",...}' - - verify transaction '{"type":0,"amount":"100",...}' --second-public-key 647aac1e2df8a5c870499d7ddc82236b1e10936977537a3844a6b05ea33f9ef6 - - create transaction transfer 100 123L --json | verify transaction -`; - -const secondPublicKeyDescription = `Specifies a source for providing a second public key to the command. The second public key must be provided via this option. Sources must be one of \`file\` or \`stdin\`. In the case of \`file\`, a corresponding identifier must also be provided. - - Note: if both transaction and second public key are passed via stdin, the transaction must be the first line. - - Examples: - - --second-public-key file:/path/to/my/message.txt - - --second-public-key 790049f919979d5ea42cca7b7aa0812cbae8f0db3ee39c1fe3cef18e25b67951 -`; - -const getTransactionInput = ({ transaction, stdin }) => { - const hasStdIn = stdin && stdin[0]; - if (!transaction && !hasStdIn) { - return null; - } - return transaction || stdin[0]; -}; - -const processSecondPublicKey = async secondPublicKey => - secondPublicKey.includes(':') ? getData(secondPublicKey) : secondPublicKey; - -const getStdInForNonInteractiveMode = async () => { - // We should only get normal stdin for NON_INTERACTIVE_MODE - if (process.env.NON_INTERACTIVE_MODE) { - const stdin = await getStdIn({ dataIsRequired: true }); - return getFirstLineFromString(stdin.data); - } - return null; -}; - -export const actionCreator = () => async ({ - transaction, - stdin, - options = {}, -}) => { - const transactionSource = getTransactionInput({ - transaction, - stdin, - }); - const transactionInput = - transactionSource || (await getStdInForNonInteractiveMode()); - - if (!transactionInput) { - throw new ValidationError('No transaction was provided.'); - } - - let transactionObject; - try { - transactionObject = JSON.parse(transactionInput); - } catch (error) { - throw new ValidationError('Could not parse transaction JSON.'); - } - - const secondPublicKey = options['second-public-key'] - ? await processSecondPublicKey(options['second-public-key']) - : null; - - const verified = transactions.utils.verifyTransaction( - transactionObject, - secondPublicKey, - ); - return { - verified, - }; -}; - -const verifyTransaction = createCommand({ - command: 'verify transaction [transaction]', - description, - actionCreator, - options: [['--second-public-key ', secondPublicKeyDescription]], - errorPrefix: 'Could not verify transaction', -}); - -export default verifyTransaction; diff --git a/test/commands/transaction/broadcast.test.js b/test/commands/transaction/broadcast.test.js new file mode 100644 index 00000000..8848635d --- /dev/null +++ b/test/commands/transaction/broadcast.test.js @@ -0,0 +1,134 @@ +/* + * LiskHQ/lisk-commander + * Copyright © 2017–2018 Lisk Foundation + * + * See the LICENSE file at the top-level directory of this distribution + * for licensing information. + * + * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, + * no part of this software, including this file, may be copied, modified, + * propagated, or distributed except according to the terms contained in the + * LICENSE file. + * + * Removal or modification of this copyright notice is prohibited. + * + */ +import { test } from '@oclif/test'; +import * as config from '../../../src/utils/config'; +import * as print from '../../../src/utils/print'; +import * as api from '../../../src/utils/api'; +import * as inputUtils from '../../../src/utils/input/utils'; + +describe('transaction:broadcast', () => { + const defaultTransaction = { + amount: '10000000000', + recipientId: '123L', + senderPublicKey: + 'a4465fd76c16fcc458448076372abf1912cc5b150663a64dffefe550f96feadd', + timestamp: 66419917, + type: 0, + fee: '10000000', + recipientPublicKey: null, + asset: {}, + signature: + '96738e173a750998f4c2cdcdf7538b71854bcffd6c0dc72b3c28081ca6946322bea7ba5d8f8974fc97950014347ce379671a6eddc0d41ea6cdfb9bb7ff76be0a', + id: '1297455432474089551', + }; + + const wrongTransaction = 'not json transaction'; + + const defaultAPIResponse = { + data: { + message: 'success', + }, + }; + + const printMethodStub = sandbox.stub(); + const apiClientStub = { + transactions: { + broadcast: sandbox.stub().resolves(defaultAPIResponse), + }, + }; + const setupTest = () => + test + .stub(print, 'default', sandbox.stub().returns(printMethodStub)) + .stub(config, 'getConfig', sandbox.stub().returns({})) + .stub(api, 'default', sandbox.stub().returns(apiClientStub)) + .stdout(); + + describe('transaction:broadcast', () => { + setupTest() + .stub( + inputUtils, + 'getRawStdIn', + sandbox.stub().rejects(new Error('Timeout error')), + ) + .command(['transaction:broadcast']) + .catch(error => + expect(error.message).to.contain('No transaction was provided.'), + ) + .it('should throw an error without transaction'); + }); + + describe('transaction:broadcast transaction', () => { + setupTest() + .command(['transaction:broadcast', wrongTransaction]) + .catch(error => + expect(error.message).to.contain( + 'Could not parse transaction JSON. Did you use the `--json` option?', + ), + ) + .it('should throw an error with invalid transaction'); + + setupTest() + .command(['transaction:broadcast', JSON.stringify(defaultTransaction)]) + .it('should broadcast the transaction', () => { + expect(apiClientStub.transactions.broadcast).to.be.calledWithExactly( + defaultTransaction, + ); + return expect(printMethodStub).to.be.calledWithExactly( + defaultAPIResponse.data, + ); + }); + }); + + describe('transaction | transaction:broadcast', () => { + setupTest() + .stub(inputUtils, 'getRawStdIn', sandbox.stub().resolves([])) + .command(['transaction:broadcast']) + .catch(error => + expect(error.message).to.contain('No transaction was provided.'), + ) + .it('should throw an error with invalid transaction from stdin'); + + setupTest() + .stub( + inputUtils, + 'getRawStdIn', + sandbox.stub().resolves(wrongTransaction), + ) + .command(['transaction:broadcast']) + .catch(error => + expect(error.message).to.contain( + 'Could not parse transaction JSON. Did you use the `--json` option?', + ), + ) + .it('should throw an error with invalid transaction from stdin'); + + setupTest() + .stub( + inputUtils, + 'getRawStdIn', + sandbox.stub().resolves([JSON.stringify(defaultTransaction)]), + ) + .command(['transaction:broadcast']) + .it('should broadcast the transaction', () => { + expect(apiClientStub.transactions.broadcast).to.be.calledWithExactly( + defaultTransaction, + ); + return expect(printMethodStub).to.be.calledWithExactly( + defaultAPIResponse.data, + ); + }); + }); +}); diff --git a/test/commands/transaction/get.test.js b/test/commands/transaction/get.test.js new file mode 100644 index 00000000..58a4ae9d --- /dev/null +++ b/test/commands/transaction/get.test.js @@ -0,0 +1,93 @@ +/* + * LiskHQ/lisk-commander + * Copyright © 2017–2018 Lisk Foundation + * + * See the LICENSE file at the top-level directory of this distribution + * for licensing information. + * + * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, + * no part of this software, including this file, may be copied, modified, + * propagated, or distributed except according to the terms contained in the + * LICENSE file. + * + * Removal or modification of this copyright notice is prohibited. + * + */ +import { test } from '@oclif/test'; +import * as config from '../../../src/utils/config'; +import * as print from '../../../src/utils/print'; +import * as api from '../../../src/utils/api'; +import * as query from '../../../src/utils/query'; + +describe('transaction:get', () => { + const endpoint = 'transactions'; + const apiConfig = { + nodes: ['http://local.host'], + network: 'main', + }; + const printMethodStub = sandbox.stub(); + const apiClientStub = sandbox.stub(); + const setupTest = () => + test + .stub(print, 'default', sandbox.stub().returns(printMethodStub)) + .stub(config, 'getConfig', sandbox.stub().returns({ api: apiConfig })) + .stub(api, 'default', sandbox.stub().returns(apiClientStub)) + .stdout(); + + setupTest() + .command(['transaction:get']) + .catch(error => expect(error.message).to.contain('Missing 1 required arg')) + .it('should throw an error when arg is not provided'); + + describe('transaction:get transaction', () => { + const transaction = '3520445367460290306L'; + const queryResult = { + id: transaction, + name: 'i am owner', + }; + + setupTest() + .stub(query, 'default', sandbox.stub().resolves(queryResult)) + .command(['transaction:get', transaction]) + .it('should get an transaction info and display as an object', () => { + expect(api.default).to.be.calledWithExactly(apiConfig); + expect(query.default).to.be.calledWithExactly(apiClientStub, endpoint, { + limit: 1, + id: transaction, + }); + return expect(printMethodStub).to.be.calledWithExactly(queryResult); + }); + }); + + describe('transaction:get transactions', () => { + const transactions = ['3520445367460290306L', '2802325248134221536L']; + const queryResult = [ + { + id: transactions[0], + name: 'i am owner', + }, + { + id: transactions[1], + name: 'some name', + }, + ]; + + setupTest() + .stub(query, 'default', sandbox.stub().resolves(queryResult)) + .command(['transaction:get', transactions.join(',')]) + .it('should get transactions info and display as an array', () => { + expect(api.default).to.be.calledWithExactly(apiConfig); + expect(query.default).to.be.calledWithExactly(apiClientStub, endpoint, [ + { + limit: 1, + id: transactions[0], + }, + { + limit: 1, + id: transactions[1], + }, + ]); + return expect(printMethodStub).to.be.calledWithExactly(queryResult); + }); + }); +}); diff --git a/test/commands/transaction/sign.test.js b/test/commands/transaction/sign.test.js new file mode 100644 index 00000000..bbd1e703 --- /dev/null +++ b/test/commands/transaction/sign.test.js @@ -0,0 +1,287 @@ +/* + * LiskHQ/lisk-commander + * Copyright © 2017–2018 Lisk Foundation + * + * See the LICENSE file at the top-level directory of this distribution + * for licensing information. + * + * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, + * no part of this software, including this file, may be copied, modified, + * propagated, or distributed except according to the terms contained in the + * LICENSE file. + * + * Removal or modification of this copyright notice is prohibited. + * + */ +import { test } from '@oclif/test'; +import * as elements from 'lisk-elements'; +import * as config from '../../../src/utils/config'; +import * as print from '../../../src/utils/print'; +import * as inputUtils from '../../../src/utils/input/utils'; +import * as getInputsFromSources from '../../../src/utils/input'; + +describe('transaction:sign', () => { + const defaultTransaction = { + amount: '10000000000', + recipientId: '123L', + senderPublicKey: null, + timestamp: 66492418, + type: 0, + fee: '10000000', + recipientPublicKey: null, + asset: {}, + }; + const invalidTransaction = 'invalid transaction'; + const defaultInputs = { + passphrase: '123', + secondPassphrase: '456', + }; + + const defaultSignedTransaction = Object.assign({}, defaultTransaction, { + signature: 'signed', + }); + + const transactionUtilStub = { + prepareTransaction: sandbox.stub().returns(defaultSignedTransaction), + }; + + const printMethodStub = sandbox.stub(); + const setupTest = () => + test + .stub(print, 'default', sandbox.stub().returns(printMethodStub)) + .stub(config, 'getConfig', sandbox.stub().returns({})) + .stub(elements.default.transaction, 'utils', transactionUtilStub) + .stub( + getInputsFromSources, + 'default', + sandbox.stub().resolves(defaultInputs), + ) + .stdout(); + + describe('transaction:sign', () => { + setupTest() + .stub( + inputUtils, + 'getRawStdIn', + sandbox.stub().rejects(new Error('Timeout error')), + ) + .command(['transaction:sign']) + .catch(error => + expect(error.message).to.contain('No transaction was provided.'), + ) + .it('should throw an error'); + }); + + describe('transaction:sign transaction', () => { + setupTest() + .command(['transaction:sign', invalidTransaction]) + .catch(error => + expect(error.message).to.contain('Could not parse transaction JSON.'), + ) + .it('should throw an error'); + + setupTest() + .command(['transaction:sign', JSON.stringify(defaultTransaction)]) + .it('should take transaction from arg to sign', () => { + expect(getInputsFromSources.default).to.be.calledWithExactly({ + passphrase: { + source: undefined, + repeatPrompt: true, + }, + secondPassphrase: null, + }); + expect(transactionUtilStub.prepareTransaction).to.be.calledWithExactly( + defaultTransaction, + defaultInputs.passphrase, + defaultInputs.secondPassphrase, + ); + return expect(printMethodStub).to.be.calledWithExactly( + defaultSignedTransaction, + ); + }); + }); + + describe('transaction:sign transaction --passphrase=pass:123', () => { + setupTest() + .command([ + 'transaction:sign', + JSON.stringify(defaultTransaction), + '--passphrase=pass:123', + ]) + .it( + 'should take transaction from arg and passphrase from flag to sign', + () => { + expect(getInputsFromSources.default).to.be.calledWithExactly({ + passphrase: { + source: 'pass:123', + repeatPrompt: true, + }, + secondPassphrase: null, + }); + expect( + transactionUtilStub.prepareTransaction, + ).to.be.calledWithExactly( + defaultTransaction, + defaultInputs.passphrase, + defaultInputs.secondPassphrase, + ); + return expect(printMethodStub).to.be.calledWithExactly( + defaultSignedTransaction, + ); + }, + ); + }); + + describe('transaction:sign transaction --passphrase=pass:123 --second-passphrase=pass:456', () => { + setupTest() + .command([ + 'transaction:sign', + JSON.stringify(defaultTransaction), + '--passphrase=pass:123', + '--second-passphrase=pass:456', + ]) + .it( + 'should take transaction from arg and passphrase and second passphrase from flag to sign', + () => { + expect(getInputsFromSources.default).to.be.calledWithExactly({ + passphrase: { + source: 'pass:123', + repeatPrompt: true, + }, + secondPassphrase: { + source: 'pass:456', + repeatPrompt: true, + }, + }); + expect( + transactionUtilStub.prepareTransaction, + ).to.be.calledWithExactly( + defaultTransaction, + defaultInputs.passphrase, + defaultInputs.secondPassphrase, + ); + return expect(printMethodStub).to.be.calledWithExactly( + defaultSignedTransaction, + ); + }, + ); + }); + + describe('transaction | transaction:sign', () => { + setupTest() + .stub(inputUtils, 'getRawStdIn', sandbox.stub().resolves([])) + .command(['transaction:sign']) + .catch(error => + expect(error.message).to.contain('No transaction was provided.'), + ) + .it('should throw an error when stdin is empty'); + + setupTest() + .stub( + inputUtils, + 'getRawStdIn', + sandbox.stub().resolves([invalidTransaction]), + ) + .command(['transaction:sign']) + .catch(error => + expect(error.message).to.contain('Could not parse transaction JSON.'), + ) + .it('should throw an error when std is an invalid JSON format'); + + setupTest() + .stub( + inputUtils, + 'getRawStdIn', + sandbox.stub().resolves([JSON.stringify(defaultTransaction)]), + ) + .command(['transaction:sign']) + .it('should take transaction from stdin and sign', () => { + expect(getInputsFromSources.default).to.be.calledWithExactly({ + passphrase: { + source: undefined, + repeatPrompt: true, + }, + secondPassphrase: null, + }); + expect(transactionUtilStub.prepareTransaction).to.be.calledWithExactly( + defaultTransaction, + defaultInputs.passphrase, + defaultInputs.secondPassphrase, + ); + return expect(printMethodStub).to.be.calledWithExactly( + defaultSignedTransaction, + ); + }); + }); + + describe('transaction | transaction:sign --passphrase=pass:123', () => { + setupTest() + .stub( + inputUtils, + 'getRawStdIn', + sandbox.stub().resolves([JSON.stringify(defaultTransaction)]), + ) + .command(['transaction:sign', '--passphrase=pass:123']) + .it( + 'should take transaction from stdin and sign with passphrase from flag', + () => { + expect(getInputsFromSources.default).to.be.calledWithExactly({ + passphrase: { + source: 'pass:123', + repeatPrompt: true, + }, + secondPassphrase: null, + }); + expect( + transactionUtilStub.prepareTransaction, + ).to.be.calledWithExactly( + defaultTransaction, + defaultInputs.passphrase, + defaultInputs.secondPassphrase, + ); + return expect(printMethodStub).to.be.calledWithExactly( + defaultSignedTransaction, + ); + }, + ); + }); + + describe('transaction | transaction:sign --passphrase=pass:123 --second-passphrase=pass:456', () => { + setupTest() + .stub( + inputUtils, + 'getRawStdIn', + sandbox.stub().resolves([JSON.stringify(defaultTransaction)]), + ) + .command([ + 'transaction:sign', + '--passphrase=pass:abc', + '--second-passphrase=pass:def', + ]) + .it( + 'should take transaction from stdin and sign with passphrase and second passphrase from flag', + () => { + expect(getInputsFromSources.default).to.be.calledWithExactly({ + passphrase: { + source: 'pass:abc', + repeatPrompt: true, + }, + secondPassphrase: { + source: 'pass:def', + repeatPrompt: true, + }, + }); + expect( + transactionUtilStub.prepareTransaction, + ).to.be.calledWithExactly( + defaultTransaction, + defaultInputs.passphrase, + defaultInputs.secondPassphrase, + ); + return expect(printMethodStub).to.be.calledWithExactly( + defaultSignedTransaction, + ); + }, + ); + }); +}); diff --git a/test/commands/transaction/verify.test.js b/test/commands/transaction/verify.test.js new file mode 100644 index 00000000..b81c3d45 --- /dev/null +++ b/test/commands/transaction/verify.test.js @@ -0,0 +1,194 @@ +/* + * LiskHQ/lisk-commander + * Copyright © 2017–2018 Lisk Foundation + * + * See the LICENSE file at the top-level directory of this distribution + * for licensing information. + * + * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, + * no part of this software, including this file, may be copied, modified, + * propagated, or distributed except according to the terms contained in the + * LICENSE file. + * + * Removal or modification of this copyright notice is prohibited. + * + */ +import { test } from '@oclif/test'; +import * as elements from 'lisk-elements'; +import * as config from '../../../src/utils/config'; +import * as print from '../../../src/utils/print'; +import * as inputUtils from '../../../src/utils/input/utils'; + +describe('transaction:verify', () => { + const defaultTransaction = { + amount: '10000000000', + recipientId: '123L', + senderPublicKey: + 'a4465fd76c16fcc458448076372abf1912cc5b150663a64dffefe550f96feadd', + timestamp: 66419917, + type: 0, + fee: '10000000', + recipientPublicKey: null, + asset: {}, + signature: + '96738e173a750998f4c2cdcdf7538b71854bcffd6c0dc72b3c28081ca6946322bea7ba5d8f8974fc97950014347ce379671a6eddc0d41ea6cdfb9bb7ff76be0a', + id: '1297455432474089551', + }; + const defaultSecondPublicKey = + '790049f919979d5ea42cca7b7aa0812cbae8f0db3ee39c1fe3cef18e25b67951'; + const invalidTransaction = 'invalid transaction'; + + const defaultVerifyTransactionResult = { + verified: true, + }; + + const printMethodStub = sandbox.stub(); + const transactionUtilStub = { + verifyTransaction: sandbox.stub().returns(true), + }; + const setupTest = () => + test + .stub(print, 'default', sandbox.stub().returns(printMethodStub)) + .stub(config, 'getConfig', sandbox.stub().returns({})) + .stub(elements.default.transaction, 'utils', transactionUtilStub) + .stub( + inputUtils, + 'getData', + sandbox.stub().resolves(defaultSecondPublicKey), + ) + .stdout(); + + describe('transaction:verify', () => { + setupTest() + .stub( + inputUtils, + 'getRawStdIn', + sandbox.stub().rejects(new Error('Timeout error')), + ) + .command(['transaction:verify']) + .catch(error => + expect(error.message).to.contain('No transaction was provided.'), + ) + .it('should throw an error'); + }); + + describe('transaction:verify transaction', () => { + setupTest() + .command(['transaction:verify', invalidTransaction]) + .catch(error => + expect(error.message).to.contain('Could not parse transaction JSON.'), + ) + .it('should throw an error'); + + setupTest() + .command(['transaction:verify', JSON.stringify(defaultTransaction)]) + .it('should verify transaction from arg', () => { + expect(transactionUtilStub.verifyTransaction).to.be.calledWithExactly( + defaultTransaction, + null, + ); + return expect(printMethodStub).to.be.calledWithExactly( + defaultVerifyTransactionResult, + ); + }); + }); + + describe('transaction:verify transaction --second-public-key=xxx', () => { + setupTest() + .command([ + 'transaction:verify', + JSON.stringify(defaultTransaction), + '--second-public-key=file:key.txt', + ]) + .it('should verify transaction from arg', () => { + expect(inputUtils.getData).to.be.calledWithExactly('file:key.txt'); + expect(transactionUtilStub.verifyTransaction).to.be.calledWithExactly( + defaultTransaction, + defaultSecondPublicKey, + ); + return expect(printMethodStub).to.be.calledWithExactly( + defaultVerifyTransactionResult, + ); + }); + + setupTest() + .command([ + 'transaction:verify', + JSON.stringify(defaultTransaction), + '--second-public-key=some-second-public-key', + ]) + .it('should verify transaction from arg', () => { + expect(inputUtils.getData).not.to.be.called; + expect(transactionUtilStub.verifyTransaction).to.be.calledWithExactly( + defaultTransaction, + 'some-second-public-key', + ); + return expect(printMethodStub).to.be.calledWithExactly( + defaultVerifyTransactionResult, + ); + }); + }); + + describe('transaction | transaction:verify', () => { + setupTest() + .stub(inputUtils, 'getRawStdIn', sandbox.stub().resolves([])) + .command(['transaction:verify']) + .catch(error => + expect(error.message).to.contain('No transaction was provided.'), + ) + .it('should throw an error when no stdin was provided'); + + setupTest() + .stub( + inputUtils, + 'getRawStdIn', + sandbox.stub().resolves([invalidTransaction]), + ) + .command(['transaction:verify']) + .catch(error => + expect(error.message).to.contain('Could not parse transaction JSON.'), + ) + .it('should throw an error when invalid JSON format was provided'); + + setupTest() + .stub( + inputUtils, + 'getRawStdIn', + sandbox.stub().resolves([JSON.stringify(defaultTransaction)]), + ) + .command(['transaction:verify', JSON.stringify(defaultTransaction)]) + .it('should verify transaction from arg', () => { + expect(transactionUtilStub.verifyTransaction).to.be.calledWithExactly( + defaultTransaction, + null, + ); + return expect(printMethodStub).to.be.calledWithExactly( + defaultVerifyTransactionResult, + ); + }); + }); + + describe('transaction | transaction:verify --second-public-key=xxx', () => { + setupTest() + .stub( + inputUtils, + 'getRawStdIn', + sandbox.stub().resolves([JSON.stringify(defaultTransaction)]), + ) + .command([ + 'transaction:verify', + JSON.stringify(defaultTransaction), + '--second-public-key=file:key.txt', + ]) + .it('should verify transaction from arg', () => { + expect(inputUtils.getData).to.be.calledWithExactly('file:key.txt'); + expect(transactionUtilStub.verifyTransaction).to.be.calledWithExactly( + defaultTransaction, + defaultSecondPublicKey, + ); + return expect(printMethodStub).to.be.calledWithExactly( + defaultVerifyTransactionResult, + ); + }); + }); +}); From 3a59cd15dd696e810de9952842976fda89fac0e5 Mon Sep 17 00:00:00 2001 From: Shusetsu Toda Date: Tue, 11 Sep 2018 10:55:37 +0200 Subject: [PATCH 2/6] :recycle: Update error catch syntax --- test/commands/transaction/broadcast.test.js | 28 ++++++++++----------- test/commands/transaction/get.test.js | 4 ++- test/commands/transaction/sign.test.js | 28 ++++++++++++--------- test/commands/transaction/verify.test.js | 28 ++++++++++++--------- 4 files changed, 49 insertions(+), 39 deletions(-) diff --git a/test/commands/transaction/broadcast.test.js b/test/commands/transaction/broadcast.test.js index 8848635d..4ef56f91 100644 --- a/test/commands/transaction/broadcast.test.js +++ b/test/commands/transaction/broadcast.test.js @@ -64,20 +64,20 @@ describe('transaction:broadcast', () => { sandbox.stub().rejects(new Error('Timeout error')), ) .command(['transaction:broadcast']) - .catch(error => - expect(error.message).to.contain('No transaction was provided.'), - ) + .catch(error => { + return expect(error.message).to.contain('No transaction was provided.'); + }) .it('should throw an error without transaction'); }); describe('transaction:broadcast transaction', () => { setupTest() .command(['transaction:broadcast', wrongTransaction]) - .catch(error => - expect(error.message).to.contain( + .catch(error => { + return expect(error.message).to.contain( 'Could not parse transaction JSON. Did you use the `--json` option?', - ), - ) + ); + }) .it('should throw an error with invalid transaction'); setupTest() @@ -96,9 +96,9 @@ describe('transaction:broadcast', () => { setupTest() .stub(inputUtils, 'getRawStdIn', sandbox.stub().resolves([])) .command(['transaction:broadcast']) - .catch(error => - expect(error.message).to.contain('No transaction was provided.'), - ) + .catch(error => { + return expect(error.message).to.contain('No transaction was provided.'); + }) .it('should throw an error with invalid transaction from stdin'); setupTest() @@ -108,11 +108,11 @@ describe('transaction:broadcast', () => { sandbox.stub().resolves(wrongTransaction), ) .command(['transaction:broadcast']) - .catch(error => - expect(error.message).to.contain( + .catch(error => { + return expect(error.message).to.contain( 'Could not parse transaction JSON. Did you use the `--json` option?', - ), - ) + ); + }) .it('should throw an error with invalid transaction from stdin'); setupTest() diff --git a/test/commands/transaction/get.test.js b/test/commands/transaction/get.test.js index 58a4ae9d..3e842bb7 100644 --- a/test/commands/transaction/get.test.js +++ b/test/commands/transaction/get.test.js @@ -36,7 +36,9 @@ describe('transaction:get', () => { setupTest() .command(['transaction:get']) - .catch(error => expect(error.message).to.contain('Missing 1 required arg')) + .catch(error => { + return expect(error.message).to.contain('Missing 1 required arg'); + }) .it('should throw an error when arg is not provided'); describe('transaction:get transaction', () => { diff --git a/test/commands/transaction/sign.test.js b/test/commands/transaction/sign.test.js index bbd1e703..20359f74 100644 --- a/test/commands/transaction/sign.test.js +++ b/test/commands/transaction/sign.test.js @@ -66,18 +66,20 @@ describe('transaction:sign', () => { sandbox.stub().rejects(new Error('Timeout error')), ) .command(['transaction:sign']) - .catch(error => - expect(error.message).to.contain('No transaction was provided.'), - ) + .catch(error => { + return expect(error.message).to.contain('No transaction was provided.'); + }) .it('should throw an error'); }); describe('transaction:sign transaction', () => { setupTest() .command(['transaction:sign', invalidTransaction]) - .catch(error => - expect(error.message).to.contain('Could not parse transaction JSON.'), - ) + .catch(error => { + return expect(error.message).to.contain( + 'Could not parse transaction JSON.', + ); + }) .it('should throw an error'); setupTest() @@ -171,9 +173,9 @@ describe('transaction:sign', () => { setupTest() .stub(inputUtils, 'getRawStdIn', sandbox.stub().resolves([])) .command(['transaction:sign']) - .catch(error => - expect(error.message).to.contain('No transaction was provided.'), - ) + .catch(error => { + return expect(error.message).to.contain('No transaction was provided.'); + }) .it('should throw an error when stdin is empty'); setupTest() @@ -183,9 +185,11 @@ describe('transaction:sign', () => { sandbox.stub().resolves([invalidTransaction]), ) .command(['transaction:sign']) - .catch(error => - expect(error.message).to.contain('Could not parse transaction JSON.'), - ) + .catch(error => { + return expect(error.message).to.contain( + 'Could not parse transaction JSON.', + ); + }) .it('should throw an error when std is an invalid JSON format'); setupTest() diff --git a/test/commands/transaction/verify.test.js b/test/commands/transaction/verify.test.js index b81c3d45..187f4b08 100644 --- a/test/commands/transaction/verify.test.js +++ b/test/commands/transaction/verify.test.js @@ -66,18 +66,20 @@ describe('transaction:verify', () => { sandbox.stub().rejects(new Error('Timeout error')), ) .command(['transaction:verify']) - .catch(error => - expect(error.message).to.contain('No transaction was provided.'), - ) + .catch(error => { + return expect(error.message).to.contain('No transaction was provided.'); + }) .it('should throw an error'); }); describe('transaction:verify transaction', () => { setupTest() .command(['transaction:verify', invalidTransaction]) - .catch(error => - expect(error.message).to.contain('Could not parse transaction JSON.'), - ) + .catch(error => { + return expect(error.message).to.contain( + 'Could not parse transaction JSON.', + ); + }) .it('should throw an error'); setupTest() @@ -133,9 +135,9 @@ describe('transaction:verify', () => { setupTest() .stub(inputUtils, 'getRawStdIn', sandbox.stub().resolves([])) .command(['transaction:verify']) - .catch(error => - expect(error.message).to.contain('No transaction was provided.'), - ) + .catch(error => { + return expect(error.message).to.contain('No transaction was provided.'); + }) .it('should throw an error when no stdin was provided'); setupTest() @@ -145,9 +147,11 @@ describe('transaction:verify', () => { sandbox.stub().resolves([invalidTransaction]), ) .command(['transaction:verify']) - .catch(error => - expect(error.message).to.contain('Could not parse transaction JSON.'), - ) + .catch(error => { + return expect(error.message).to.contain( + 'Could not parse transaction JSON.', + ); + }) .it('should throw an error when invalid JSON format was provided'); setupTest() From 998956da7459a7186488cee4083d7d851ee10b5a Mon Sep 17 00:00:00 2001 From: Shusetsu Toda Date: Fri, 14 Sep 2018 10:58:47 +0200 Subject: [PATCH 3/6] :bug: Fix transaction:get command to be consistent with other get commands --- src/commands/transaction/get.js | 22 +++---- test/commands/transaction/get.test.js | 86 +++++++++++++++++++++------ 2 files changed, 79 insertions(+), 29 deletions(-) diff --git a/src/commands/transaction/get.js b/src/commands/transaction/get.js index 2dfe13b2..98c3af87 100644 --- a/src/commands/transaction/get.js +++ b/src/commands/transaction/get.js @@ -20,13 +20,16 @@ import query from '../../utils/query'; export default class GetCommand extends BaseCommand { async run() { const { args: { ids } } = this.parse(GetCommand); - const req = - ids.length === 1 - ? { limit: 1, id: ids[0] } - : ids.map(id => ({ - limit: 1, - id, - })); + const req = ids.map(id => ({ + query: { + limit: 1, + id, + }, + placeholder: { + id, + message: 'Transaction not found.', + }, + })); const client = getAPIClient(this.userConfig.api); const results = await query(client, 'transactions', req); this.print(results); @@ -37,9 +40,8 @@ GetCommand.args = [ { name: 'ids', required: true, - description: - 'Comma separated transaction id(s) which you want to get the information of.', - parse: input => input.split(','), + description: 'Comma-separated transaction ID(s) to get information about.', + parse: input => input.split(',').filter(Boolean), }, ]; diff --git a/test/commands/transaction/get.test.js b/test/commands/transaction/get.test.js index 3e842bb7..e5d8293a 100644 --- a/test/commands/transaction/get.test.js +++ b/test/commands/transaction/get.test.js @@ -42,54 +42,102 @@ describe('transaction:get', () => { .it('should throw an error when arg is not provided'); describe('transaction:get transaction', () => { - const transaction = '3520445367460290306L'; + const transactionId = '3520445367460290306'; const queryResult = { - id: transaction, - name: 'i am owner', + id: transactionId, + type: 2, }; setupTest() .stub(query, 'default', sandbox.stub().resolves(queryResult)) - .command(['transaction:get', transaction]) - .it('should get an transaction info and display as an object', () => { + .command(['transaction:get', transactionId]) + .it('should get an transaction info and display as an array', () => { expect(api.default).to.be.calledWithExactly(apiConfig); - expect(query.default).to.be.calledWithExactly(apiClientStub, endpoint, { - limit: 1, - id: transaction, - }); + expect(query.default).to.be.calledWithExactly(apiClientStub, endpoint, [ + { + query: { + limit: 1, + id: transactionId, + }, + placeholder: { + id: transactionId, + message: 'Transaction not found.', + }, + }, + ]); return expect(printMethodStub).to.be.calledWithExactly(queryResult); }); }); describe('transaction:get transactions', () => { - const transactions = ['3520445367460290306L', '2802325248134221536L']; + const transactionIds = ['3520445367460290306', '2802325248134221536']; + const transactionIdsWithEmpty = ['3520445367460290306', '']; const queryResult = [ { - id: transactions[0], - name: 'i am owner', + id: transactionIds[0], + type: 0, }, { - id: transactions[1], - name: 'some name', + id: transactionIds[1], + type: 3, }, ]; setupTest() .stub(query, 'default', sandbox.stub().resolves(queryResult)) - .command(['transaction:get', transactions.join(',')]) + .command(['transaction:get', transactionIds.join(',')]) .it('should get transactions info and display as an array', () => { expect(api.default).to.be.calledWithExactly(apiConfig); expect(query.default).to.be.calledWithExactly(apiClientStub, endpoint, [ { - limit: 1, - id: transactions[0], + query: { + limit: 1, + id: transactionIds[0], + }, + placeholder: { + id: transactionIds[0], + message: 'Transaction not found.', + }, }, { - limit: 1, - id: transactions[1], + query: { + limit: 1, + id: transactionIds[1], + }, + placeholder: { + id: transactionIds[1], + message: 'Transaction not found.', + }, }, ]); return expect(printMethodStub).to.be.calledWithExactly(queryResult); }); + + setupTest() + .stub(query, 'default', sandbox.stub().resolves(queryResult)) + .command(['transaction:get', transactionIdsWithEmpty.join(',')]) + .it( + 'should get transactions info only using non-empty args and display as an array', + () => { + expect(api.default).to.be.calledWithExactly(apiConfig); + expect(query.default).to.be.calledWithExactly( + apiClientStub, + endpoint, + [ + { + query: { + limit: 1, + id: transactionIdsWithEmpty[0], + }, + placeholder: { + id: transactionIdsWithEmpty[0], + message: 'Transaction not found.', + }, + }, + ], + ); + return expect(printMethodStub).to.be.calledWithExactly(queryResult); + }, + ); }); }); From 285f8295abea01e30859376a37721de31c8c9141 Mon Sep 17 00:00:00 2001 From: Shusetsu Toda Date: Mon, 17 Sep 2018 13:33:27 +0200 Subject: [PATCH 4/6] :recycle: Update tests phrases and utilize function --- src/commands/transaction/broadcast.js | 12 ++------ src/commands/transaction/sign.js | 10 ++----- src/commands/transaction/verify.js | 11 ++----- src/utils/transactions.js | 12 ++++++-- test/commands/transaction/broadcast.test.js | 4 +-- test/commands/transaction/get.test.js | 20 +++++++++++-- test/commands/transaction/sign.test.js | 11 +++---- test/commands/transaction/verify.test.js | 33 ++++++++++----------- 8 files changed, 60 insertions(+), 53 deletions(-) diff --git a/src/commands/transaction/broadcast.js b/src/commands/transaction/broadcast.js index bdf7853b..5643d305 100644 --- a/src/commands/transaction/broadcast.js +++ b/src/commands/transaction/broadcast.js @@ -14,6 +14,7 @@ * */ import BaseCommand from '../../base'; +import parseTransactionString from '../../utils/transactions'; import { ValidationError } from '../../utils/error'; import { getRawStdIn } from '../../utils/input/utils'; import getAPIClient from '../../utils/api'; @@ -35,14 +36,7 @@ export default class BroadcastCommand extends BaseCommand { const { args: { transaction } } = this.parse(BroadcastCommand); const transactionInput = transaction || (await getTransactionInput(transaction)); - let transactionObject; - try { - transactionObject = JSON.parse(transactionInput); - } catch (error) { - throw new ValidationError( - 'Could not parse transaction JSON. Did you use the `--json` option?', - ); - } + const transactionObject = parseTransactionString(transactionInput); const client = getAPIClient(this.userConfig.api); const response = await client.transactions.broadcast(transactionObject); this.print(response.data); @@ -52,7 +46,7 @@ export default class BroadcastCommand extends BaseCommand { BroadcastCommand.args = [ { name: 'transaction', - description: 'Transaction to broadcast.', + description: 'Transaction to broadcast in JSON format.', }, ]; diff --git a/src/commands/transaction/sign.js b/src/commands/transaction/sign.js index c720a033..69f8be0b 100644 --- a/src/commands/transaction/sign.js +++ b/src/commands/transaction/sign.js @@ -18,6 +18,7 @@ import { flags as flagParser } from '@oclif/command'; import BaseCommand from '../../base'; import { getRawStdIn } from '../../utils/input/utils'; import { ValidationError } from '../../utils/error'; +import parseTransactionString from '../../utils/transactions'; import getInputsFromSources from '../../utils/input'; import commonFlags from '../../utils/flags'; @@ -46,12 +47,7 @@ export default class SignCommand extends BaseCommand { const transactionInput = transaction || (await getTransactionInput(transaction)); - let transactionObject; - try { - transactionObject = JSON.parse(transactionInput); - } catch (error) { - throw new ValidationError('Could not parse transaction JSON.'); - } + const transactionObject = parseTransactionString(transactionInput); const { passphrase, secondPassphrase } = await getInputsFromSources({ passphrase: { @@ -79,7 +75,7 @@ export default class SignCommand extends BaseCommand { SignCommand.args = [ { name: 'transaction', - description: 'Transaction to sign.', + description: 'Transaction to sign in JSON format.', }, ]; diff --git a/src/commands/transaction/verify.js b/src/commands/transaction/verify.js index 57d4753b..e712043d 100644 --- a/src/commands/transaction/verify.js +++ b/src/commands/transaction/verify.js @@ -16,6 +16,7 @@ import elements from 'lisk-elements'; import { flags as flagParser } from '@oclif/command'; import BaseCommand from '../../base'; +import parseTransactionString from '../../utils/transactions'; import { getRawStdIn, getData } from '../../utils/input/utils'; import { ValidationError } from '../../utils/error'; @@ -51,13 +52,7 @@ export default class VerifyCommand extends BaseCommand { } = this.parse(VerifyCommand); const transactionInput = transaction || (await getTransactionInput()); - - let transactionObject; - try { - transactionObject = JSON.parse(transactionInput); - } catch (error) { - throw new ValidationError('Could not parse transaction JSON.'); - } + const transactionObject = parseTransactionString(transactionInput); const secondPublicKey = secondPublicKeySource ? await processSecondPublicKey(secondPublicKeySource) @@ -74,7 +69,7 @@ export default class VerifyCommand extends BaseCommand { VerifyCommand.args = [ { name: 'transaction', - description: 'Transaction to verify.', + description: 'Transaction to verify in JSON format.', }, ]; diff --git a/src/utils/transactions.js b/src/utils/transactions.js index 2a67e5c9..825a9008 100644 --- a/src/utils/transactions.js +++ b/src/utils/transactions.js @@ -13,6 +13,14 @@ * Removal or modification of this copyright notice is prohibited. * */ -import elements from 'lisk-elements'; +import { ValidationError } from './error'; -export default elements.transaction; +const parseTransactionString = transactionStr => { + try { + return JSON.parse(transactionStr); + } catch (error) { + throw new ValidationError('Could not parse transaction JSON.'); + } +}; + +export default parseTransactionString; diff --git a/test/commands/transaction/broadcast.test.js b/test/commands/transaction/broadcast.test.js index 4ef56f91..a6cd25bb 100644 --- a/test/commands/transaction/broadcast.test.js +++ b/test/commands/transaction/broadcast.test.js @@ -75,7 +75,7 @@ describe('transaction:broadcast', () => { .command(['transaction:broadcast', wrongTransaction]) .catch(error => { return expect(error.message).to.contain( - 'Could not parse transaction JSON. Did you use the `--json` option?', + 'Could not parse transaction JSON.', ); }) .it('should throw an error with invalid transaction'); @@ -110,7 +110,7 @@ describe('transaction:broadcast', () => { .command(['transaction:broadcast']) .catch(error => { return expect(error.message).to.contain( - 'Could not parse transaction JSON. Did you use the `--json` option?', + 'Could not parse transaction JSON.', ); }) .it('should throw an error with invalid transaction from stdin'); diff --git a/test/commands/transaction/get.test.js b/test/commands/transaction/get.test.js index e5d8293a..76eecdf7 100644 --- a/test/commands/transaction/get.test.js +++ b/test/commands/transaction/get.test.js @@ -51,7 +51,7 @@ describe('transaction:get', () => { setupTest() .stub(query, 'default', sandbox.stub().resolves(queryResult)) .command(['transaction:get', transactionId]) - .it('should get an transaction info and display as an array', () => { + .it('should get a transaction’s info and display as an array', () => { expect(api.default).to.be.calledWithExactly(apiConfig); expect(query.default).to.be.calledWithExactly(apiClientStub, endpoint, [ { @@ -71,7 +71,11 @@ describe('transaction:get', () => { describe('transaction:get transactions', () => { const transactionIds = ['3520445367460290306', '2802325248134221536']; - const transactionIdsWithEmpty = ['3520445367460290306', '']; + const transactionIdsWithEmpty = [ + '3520445367460290306', + '', + '2802325248134221536', + ]; const queryResult = [ { id: transactionIds[0], @@ -86,7 +90,7 @@ describe('transaction:get', () => { setupTest() .stub(query, 'default', sandbox.stub().resolves(queryResult)) .command(['transaction:get', transactionIds.join(',')]) - .it('should get transactions info and display as an array', () => { + .it('should get two transaction’s info and display as an array', () => { expect(api.default).to.be.calledWithExactly(apiConfig); expect(query.default).to.be.calledWithExactly(apiClientStub, endpoint, [ { @@ -134,6 +138,16 @@ describe('transaction:get', () => { message: 'Transaction not found.', }, }, + { + query: { + limit: 1, + id: transactionIdsWithEmpty[2], + }, + placeholder: { + id: transactionIdsWithEmpty[2], + message: 'Transaction not found.', + }, + }, ], ); return expect(printMethodStub).to.be.calledWithExactly(queryResult); diff --git a/test/commands/transaction/sign.test.js b/test/commands/transaction/sign.test.js index 20359f74..935c3e0d 100644 --- a/test/commands/transaction/sign.test.js +++ b/test/commands/transaction/sign.test.js @@ -38,7 +38,8 @@ describe('transaction:sign', () => { }; const defaultSignedTransaction = Object.assign({}, defaultTransaction, { - signature: 'signed', + signature: + 'c9c8a9a0d0ba1c8ee519792f286d751071de588448eb984ddd9fe4ea0fe34db474692407004047068dee785abca22a744203fb0342b5404349fa9d6abab1480d', }); const transactionUtilStub = { @@ -103,7 +104,7 @@ describe('transaction:sign', () => { }); }); - describe('transaction:sign transaction --passphrase=pass:123', () => { + describe('transaction:sign transaction --passphrase=pass:xxx', () => { setupTest() .command([ 'transaction:sign', @@ -134,7 +135,7 @@ describe('transaction:sign', () => { ); }); - describe('transaction:sign transaction --passphrase=pass:123 --second-passphrase=pass:456', () => { + describe('transaction:sign transaction --passphrase=pass:xxx --second-passphrase=pass:xxx', () => { setupTest() .command([ 'transaction:sign', @@ -218,7 +219,7 @@ describe('transaction:sign', () => { }); }); - describe('transaction | transaction:sign --passphrase=pass:123', () => { + describe('transaction | transaction:sign --passphrase=pass:xxx', () => { setupTest() .stub( inputUtils, @@ -250,7 +251,7 @@ describe('transaction:sign', () => { ); }); - describe('transaction | transaction:sign --passphrase=pass:123 --second-passphrase=pass:456', () => { + describe('transaction | transaction:sign --passphrase=pass:xxx --second-passphrase=pass:xxx', () => { setupTest() .stub( inputUtils, diff --git a/test/commands/transaction/verify.test.js b/test/commands/transaction/verify.test.js index 187f4b08..11c3b510 100644 --- a/test/commands/transaction/verify.test.js +++ b/test/commands/transaction/verify.test.js @@ -80,7 +80,7 @@ describe('transaction:verify', () => { 'Could not parse transaction JSON.', ); }) - .it('should throw an error'); + .it('should throw an error for invalid JSON'); setupTest() .command(['transaction:verify', JSON.stringify(defaultTransaction)]) @@ -160,7 +160,7 @@ describe('transaction:verify', () => { 'getRawStdIn', sandbox.stub().resolves([JSON.stringify(defaultTransaction)]), ) - .command(['transaction:verify', JSON.stringify(defaultTransaction)]) + .command(['transaction:verify']) .it('should verify transaction from arg', () => { expect(transactionUtilStub.verifyTransaction).to.be.calledWithExactly( defaultTransaction, @@ -179,20 +179,19 @@ describe('transaction:verify', () => { 'getRawStdIn', sandbox.stub().resolves([JSON.stringify(defaultTransaction)]), ) - .command([ - 'transaction:verify', - JSON.stringify(defaultTransaction), - '--second-public-key=file:key.txt', - ]) - .it('should verify transaction from arg', () => { - expect(inputUtils.getData).to.be.calledWithExactly('file:key.txt'); - expect(transactionUtilStub.verifyTransaction).to.be.calledWithExactly( - defaultTransaction, - defaultSecondPublicKey, - ); - return expect(printMethodStub).to.be.calledWithExactly( - defaultVerifyTransactionResult, - ); - }); + .command(['transaction:verify', '--second-public-key=file:key.txt']) + .it( + 'should verify transaction from stdIn and the second public key flag', + () => { + expect(inputUtils.getData).to.be.calledWithExactly('file:key.txt'); + expect(transactionUtilStub.verifyTransaction).to.be.calledWithExactly( + defaultTransaction, + defaultSecondPublicKey, + ); + return expect(printMethodStub).to.be.calledWithExactly( + defaultVerifyTransactionResult, + ); + }, + ); }); }); From a8182afa30b4e7107abafffaae2dfe03acaf0614 Mon Sep 17 00:00:00 2001 From: Shusetsu Toda Date: Mon, 17 Sep 2018 17:41:35 +0200 Subject: [PATCH 5/6] :recycle: Fis test suite phrases and add unit test for transactions util --- test/commands/transaction/broadcast.test.js | 7 ++- test/commands/transaction/get.test.js | 2 +- test/commands/transaction/verify.test.js | 50 ++++++++++--------- test/utils/transactions.js | 53 +++++++++++++++++++++ 4 files changed, 88 insertions(+), 24 deletions(-) create mode 100644 test/utils/transactions.js diff --git a/test/commands/transaction/broadcast.test.js b/test/commands/transaction/broadcast.test.js index a6cd25bb..25380be2 100644 --- a/test/commands/transaction/broadcast.test.js +++ b/test/commands/transaction/broadcast.test.js @@ -20,6 +20,10 @@ import * as api from '../../../src/utils/api'; import * as inputUtils from '../../../src/utils/input/utils'; describe('transaction:broadcast', () => { + const apiConfig = { + nodes: ['http://local.host'], + network: 'main', + }; const defaultTransaction = { amount: '10000000000', recipientId: '123L', @@ -52,7 +56,7 @@ describe('transaction:broadcast', () => { const setupTest = () => test .stub(print, 'default', sandbox.stub().returns(printMethodStub)) - .stub(config, 'getConfig', sandbox.stub().returns({})) + .stub(config, 'getConfig', sandbox.stub().returns({ api: apiConfig })) .stub(api, 'default', sandbox.stub().returns(apiClientStub)) .stdout(); @@ -83,6 +87,7 @@ describe('transaction:broadcast', () => { setupTest() .command(['transaction:broadcast', JSON.stringify(defaultTransaction)]) .it('should broadcast the transaction', () => { + expect(api.default).to.be.calledWithExactly(apiConfig); expect(apiClientStub.transactions.broadcast).to.be.calledWithExactly( defaultTransaction, ); diff --git a/test/commands/transaction/get.test.js b/test/commands/transaction/get.test.js index 76eecdf7..f45a7897 100644 --- a/test/commands/transaction/get.test.js +++ b/test/commands/transaction/get.test.js @@ -90,7 +90,7 @@ describe('transaction:get', () => { setupTest() .stub(query, 'default', sandbox.stub().resolves(queryResult)) .command(['transaction:get', transactionIds.join(',')]) - .it('should get two transaction’s info and display as an array', () => { + .it('should get two transactions’ info and display as an array', () => { expect(api.default).to.be.calledWithExactly(apiConfig); expect(query.default).to.be.calledWithExactly(apiClientStub, endpoint, [ { diff --git a/test/commands/transaction/verify.test.js b/test/commands/transaction/verify.test.js index 11c3b510..a68168cc 100644 --- a/test/commands/transaction/verify.test.js +++ b/test/commands/transaction/verify.test.js @@ -102,16 +102,19 @@ describe('transaction:verify', () => { JSON.stringify(defaultTransaction), '--second-public-key=file:key.txt', ]) - .it('should verify transaction from arg', () => { - expect(inputUtils.getData).to.be.calledWithExactly('file:key.txt'); - expect(transactionUtilStub.verifyTransaction).to.be.calledWithExactly( - defaultTransaction, - defaultSecondPublicKey, - ); - return expect(printMethodStub).to.be.calledWithExactly( - defaultVerifyTransactionResult, - ); - }); + .it( + 'should verify transaction from arg and second public key from an external source', + () => { + expect(inputUtils.getData).to.be.calledWithExactly('file:key.txt'); + expect(transactionUtilStub.verifyTransaction).to.be.calledWithExactly( + defaultTransaction, + defaultSecondPublicKey, + ); + return expect(printMethodStub).to.be.calledWithExactly( + defaultVerifyTransactionResult, + ); + }, + ); setupTest() .command([ @@ -119,16 +122,19 @@ describe('transaction:verify', () => { JSON.stringify(defaultTransaction), '--second-public-key=some-second-public-key', ]) - .it('should verify transaction from arg', () => { - expect(inputUtils.getData).not.to.be.called; - expect(transactionUtilStub.verifyTransaction).to.be.calledWithExactly( - defaultTransaction, - 'some-second-public-key', - ); - return expect(printMethodStub).to.be.calledWithExactly( - defaultVerifyTransactionResult, - ); - }); + .it( + 'should verify transaction from arg and second public key from the flag', + () => { + expect(inputUtils.getData).not.to.be.called; + expect(transactionUtilStub.verifyTransaction).to.be.calledWithExactly( + defaultTransaction, + 'some-second-public-key', + ); + return expect(printMethodStub).to.be.calledWithExactly( + defaultVerifyTransactionResult, + ); + }, + ); }); describe('transaction | transaction:verify', () => { @@ -161,7 +167,7 @@ describe('transaction:verify', () => { sandbox.stub().resolves([JSON.stringify(defaultTransaction)]), ) .command(['transaction:verify']) - .it('should verify transaction from arg', () => { + .it('should verify transaction from stdin', () => { expect(transactionUtilStub.verifyTransaction).to.be.calledWithExactly( defaultTransaction, null, @@ -181,7 +187,7 @@ describe('transaction:verify', () => { ) .command(['transaction:verify', '--second-public-key=file:key.txt']) .it( - 'should verify transaction from stdIn and the second public key flag', + 'should verify transaction from stdin and the second public key flag', () => { expect(inputUtils.getData).to.be.calledWithExactly('file:key.txt'); expect(transactionUtilStub.verifyTransaction).to.be.calledWithExactly( diff --git a/test/utils/transactions.js b/test/utils/transactions.js new file mode 100644 index 00000000..3d811359 --- /dev/null +++ b/test/utils/transactions.js @@ -0,0 +1,53 @@ +/* + * LiskHQ/lisk-commander + * Copyright © 2017–2018 Lisk Foundation + * + * See the LICENSE file at the top-level directory of this distribution + * for licensing information. + * + * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, + * no part of this software, including this file, may be copied, modified, + * propagated, or distributed except according to the terms contained in the + * LICENSE file. + * + * Removal or modification of this copyright notice is prohibited. + * + */ +import parseTransactionString from '../../src/utils/transactions'; +import { ValidationError } from '../../src/utils/error'; + +describe('transactions utils', () => { + describe('#parseTransactionString', () => { + const validTransactionString = + '{"amount":"10000000000","recipientId":"100L","senderPublicKey":"a4465fd76c16fcc458448076372abf1912cc5b150663a64dffefe550f96feadd","timestamp":73074942,"type":0,"fee":"10000000","recipientPublicKey":null,"asset":{},"signature":"304d375ea6230eaf222da6041d8553be5214ef6e399869e686e970eb5c3d9642217e68494c778c9c7f77c11cc1cb0b92df4bd4fa25e0149e2009a225369ffc01","id":"10769065659474020955"}'; + + it('should parse transaction string into a object', () => { + return expect(parseTransactionString(validTransactionString)).to.be.an( + 'object', + ); + }); + + it('should throw an validation error when the input is not valid JSON format', () => { + return expect( + parseTransactionString.bind( + null, + '{"amount":"10000000000","recipientId":"100L"', + ), + ).to.throw(ValidationError, 'Could not parse transaction JSON.'); + }); + + it('should throw an validation error when the input is empty', () => { + return expect(parseTransactionString.bind(null, '')).to.throw( + ValidationError, + 'Could not parse transaction JSON.', + ); + }); + + it('should throw an validation error when the input is undefined', () => { + return expect(parseTransactionString.bind(null)).to.throw( + ValidationError, + 'Could not parse transaction JSON.', + ); + }); + }); +}); From d4e1553da8c682c86ed4dac4c2a21f64d4a0d50d Mon Sep 17 00:00:00 2001 From: Shusetsu Toda Date: Wed, 19 Sep 2018 12:15:22 +0200 Subject: [PATCH 6/6] :recycle: Fix test phrase and add api test --- test/commands/transaction/broadcast.test.js | 1 + test/utils/transactions.js | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/test/commands/transaction/broadcast.test.js b/test/commands/transaction/broadcast.test.js index 25380be2..7448a663 100644 --- a/test/commands/transaction/broadcast.test.js +++ b/test/commands/transaction/broadcast.test.js @@ -128,6 +128,7 @@ describe('transaction:broadcast', () => { ) .command(['transaction:broadcast']) .it('should broadcast the transaction', () => { + expect(api.default).to.be.calledWithExactly(apiConfig); expect(apiClientStub.transactions.broadcast).to.be.calledWithExactly( defaultTransaction, ); diff --git a/test/utils/transactions.js b/test/utils/transactions.js index 3d811359..b2fd08f3 100644 --- a/test/utils/transactions.js +++ b/test/utils/transactions.js @@ -27,7 +27,7 @@ describe('transactions utils', () => { ); }); - it('should throw an validation error when the input is not valid JSON format', () => { + it('should throw a validation error when the input is not valid JSON format', () => { return expect( parseTransactionString.bind( null, @@ -36,14 +36,14 @@ describe('transactions utils', () => { ).to.throw(ValidationError, 'Could not parse transaction JSON.'); }); - it('should throw an validation error when the input is empty', () => { + it('should throw a validation error when the input is empty', () => { return expect(parseTransactionString.bind(null, '')).to.throw( ValidationError, 'Could not parse transaction JSON.', ); }); - it('should throw an validation error when the input is undefined', () => { + it('should throw a validation error when the input is undefined', () => { return expect(parseTransactionString.bind(null)).to.throw( ValidationError, 'Could not parse transaction JSON.',