Skip to content

Commit

Permalink
Add form for getting account balance arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
dmytrostriletskyi committed Apr 19, 2019
1 parent 21a83bb commit 0f01dc7
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 10 deletions.
24 changes: 16 additions & 8 deletions cli/account/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,22 @@
Provide implementation of the command line interface's account commands.
"""
import asyncio
import re
import sys

import click
from remme import Remme

from cli.account.forms import GetAccountBalanceForm
from cli.account.help import GET_ACCOUNT_BALANCE_ADDRESS_ARGUMENT_HELP_MESSAGE
from cli.account.service import Account
from cli.constants import (
ADDRESS_REGEXP,
FAILED_EXIT_FROM_COMMAND_CODE,
NODE_URL_ARGUMENT_HELP_MESSAGE,
)
from cli.utils import (
default_node_url,
dict_to_pretty_json,
)

loop = asyncio.get_event_loop()

Expand All @@ -28,20 +31,25 @@ def account_commands():


@click.option('--address', type=str, required=True, help=GET_ACCOUNT_BALANCE_ADDRESS_ARGUMENT_HELP_MESSAGE)
@click.option('--node-url', type=str, required=False, help=NODE_URL_ARGUMENT_HELP_MESSAGE)
@click.option('--node-url', type=str, required=False, help=NODE_URL_ARGUMENT_HELP_MESSAGE, default=default_node_url())
@account_commands.command('get-balance')
def get_balance(address, node_url):
"""
Get balance of the account by its address.
"""
if re.match(pattern=ADDRESS_REGEXP, string=address) is None:
click.echo(f'The following address `{address}` is invalid.')
arguments, errors = GetAccountBalanceForm().load({
'address': address,
'node_url': node_url,
})

if errors:
click.echo(dict_to_pretty_json(errors))
sys.exit(FAILED_EXIT_FROM_COMMAND_CODE)

if node_url is None:
node_url = 'localhost'
address = arguments.get('address')
node_url = arguments.get('node_url')

remme = Remme(private_key_hex=None, network_config={
remme = Remme(network_config={
'node_address': str(node_url) + ':8080',
})

Expand Down
49 changes: 49 additions & 0 deletions cli/account/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""
Provide forms for command line interface's account commands.
"""
import re

from marshmallow import (
Schema,
ValidationError,
fields,
validates,
)

from cli.constants import (
ADDRESS_REGEXP,
DOMAIN_NAME_REGEXP,
)


class GetAccountBalanceForm(Schema):
"""
Get balance of the account form.
"""

address = fields.String(required=True)
node_url = fields.String(allow_none=True, required=False)

@validates('address')
def validate_address(self, address):
"""
Validate account address.
"""
if re.match(pattern=ADDRESS_REGEXP, string=address) is None:
raise ValidationError(f'The following address `{address}` is invalid.')

@validates('node_url')
def validate_node_url(self, node_url):
"""
Validate node URL.
If node URL is localhost, it means client didn't passed any URL, so nothing to validate.
"""
if node_url is 'localhost':
return

if 'http' in node_url or 'https' in node_url:
raise ValidationError(f'Pass the following node URL `{node_url}` without protocol (http, https, etc.).')

if re.match(pattern=DOMAIN_NAME_REGEXP, string=node_url) is None:
raise ValidationError(f'The following node URL `{node_url}` is invalid.')
3 changes: 2 additions & 1 deletion cli/constants.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""
Provide constants for command line interface.
"""
ADDRESS_REGEXP = '^[0-9a-f]{70}$'
ADDRESS_REGEXP = r'^[0-9a-f]{70}$'
DOMAIN_NAME_REGEXP = r'(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]'

PASSED_EXIT_FROM_COMMAND_CODE = 0
FAILED_EXIT_FROM_COMMAND_CODE = -1
Expand Down
23 changes: 23 additions & 0 deletions cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,26 @@ def dict_to_pretty_json(data):
Convert dictionary to string with indents as human readable text.
"""
return json.dumps(data, indent=4, sort_keys=True)


def default_node_url():
"""
Get default node URL.
"""
return 'localhost'


async def return_async_value(value):
"""
Asynchronous function return value impostor.
Using for mock particular asynchronous function with specified return value.
Example of usage in code:
mock_account_get_balance = mock.patch('cli.account.service.Account.get_balance')
mock_account_get_balance.return_value = return_async_value(13500)
References:
- https://github.com/pytest-dev/pytest-mock/issues/60
"""
return value
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ accessify==0.3.1
asyncio==3.4.3
click==7.0
remme==1.0.0
marshmallow==2.19.2
116 changes: 115 additions & 1 deletion tests/test_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
PASSED_EXIT_FROM_COMMAND_CODE,
)
from cli.entrypoint import cli
from cli.utils import (
dict_to_pretty_json,
return_async_value,
)


def test_get_balance():
Expand Down Expand Up @@ -49,5 +53,115 @@ def test_get_balance_invalid_address():
NODE_IP_ADDRESS_FOR_TESTING,
])

expected_error = {
'address': [
f'The following address `{invalid_address}` is invalid.',
],
}

assert FAILED_EXIT_FROM_COMMAND_CODE == result.exit_code
assert dict_to_pretty_json(expected_error) in result.output


def test_get_balance_without_node_url(mocker):
"""
Case: get a balance of an account by address without passing node URL.
Expect: balance is returned.
"""
balance_from_localhost = 13500

mock_account_get_balance = mocker.patch('cli.account.service.Account.get_balance')
mock_account_get_balance.return_value = return_async_value(balance_from_localhost)

runner = CliRunner()
result = runner.invoke(cli, [
'account',
'get-balance',
'--address',
'1120076ecf036e857f42129b58303bcf1e03723764a1702cbe98529802aad8514ee3cf',
])

assert PASSED_EXIT_FROM_COMMAND_CODE == result.exit_code
assert True is isinstance(json.loads(result.output), int)
assert str(balance_from_localhost) in result.output


def test_get_balance_invalid_node_url():
"""
Case: get a balance of an account by passing invalid node URL.
Expect: the following node URL is invalid error message.
"""
invalid_node_url = 'domainwithoutextention'

runner = CliRunner()
result = runner.invoke(cli, [
'account',
'get-balance',
'--address',
'1120076ecf036e857f42129b58303bcf1e03723764a1702cbe98529802aad8514ee3cf',
'--node-url',
invalid_node_url,
])

expected_error = {
'node_url': [
f'The following node URL `{invalid_node_url}` is invalid.',
],
}

assert FAILED_EXIT_FROM_COMMAND_CODE == result.exit_code
assert dict_to_pretty_json(expected_error) in result.output


def test_get_balance_node_url_with_http():
"""
Case: get a balance of an account by passing node URL with explicit HTTP protocol.
Expect: the following node URL contains protocol error message.
"""
node_url_with_http_protocol = 'http://masternode.com'

runner = CliRunner()
result = runner.invoke(cli, [
'account',
'get-balance',
'--address',
'1120076ecf036e857f42129b58303bcf1e03723764a1702cbe98529802aad8514ee3cf',
'--node-url',
node_url_with_http_protocol,
])

expected_error = {
'node_url': [
f'Pass the following node URL `{node_url_with_http_protocol}` without protocol (http, https, etc.).',
],
}

assert FAILED_EXIT_FROM_COMMAND_CODE == result.exit_code
assert dict_to_pretty_json(expected_error) in result.output


def test_get_balance_node_url_with_https():
"""
Case: get a balance of an account by passing node URL with explicit HTTPS protocol.
Expect: the following node URL contains protocol error message.
"""
node_url_with_https_protocol = 'https://masternode.com'

runner = CliRunner()
result = runner.invoke(cli, [
'account',
'get-balance',
'--address',
'1120076ecf036e857f42129b58303bcf1e03723764a1702cbe98529802aad8514ee3cf',
'--node-url',
node_url_with_https_protocol,
])

expected_error = {
'node_url': [
f'Pass the following node URL `{node_url_with_https_protocol}` without protocol (http, https, etc.).',
],
}

assert FAILED_EXIT_FROM_COMMAND_CODE == result.exit_code
assert f'The following address `{invalid_address}` is invalid.' in result.output
assert dict_to_pretty_json(expected_error) in result.output

0 comments on commit 0f01dc7

Please sign in to comment.