From 079d4475b0c70aa0d52772437056faed32297ffb Mon Sep 17 00:00:00 2001 From: Anastasiia Bilova Date: Thu, 2 May 2019 10:39:15 +0300 Subject: [PATCH] Get information about public key by public key address (#17) --- README.md | 31 ++- cli/constants.py | 5 +- cli/generic/forms/fields.py | 21 ++ cli/public_key/cli.py | 48 +++- cli/public_key/forms.py | 10 + cli/public_key/help.py | 3 +- cli/public_key/interfaces.py | 6 + cli/public_key/service.py | 22 +- setup.cfg | 2 +- tests/conftest.py | 45 ++++ ...ys.py => test_get_public_key_addresses.py} | 10 +- tests/public_key/test_get_public_key_info.py | 239 ++++++++++++++++++ 12 files changed, 426 insertions(+), 16 deletions(-) rename tests/public_key/{test_get_public_keys.py => test_get_public_key_addresses.py} (96%) create mode 100644 tests/public_key/test_get_public_key_info.py diff --git a/README.md b/README.md index e8d4122..49da8f2 100644 --- a/README.md +++ b/README.md @@ -195,7 +195,7 @@ $ remme public-key get-list \ --node-url=node-genesis-testnet.remme.io { "result": { - "public_key_addresses": [ + "addresses": [ "a23be10b3aad1b4a98f338c71d6dcdb2aa2f296c7e31fb400615e335dc10dd1d4f62bf", "a23be14b362514d624c1985277005327f6fc40413fb090eee6fccb673a32c9809060ff" ] @@ -203,6 +203,35 @@ $ remme public-key get-list \ } ``` +Get information about public key by its address — ``remme public-key get-info``: + +| Arguments | Type | Required | Description | +| :-------: | :----: | :------: | ---------------------------------------------------------- | +| address | String | Yes | Public key address to get information about public key by. | +| node-url | String | No | Node URL to apply a command to. | + +```bash +$ remme public-key get-info \ + --address=a23be17addad8eeb5177a395ea47eb54b4a646f8c570f4a2ecc0b1d2f6241c6845181b \ + --node-url=node-genesis-testnet.remme.io +{ + "result": { + "information": { + "address": "a23be10d215132aee9377cfe26b6d301d32da070a799c227fb4701103e5626d48cd6ba", + "entity_hash": "1edd6d5b1c722a83e03b17180b888d89ec4c079a0044f074b7c8bb2720cad8ba4e97a80c7edbd24c1824f5312dfd8a0877453394a63410b52c1f16e1d60ef754", + "entity_hash_signature": "1322ca51fb6d33e44d2b6c028eb668b5712a5277bbdea089112203e8e950d1c7d02d446291865a2f5fca4c6767fb84583e53205df850f1fc05ea6f22c736635f425b0159881f7f998da52378bf08353d87d2a2c226a7ababea9a245e69be06d54c573a42c3be907ca49589a67b5e9cc4d8ed12cea8546b2df531fd9620f4dc71869d8fa0bfcbef239d9a6e2e3bf12bcac4fd562b22ff408d7b077b75d8e59af0348264a7c9e7e61b4c5f844636a0fbbcfae61955efdf10323a992ea2a1734eb0ee7952519b00e696a02e7460771b0e0887e011b709e88abfda896b68150c08dcf6b4bf7c70f996f6031c13311056ab935ce1fdf63d3f19b5a3ca6ae604c4f12b", + "is_revoked": false, + "is_valid": true, + "owner_public_key": "03738df3f4ac3621ba8e89413d3ff4ad036c3a0a4dbb164b695885aab6aab614ad", + "public_key": "30820122300d06092a864886f70d01010105000382010f003082010a02820101008b29cc5ec32dab21b48b63faf2fd00f88879b9d4286c3cde6218d19263ea8226fce499039968c5f9736149e298bbc56680b516f2d83507d88fb95771445ca3c59bcdbb31bb5993a4e5dfcd2c4bc86328ec76e95e2f4582f9cac8223a2f16a2b14c4358b6fb105e37baf9daa9bd5b708ab204d8015a1ce782e28024eae1801151616c90a3b1aa1916d5b8dd021b3aa4cec77450660841f8619a7234c6199d01ccd43b1d6ff7fa5f50bf80bc06b682b126bdca0753a6830b7a95afca79442ec64fd09ddcc34627dcbdad0c5e66317db98d0e1c24c3f992b83f4b0f97e2b0300a2cb51e33eccf060f26b4e19a88f15216f8c17be5f5e023a1f260f7c93a2a4523ed0203010001", + "type": "rsa", + "valid_from": 1556118334, + "valid_to": 1587222334 + } + } +} +``` + ## Development

Requirements

diff --git a/cli/constants.py b/cli/constants.py index dfdabf5..d8821fb 100644 --- a/cli/constants.py +++ b/cli/constants.py @@ -3,8 +3,9 @@ """ ADDRESS_REGEXP = r'^[0-9a-f]{70}$' BATCH_ID_REGEXP = r'^[0-9a-f]{128}$' -PUBLIC_KEY_REGEXP = r'^[0-9a-f]{66}$' PRIVATE_KEY_REGEXP = r'^[a-f0-9]{64}$' +PUBLIC_KEY_REGEXP = r'^[0-9a-f]{66}$' +PUBLIC_KEY_ADDRESS_REGEXP = r'^[0-9a-f]{70}$' HEADER_SIGNATURE_REGEXP = r'^[0-9a-f]{128}$' 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]' @@ -12,7 +13,7 @@ FAILED_EXIT_FROM_COMMAND_CODE = -1 INCORRECT_ENTERED_COMMAND_CODE = 2 -NODE_URL_ARGUMENT_HELP_MESSAGE = 'Apply the command to the specified node by its URL.' +NODE_URL_ARGUMENT_HELP_MESSAGE = 'Node URL to apply a command to.' CLI_CONFIG_FILE_NAME = 'remme-core-cli' diff --git a/cli/generic/forms/fields.py b/cli/generic/forms/fields.py index 0799e05..ab8f582 100644 --- a/cli/generic/forms/fields.py +++ b/cli/generic/forms/fields.py @@ -12,6 +12,7 @@ ADDRESS_REGEXP, DOMAIN_NAME_REGEXP, PRIVATE_KEY_REGEXP, + PUBLIC_KEY_ADDRESS_REGEXP, ) @@ -81,3 +82,23 @@ def _deserialize(self, value, attr, data, **kwargs): raise ValidationError(f'The following private key `{private_key}` is invalid.') return value + + +class PublicKeyAddressField(fields.Field): + """ + Implements validation of the public key address. + + References: + - https://marshmallow.readthedocs.io/en/3.0/custom_fields.html + """ + + def _deserialize(self, value, attr, data, **kwargs): + """ + Validate data (public key address) that was passed to field. + """ + public_key_address = value + + if re.match(pattern=PUBLIC_KEY_ADDRESS_REGEXP, string=public_key_address) is None: + raise ValidationError(f'The following public key address `{public_key_address}` is invalid.') + + return value diff --git a/cli/public_key/cli.py b/cli/public_key/cli.py index 7daadd2..553bf43 100644 --- a/cli/public_key/cli.py +++ b/cli/public_key/cli.py @@ -10,8 +10,14 @@ FAILED_EXIT_FROM_COMMAND_CODE, NODE_URL_ARGUMENT_HELP_MESSAGE, ) -from cli.public_key.forms import GetPublicKeysForm -from cli.public_key.help import ADDRESS_ARGUMENT_HELP_MESSAGE +from cli.public_key.forms import ( + GetPublicKeyInformationForm, + GetPublicKeysForm, +) +from cli.public_key.help import ( + ACCOUNT_ADDRESS_ARGUMENT_HELP_MESSAGE, + PUBLIC_KEY_ADDRESS_ARGUMENT_HELP_MESSAGE, +) from cli.public_key.service import PublicKey from cli.utils import ( default_node_url, @@ -28,7 +34,39 @@ def public_key_commands(): pass -@click.option('--address', type=str, required=True, help=ADDRESS_ARGUMENT_HELP_MESSAGE) +@click.option('--address', type=str, required=True, help=PUBLIC_KEY_ADDRESS_ARGUMENT_HELP_MESSAGE) +@click.option('--node-url', type=str, required=False, help=NODE_URL_ARGUMENT_HELP_MESSAGE, default=default_node_url()) +@public_key_commands.command('get-info') +def get_public_key_info(address, node_url): + """ + Get information about public key by its address. + """ + arguments, errors = GetPublicKeyInformationForm().load({ + 'address': address, + 'node_url': node_url, + }) + + if errors: + print_errors(errors) + sys.exit(FAILED_EXIT_FROM_COMMAND_CODE) + + public_key_address = arguments.get('address') + node_url = arguments.get('node_url') + + remme = Remme(network_config={ + 'node_address': str(node_url) + ':8080', + }) + + result, errors = PublicKey(service=remme).get(address=public_key_address) + + if errors is not None: + print_errors(errors=errors) + sys.exit(FAILED_EXIT_FROM_COMMAND_CODE) + + print_result(result=result) + + +@click.option('--address', type=str, required=True, help=ACCOUNT_ADDRESS_ARGUMENT_HELP_MESSAGE) @click.option('--node-url', type=str, required=False, help=NODE_URL_ARGUMENT_HELP_MESSAGE, default=default_node_url()) @public_key_commands.command('get-list') def get_public_keys(address, node_url): @@ -44,14 +82,14 @@ def get_public_keys(address, node_url): print_errors(errors) sys.exit(FAILED_EXIT_FROM_COMMAND_CODE) - address = arguments.get('address') + account_address = arguments.get('address') node_url = arguments.get('node_url') remme = Remme(network_config={ 'node_address': str(node_url) + ':8080', }) - result, errors = PublicKey(service=remme).get_list(address=address) + result, errors = PublicKey(service=remme).get_list(address=account_address) if errors is not None: print_errors(errors=errors) diff --git a/cli/public_key/forms.py b/cli/public_key/forms.py index c840a8d..5fe3c95 100644 --- a/cli/public_key/forms.py +++ b/cli/public_key/forms.py @@ -6,9 +6,19 @@ from cli.generic.forms.fields import ( AccountAddressField, NodeURLField, + PublicKeyAddressField, ) +class GetPublicKeyInformationForm(Schema): + """ + Get information about public key of the public key information form. + """ + + address = PublicKeyAddressField(required=True) + node_url = NodeURLField(allow_none=True, required=False) + + class GetPublicKeysForm(Schema): """ Get a list of the addresses of the public keys form. diff --git a/cli/public_key/help.py b/cli/public_key/help.py index a426d5b..6a6061b 100644 --- a/cli/public_key/help.py +++ b/cli/public_key/help.py @@ -1,4 +1,5 @@ """ Provide help messages for command line interface's public key commands. """ -ADDRESS_ARGUMENT_HELP_MESSAGE = 'Account address to get a list of the addresses of the public keys by.' +ACCOUNT_ADDRESS_ARGUMENT_HELP_MESSAGE = 'Account address to get a list of the addresses of the public keys by.' +PUBLIC_KEY_ADDRESS_ARGUMENT_HELP_MESSAGE = 'Public key address to get information about public key by.' diff --git a/cli/public_key/interfaces.py b/cli/public_key/interfaces.py index 5687836..c060995 100644 --- a/cli/public_key/interfaces.py +++ b/cli/public_key/interfaces.py @@ -8,6 +8,12 @@ class PublicKeyInterface: Implements public key interface. """ + def get(self, address): + """ + Get information about public key by its address. + """ + pass + def get_list(self, address): """ Get a list of the addresses of the public keys by account address. diff --git a/cli/public_key/service.py b/cli/public_key/service.py index 14e4515..a582e1d 100644 --- a/cli/public_key/service.py +++ b/cli/public_key/service.py @@ -4,6 +4,7 @@ import asyncio from accessify import implements +from aiohttp_json_rpc import RpcGenericServerDefinedError from cli.public_key.interfaces import PublicKeyInterface @@ -25,6 +26,25 @@ def __init__(self, service): """ self.service = service + def get(self, address): + """ + Get information about public key by its address. + """ + try: + public_key_info = loop.run_until_complete( + self.service.public_key_storage.get_info(public_key_address=address), + ) + + except RpcGenericServerDefinedError as error: + return None, str(error.message) + + except Exception as error: + return None, str(error) + + return { + 'information': public_key_info.data, + }, None + def get_list(self, address): """ Get a list of the addresses of the public keys by account address. @@ -38,5 +58,5 @@ def get_list(self, address): return None, str(error) return { - 'public_key_addresses': public_key_addresses, + 'addresses': public_key_addresses, }, None diff --git a/setup.cfg b/setup.cfg index 1318256..792d491 100644 --- a/setup.cfg +++ b/setup.cfg @@ -9,7 +9,7 @@ combine_as_imports=True max-line-length=120 ignore=D200, D413, D107, D100 per-file-ignores= - */__init__.py: D104, F401, D100 + */__init__.py: D104, F401, D100, */test_*: D205 [coverage:run] diff --git a/tests/conftest.py b/tests/conftest.py index 9bb8fbe..34d7c84 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -113,6 +113,43 @@ def batch_id(self): '08f5308af03fd4aa18ff1d868f043b12dd7b0a792e141f000a2505acd4b7a956' +class PublicKeyInformation: + """ + Impose public key information data transfer object. + """ + + @property + def data(self): + """ + Get public key information. + """ + return { + 'address': 'a23be1ae97d605bcbe61c312d9a443c010dbe7e6a0761e24b10d5368829ab0a7d36acc', + 'entity_hash': '13517cee1694346b584c08f5d84cc584b407043ef6a682942b1e18f0466cf1e2' + 'dede1756499ac8a2ee495cba258c609ea6147b4daa15225ba60ec5ffca419bd6', + 'entity_hash_signature': '6e081607aac18e63a44265e0054fb3e60d3791ac2292925c56d54df216396a3af42e96557a0da09b' + '236cdb5970ca5272473dcc9d71f16978e09a26bfb5e96562d8002473cfdf29b4c04dcc97cd4fa9d7' + '68ea13bd04b7479fe2f5965cfb0f944848511c9f597eacd4a23a1fd66aabfa95f45130fae1ae2507' + 'e8ed8d8ee308c77a042c86bbc476e2d3ed46c4b3c2a86c87470bb94c6e266cfd44bc513d04e5523c' + '7faf08887df4e37f2e31bda1bf403cb7bb3145602cd56dea7965e6e417d86620704a68013e95bbda' + '6a81e3dfb86aa489fa060e78bf9edfc4329ffc1f8484ecc610fccb5302499e1f18d0e2584db9c23c' + 'db4b0485ccda7cab17896b8fec28c851', + 'is_revoked': False, + 'is_valid': True, + 'owner_public_key': '03738df3f4ac3621ba8e89413d3ff4ad036c3a0a4dbb164b695885aab6aab614ad', + 'public_key': '30820122300d06092a864886f70d01010105000382010f003082010a028201010098ed61c659566b05d4017a0b5' + '9b7ce15f6be8432a470713cf3f0ac40b9ded6b65c227704c9bbde4f41a81a380c1ebadb771d1295418805eb16d5' + '14c0eb8a8747d08bb1cb5269d5ecb1152d64a8d8bb14836589f6babce22c2deac7dc6b80fcf285c74b67c5ccaf7' + '464df47d10dccecf02d8c4ed9924a8f4ee0df8661d9378fdb0a42355eda8128e88f7871ac5ea7c2605afa1b2b40' + '0e1a13b9f9fcf037aa7defcfe2abdcc4b9635d8601d1755660c0838fb2e10c35a88e7b9c1fc89db58cb6fa701a8' + 'a80f3dbbf587c1af43e7029e4bc79a26012cb9534d66a818c68acb9707a1a1a7c02b781df4928540053693696fb' + '058d1935fed35cf307c362e7f601710b0203010001', + 'type': 'rsa', + 'valid_from': 1554753277, + 'valid_to': 1585857277, + } + + class NodeConfigurations: """ Impose node configurations data transfer object. @@ -137,6 +174,14 @@ def sent_transaction(): return SentTransaction() +@pytest.fixture() +def public_key_info(): + """ + Get public key information fixture. + """ + return PublicKeyInformation() + + @pytest.fixture() def node_configurations(): """ diff --git a/tests/public_key/test_get_public_keys.py b/tests/public_key/test_get_public_key_addresses.py similarity index 96% rename from tests/public_key/test_get_public_keys.py rename to tests/public_key/test_get_public_key_addresses.py index a9c535f..c01cdf2 100644 --- a/tests/public_key/test_get_public_keys.py +++ b/tests/public_key/test_get_public_key_addresses.py @@ -7,10 +7,10 @@ from click.testing import CliRunner from cli.constants import ( - ADDRESS_REGEXP, FAILED_EXIT_FROM_COMMAND_CODE, NODE_IP_ADDRESS_FOR_TESTING, PASSED_EXIT_FROM_COMMAND_CODE, + PUBLIC_KEY_ADDRESS_REGEXP, ) from cli.entrypoint import cli from cli.utils import dict_to_pretty_json @@ -33,12 +33,12 @@ def test_get_public_keys(): NODE_IP_ADDRESS_FOR_TESTING, ]) - public_key_addresses = json.loads(result.output).get('result').get('public_key_addresses') + public_key_addresses = json.loads(result.output).get('result').get('addresses') assert PASSED_EXIT_FROM_COMMAND_CODE == result.exit_code for public_key in public_key_addresses: - assert re.match(pattern=ADDRESS_REGEXP, string=public_key) is not None + assert re.match(pattern=PUBLIC_KEY_ADDRESS_REGEXP, string=public_key) is not None def test_get_public_keys_invalid_address(): @@ -83,7 +83,7 @@ def test_get_public_keys_without_node_url(mocker): expected_result = { 'result': { - 'public_key_addresses': public_key_addresses, + 'addresses': public_key_addresses, }, } @@ -195,7 +195,7 @@ def test_get_public_keys_non_existing_address(): NODE_IP_ADDRESS_FOR_TESTING, ]) - public_key_addresses = json.loads(result.output).get('result').get('public_key_addresses') + public_key_addresses = json.loads(result.output).get('result').get('addresses') assert PASSED_EXIT_FROM_COMMAND_CODE == result.exit_code assert isinstance(public_key_addresses, list) diff --git a/tests/public_key/test_get_public_key_info.py b/tests/public_key/test_get_public_key_info.py new file mode 100644 index 0000000..4f86d35 --- /dev/null +++ b/tests/public_key/test_get_public_key_info.py @@ -0,0 +1,239 @@ +""" +Provide tests for command line interface's public key information commands. +""" +import json +import re + +from click.testing import CliRunner + +from cli.constants import ( + ADDRESS_REGEXP, + FAILED_EXIT_FROM_COMMAND_CODE, + HEADER_SIGNATURE_REGEXP, + NODE_27_IN_TESTNET_ADDRESS, + PASSED_EXIT_FROM_COMMAND_CODE, + PUBLIC_KEY_REGEXP, +) +from cli.entrypoint import cli +from cli.utils import dict_to_pretty_json + +PUBLIC_KEY_ADDRESS_PRESENTED_ON_THE_TEST_NODE = 'a23be17addad8eeb5177a395ea47eb54b4a646f8c570f4a2ecc0b1d2f6241c6845181b' + + +def test_get_public_key_info(): + """ + Case: get information about public key by its address. + Expect: dictionary of public key information, keys matched regexp checking. + """ + runner = CliRunner() + result = runner.invoke(cli, [ + 'public-key', + 'get-info', + '--address', + PUBLIC_KEY_ADDRESS_PRESENTED_ON_THE_TEST_NODE, + '--node-url', + NODE_27_IN_TESTNET_ADDRESS, + ]) + + public_key_info = json.loads(result.output).get('result').get('information') + + assert PASSED_EXIT_FROM_COMMAND_CODE == result.exit_code + assert re.match(pattern=ADDRESS_REGEXP, string=public_key_info.get('address')) is not None + assert re.match(pattern=HEADER_SIGNATURE_REGEXP, string=public_key_info.get('entity_hash')) is not None + assert re.match(pattern=PUBLIC_KEY_REGEXP, string=public_key_info.get('owner_public_key')) is not None + assert isinstance(public_key_info.get('valid_from'), int) + assert isinstance(public_key_info.get('valid_to'), int) + assert isinstance(public_key_info.get('is_revoked'), bool) + assert isinstance(public_key_info.get('is_valid'), bool) + + +def test_get_public_key_info_invalid_address(): + """ + Case: get information about public key by invalid address. + Expect: the following public key address is not valid error message. + """ + invalid_address = 'a23be14785e7b073b50e24f72e086675289795b969a895a7f02202404086946e8ddczz' + + runner = CliRunner() + result = runner.invoke(cli, [ + 'public-key', + 'get-info', + '--address', + invalid_address, + '--node-url', + NODE_27_IN_TESTNET_ADDRESS, + ]) + + expected_error = { + 'errors': { + 'address': [ + f'The following public key 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_public_key_info_without_node_url(mocker, public_key_info): + """ + Case: get information about public key without passing node URL. + Expect: dictionary of public key information is returned from node on localhost. + """ + mock_public_key_get_info = mocker.patch('cli.public_key.service.loop.run_until_complete') + mock_public_key_get_info.return_value = public_key_info + + runner = CliRunner() + result = runner.invoke(cli, [ + 'public-key', + 'get-info', + '--address', + PUBLIC_KEY_ADDRESS_PRESENTED_ON_THE_TEST_NODE, + ]) + + expected_result = { + 'result': { + 'information': public_key_info.data, + }, + } + + assert PASSED_EXIT_FROM_COMMAND_CODE == result.exit_code + assert expected_result == json.loads(result.output) + + +def test_get_public_key_info_invalid_node_url(): + """ + Case: get information about public key by passing invalid node URL. + Expect: the following node URL is invalid error message. + """ + invalid_node_url = 'domainwithoutextention' + + runner = CliRunner() + result = runner.invoke(cli, [ + 'public-key', + 'get-info', + '--address', + PUBLIC_KEY_ADDRESS_PRESENTED_ON_THE_TEST_NODE, + '--node-url', + invalid_node_url, + ]) + + expected_error = { + 'errors': { + '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_public_key_info_node_url_with_http(): + """ + Case: get information about public key 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, [ + 'public-key', + 'get-info', + '--address', + PUBLIC_KEY_ADDRESS_PRESENTED_ON_THE_TEST_NODE, + '--node-url', + node_url_with_http_protocol, + ]) + + expected_error = { + 'errors': { + '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_public_key_info_node_url_with_https(): + """ + Case: get information about public key 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, [ + 'public-key', + 'get-list', + '--address', + PUBLIC_KEY_ADDRESS_PRESENTED_ON_THE_TEST_NODE, + '--node-url', + node_url_with_https_protocol, + ]) + + expected_error = { + 'errors': { + '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 dict_to_pretty_json(expected_error) in result.output + + +def test_get_public_key_info_non_existing_address(): + """ + Case: get information about public key by passing non existing address. + Expect: public key information not found error message. + """ + non_existing_address = 'a23be14785e7b073b50e24f72e086675289795b969a895a7f02202404086946e8ddc5c' + + runner = CliRunner() + result = runner.invoke(cli, [ + 'public-key', + 'get-info', + '--address', + non_existing_address, + '--node-url', + NODE_27_IN_TESTNET_ADDRESS, + ]) + + expected_error = { + 'errors': 'Public key info not found', + } + + assert FAILED_EXIT_FROM_COMMAND_CODE == result.exit_code + assert dict_to_pretty_json(expected_error) in result.output + + +def test_get_public_key_info_non_existing_node_url(): + """ + Case: get information about public key by passing non existing node URL. + Expect: check if node running at URL error message. + """ + non_existing_node_url = 'non-existing-node.com' + + runner = CliRunner() + result = runner.invoke(cli, [ + 'public-key', + 'get-info', + '--address', + PUBLIC_KEY_ADDRESS_PRESENTED_ON_THE_TEST_NODE, + '--node-url', + non_existing_node_url, + ]) + + expected_error = { + 'errors': f'Please check if your node running at http://{non_existing_node_url}:8080.', + } + + assert FAILED_EXIT_FROM_COMMAND_CODE == result.exit_code + assert dict_to_pretty_json(expected_error) in result.output