Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement getting node configurations #25

Merged
merged 13 commits into from
Apr 25, 2019
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* [Configuration file](#configuration-file)
* [Service](#service)
* [Account](#account)
* [Node](#node)
* [Development](#development)
* [Requirements](#development-requirements)
* [Docker](#docker)
Expand Down Expand Up @@ -139,6 +140,26 @@ $ remme account transfer-tokens \
}
```

### Node

Get node configurations — ``remme node get-configs``:

| Arguments | Type | Required | Description |
| :-------: | :----: | :-------: | ------------------------------- |
| node-url | String | No | Node URL to apply a command to. |

```bash
$ remme node get-configs --node-url=node-genesis-testnet.remme.io
{
"result": {
"configurations": {
"node_address": "1168296ecf036e857f42129b58303bcf1e03723764a1702cbe98529802aad8514ee3cf",
"node_public_key": "03738df3f4ac3621ba8e89413d3ff4ad036c3a0a4dbb164b695885aab6aab614ad"
}
}
}
```

## Development

<h3 id="development-requirements">Requirements</h4>
Expand Down
1 change: 1 addition & 0 deletions cli/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@
CLI_CONFIG_FILE_NAME = 'remme-core-cli'

NODE_IP_ADDRESS_FOR_TESTING = '159.89.104.9'
LATEST_RELEASE_NODE_IP_ADDRESS_FOR_TESTING = '165.22.75.163'
PRIVATE_KEY_FOR_TESTING = 'b03e31d2f310305eab249133b53b5fb3270090fc1692c9b022b81c6b9bb6029b'
2 changes: 2 additions & 0 deletions cli/entrypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import click

from cli.account.cli import account_commands
from cli.node.cli import node_commands


@click.group()
Expand All @@ -17,3 +18,4 @@ def cli():


cli.add_command(account_commands)
cli.add_command(node_commands)
Empty file added cli/node/__init__.py
Empty file.
59 changes: 59 additions & 0 deletions cli/node/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""
Provide implementation of the command line interface's node commands.
"""
import asyncio
import sys

import click
from remme import Remme

from cli.constants import (
FAILED_EXIT_FROM_COMMAND_CODE,
NODE_URL_ARGUMENT_HELP_MESSAGE,
)
from cli.node.forms import GetNodeConfigurationsForm
from cli.node.service import Node
from cli.utils import (
default_node_url,
print_errors,
print_result,
)

loop = asyncio.get_event_loop()


@click.group('node', chain=True)
def node_commands():
"""
Provide commands for working with node.
"""
pass


@click.option('--node-url', type=str, required=False, help=NODE_URL_ARGUMENT_HELP_MESSAGE, default=default_node_url())
@node_commands.command('get-configs')
def get_config(node_url):
"""
Get node configurations.
"""
arguments, errors = GetNodeConfigurationsForm().load({
'node_url': node_url,
})

if errors:
print_errors(errors=errors)
sys.exit(FAILED_EXIT_FROM_COMMAND_CODE)

node_url = arguments.get('node_url')

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

result, errors = Node(service=remme).get_configs()

if errors is not None:
print_errors(errors=errors)
sys.exit(FAILED_EXIT_FROM_COMMAND_CODE)

print_result(result=result)
14 changes: 14 additions & 0 deletions cli/node/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""
Provide forms for command line interface's node commands.
"""
from marshmallow import Schema

from cli.generic.forms.fields import NodeURLField


class GetNodeConfigurationsForm(Schema):
"""
Get node configurations.
"""

node_url = NodeURLField(allow_none=True, required=False)
15 changes: 15 additions & 0 deletions cli/node/interfaces.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""
Provide implementation of the node interfaces.
"""


class NodeInterface:
"""
Implements node interface.
"""

def get_configs(self):
"""
Get node configurations.
"""
pass
40 changes: 40 additions & 0 deletions cli/node/service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""
Provide implementation of the node.
"""
import asyncio

from accessify import implements

from cli.node.interfaces import NodeInterface

loop = asyncio.get_event_loop()


@implements(NodeInterface)
class Node:
"""
Implements node.
"""

def __init__(self, service):
"""
Constructor.

Arguments:
service: object to interact with Remme core API.
"""
self.service = service

def get_configs(self):
"""
Get node configurations.
"""
try:
configurations = loop.run_until_complete(self.service.node_management.get_node_config())

except Exception as error:
return None, str(error)

return {
'configurations': configurations.data,
}, None
24 changes: 24 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,33 @@ def batch_id(self):
'08f5308af03fd4aa18ff1d868f043b12dd7b0a792e141f000a2505acd4b7a956'


class NodeConfigurations:
"""
Impose node configurations data transfer object.
"""

@property
def data(self):
"""
Get node configurations.
"""
return {
'node_address': '11682919ed54658edf965f955a5783e6a653ce3bb411b99c8afe9f6e5840af45171774',
'node_public_key': '03725231d64d1b379a1d855d0e7614684744ba915bd657e398f5a5cefc9ced896d',
}


@pytest.fixture()
def sent_transaction():
"""
Get sent transaction fixture.
"""
return SentTransaction()


@pytest.fixture()
def node_configurations():
"""
Get node configurations fixture.
"""
return NodeConfigurations()
Empty file added tests/node/__init__.py
Empty file.
168 changes: 168 additions & 0 deletions tests/node/test_get_configs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
"""
Provide tests for command line interface's node get configurations command.
"""
import json

from click.testing import CliRunner

from cli.constants import (
FAILED_EXIT_FROM_COMMAND_CODE,
LATEST_RELEASE_NODE_IP_ADDRESS_FOR_TESTING,
PASSED_EXIT_FROM_COMMAND_CODE,
)
from cli.entrypoint import cli
from cli.utils import dict_to_pretty_json


def test_get_node_configs():
"""
Case: get node configurations.
Expect: node public key and address are returned.
"""
runner = CliRunner()
result = runner.invoke(cli, [
'node',
'get-configs',
'--node-url',
LATEST_RELEASE_NODE_IP_ADDRESS_FOR_TESTING,
])

expected_node_configurations = {
'result': {
'configurations': {
'node_address': '11682919ed54658edf965f955a5783e6a653ce3bb411b99c8afe9f6e5840af45171774',
'node_public_key': '03725231d64d1b379a1d855d0e7614684744ba915bd657e398f5a5cefc9ced896d',
},
},
}

assert PASSED_EXIT_FROM_COMMAND_CODE == result.exit_code
assert expected_node_configurations == json.loads(result.output)


def test_get_node_configs_without_node_url(mocker, node_configurations):
"""
Case: get node configurations without passing node URL.
Expect: batch identifier is returned from node on localhost.
"""
mock_account_get_balance = mocker.patch('cli.node.service.loop.run_until_complete')
mock_account_get_balance.return_value = node_configurations

runner = CliRunner()
result = runner.invoke(cli, [
'node',
'get-configs',
])

expected_node_configurations = {
'result': {
'configurations': node_configurations.data,
},
}

assert PASSED_EXIT_FROM_COMMAND_CODE == result.exit_code
assert expected_node_configurations == json.loads(result.output)


def test_get_node_configs_invalid_node_url():
"""
Case: get node configurations by passing invalid node URL.
Expect: the following node URL is invalid error message.
"""
invalid_node_url = 'domainwithoutextention'

runner = CliRunner()
result = runner.invoke(cli, [
'node',
'get-configs',
'--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_node_configs_node_url_with_http():
"""
Case: get node configurations 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, [
'node',
'get-configs',
'--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_node_configs_node_url_with_https():
"""
Case: get node configurations 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, [
'node',
'get-configs',
'--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_node_configs_non_existing_node_url():
"""
Case: get node configurations 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, [
'node',
'get-configs',
'--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