mirror of https://github.com/nucypher/nucypher.git
234 lines
10 KiB
Python
234 lines
10 KiB
Python
"""
|
|
This file is part of nucypher.
|
|
|
|
nucypher is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
nucypher is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
|
"""
|
|
|
|
import click
|
|
import pytest
|
|
from eth_utils import is_checksum_address
|
|
from unittest.mock import Mock
|
|
from web3 import Web3
|
|
|
|
from nucypher.blockchain.eth.actors import Wallet
|
|
from nucypher.blockchain.eth.clients import EthereumClient
|
|
from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory
|
|
from nucypher.blockchain.eth.signers import KeystoreSigner
|
|
from nucypher.blockchain.eth.token import NU
|
|
from nucypher.cli.actions.select import select_client_account
|
|
from nucypher.cli.literature import (
|
|
NO_ETH_ACCOUNTS,
|
|
GENERIC_SELECT_ACCOUNT,
|
|
)
|
|
from nucypher.config.constants import TEMPORARY_DOMAIN
|
|
from nucypher.types import SubStakeInfo
|
|
from tests.constants import MOCK_PROVIDER_URI, MOCK_SIGNER_URI, NUMBER_OF_ETH_TEST_ACCOUNTS
|
|
|
|
|
|
@pytest.mark.parametrize('selection', range(NUMBER_OF_ETH_TEST_ACCOUNTS))
|
|
def test_select_client_account(mock_stdin, test_emitter, mock_testerchain, selection, capsys):
|
|
"""Fine-grained assertions about the return value of interactive client account selection"""
|
|
mock_stdin.line(str(selection))
|
|
expected_account = mock_testerchain.client.accounts[selection]
|
|
selected_account = select_client_account(emitter=test_emitter, provider_uri=MOCK_PROVIDER_URI)
|
|
assert selected_account, "Account selection returned Falsy instead of an address"
|
|
assert isinstance(selected_account, str), "Selection is not a str"
|
|
assert is_checksum_address(selected_account), "Selection is not a valid checksum address"
|
|
assert selected_account == expected_account, "Selection returned the wrong address"
|
|
assert mock_stdin.empty()
|
|
captured = capsys.readouterr()
|
|
assert GENERIC_SELECT_ACCOUNT in captured.out
|
|
|
|
|
|
def test_select_client_account_with_no_accounts(mocker,
|
|
mock_stdin, # used to assert the user was not prompted
|
|
test_emitter,
|
|
mock_testerchain,
|
|
capsys):
|
|
mocker.patch.object(EthereumClient, 'accounts', return_value=[])
|
|
with pytest.raises(click.Abort):
|
|
select_client_account(emitter=test_emitter, provider_uri=MOCK_PROVIDER_URI)
|
|
captured = capsys.readouterr()
|
|
assert NO_ETH_ACCOUNTS in captured.out
|
|
|
|
|
|
def test_select_client_account_ambiguous_source(mock_stdin, # used to assert the user was not prompted
|
|
test_emitter,
|
|
mock_testerchain):
|
|
|
|
#
|
|
# Implicit wallet
|
|
#
|
|
|
|
error_message = "At least a provider URI or signer URI is necessary to select an account"
|
|
with pytest.raises(ValueError, match=error_message):
|
|
select_client_account(emitter=test_emitter)
|
|
|
|
error_message = "Pass either signer or signer_uri but not both."
|
|
with pytest.raises(ValueError, match=error_message):
|
|
select_client_account(emitter=test_emitter, signer=Mock(), signer_uri=MOCK_SIGNER_URI)
|
|
|
|
#
|
|
# Explicit wallet
|
|
#
|
|
|
|
error_message = "If a wallet is provided, don't provide a signer, provider URI, or signer URI."
|
|
with pytest.raises(ValueError, match=error_message):
|
|
select_client_account(emitter=test_emitter,
|
|
signer_uri=Mock(),
|
|
wallet=Mock())
|
|
|
|
with pytest.raises(ValueError, match=error_message):
|
|
select_client_account(emitter=test_emitter,
|
|
signer=Mock(),
|
|
wallet=Mock())
|
|
|
|
with pytest.raises(ValueError, match=error_message):
|
|
select_client_account(emitter=test_emitter,
|
|
provider_uri=Mock(),
|
|
wallet=Mock())
|
|
|
|
|
|
@pytest.mark.parametrize('selection', range(NUMBER_OF_ETH_TEST_ACCOUNTS))
|
|
def test_select_client_account_valid_sources(mocker,
|
|
mock_stdin,
|
|
test_emitter,
|
|
mock_testerchain,
|
|
patch_keystore,
|
|
mock_accounts,
|
|
selection,
|
|
capsys):
|
|
|
|
# From External Signer
|
|
mock_stdin.line(str(selection))
|
|
mock_signer = mocker.patch.object(KeystoreSigner, 'from_signer_uri')
|
|
selected_account = select_client_account(emitter=test_emitter, signer_uri=MOCK_SIGNER_URI)
|
|
expected_account = mock_testerchain.client.accounts[selection]
|
|
assert selected_account == expected_account
|
|
mock_signer.assert_called_once_with(uri=MOCK_SIGNER_URI)
|
|
assert mock_stdin.empty()
|
|
captured = capsys.readouterr()
|
|
assert GENERIC_SELECT_ACCOUNT in captured.out and f"Selected {selection}" in captured.out
|
|
|
|
# From Wallet
|
|
mock_stdin.line(str(selection))
|
|
expected_account = mock_testerchain.client.accounts[selection]
|
|
wallet = Wallet(provider_uri=MOCK_PROVIDER_URI)
|
|
selected_account = select_client_account(emitter=test_emitter, wallet=wallet)
|
|
assert selected_account == expected_account
|
|
assert mock_stdin.empty()
|
|
captured = capsys.readouterr()
|
|
assert GENERIC_SELECT_ACCOUNT in captured.out and f"Selected {selection}" in captured.out
|
|
|
|
# From pre-initialized Provider
|
|
mock_stdin.line(str(selection))
|
|
expected_account = mock_testerchain.client.accounts[selection]
|
|
selected_account = select_client_account(emitter=test_emitter, provider_uri=MOCK_PROVIDER_URI)
|
|
assert selected_account == expected_account
|
|
assert mock_stdin.empty()
|
|
captured = capsys.readouterr()
|
|
assert GENERIC_SELECT_ACCOUNT in captured.out and f"Selected {selection}" in captured.out
|
|
|
|
# From uninitialized Provider
|
|
mock_stdin.line(str(selection))
|
|
mocker.patch.object(BlockchainInterfaceFactory, 'is_interface_initialized', return_value=False)
|
|
mocker.patch.object(BlockchainInterfaceFactory, '_interfaces', return_value={})
|
|
mocker.patch.object(BlockchainInterfaceFactory, 'get_interface', return_value=mock_testerchain)
|
|
selected_account = select_client_account(emitter=test_emitter, provider_uri=MOCK_PROVIDER_URI)
|
|
assert selected_account == expected_account
|
|
assert mock_stdin.empty()
|
|
captured = capsys.readouterr()
|
|
assert GENERIC_SELECT_ACCOUNT in captured.out and f"Selected {selection}" in captured.out
|
|
|
|
|
|
@pytest.mark.parametrize('selection,show_staking,show_eth,show_tokens,stake_info', (
|
|
(0, True, True, True, []),
|
|
(1, True, True, True, []),
|
|
(5, True, True, True, []),
|
|
(NUMBER_OF_ETH_TEST_ACCOUNTS-1, True, True, True, []),
|
|
(4, True, True, True, [SubStakeInfo(1, 2, 3)]),
|
|
(7, True, True, True, [SubStakeInfo(1, 2, 3), SubStakeInfo(1, 2, 3)]),
|
|
(0, False, True, True, []),
|
|
(0, False, False, True, []),
|
|
(0, False, False, False, []),
|
|
))
|
|
def test_select_client_account_with_balance_display(mock_stdin,
|
|
test_emitter,
|
|
mock_testerchain,
|
|
capsys,
|
|
test_registry_source_manager,
|
|
mock_staking_agent,
|
|
mock_token_agent,
|
|
selection,
|
|
show_staking,
|
|
show_eth,
|
|
show_tokens,
|
|
stake_info):
|
|
|
|
# Setup
|
|
mock_staking_agent.get_all_stakes.return_value = stake_info
|
|
|
|
# Missing network kwarg with balance display active
|
|
blockchain_read_required = any((show_staking, show_eth, show_tokens))
|
|
if blockchain_read_required:
|
|
with pytest.raises(ValueError, match='Pass network name or registry; Got neither.'):
|
|
select_client_account(emitter=test_emitter,
|
|
show_eth_balance=show_eth,
|
|
show_nu_balance=show_tokens,
|
|
show_staking=show_staking,
|
|
provider_uri=MOCK_PROVIDER_URI)
|
|
|
|
# Good selection
|
|
mock_stdin.line(str(selection))
|
|
selected_account = select_client_account(emitter=test_emitter,
|
|
network=TEMPORARY_DOMAIN,
|
|
show_eth_balance=show_eth,
|
|
show_nu_balance=show_tokens,
|
|
show_staking=show_staking,
|
|
provider_uri=MOCK_PROVIDER_URI)
|
|
|
|
# check for accurate selection consistency with client index
|
|
assert selected_account == mock_testerchain.client.accounts[selection]
|
|
assert mock_stdin.empty()
|
|
|
|
# Display account info
|
|
headers = ['Account']
|
|
if show_staking:
|
|
headers.append('Staking')
|
|
if show_eth:
|
|
headers.append('ETH')
|
|
if show_tokens:
|
|
headers.append('NU')
|
|
|
|
captured = capsys.readouterr()
|
|
for column_name in headers:
|
|
assert column_name in captured.out, f'"{column_name}" column was not displayed'
|
|
|
|
for account in mock_testerchain.client.accounts:
|
|
assert account in captured.out
|
|
|
|
if show_tokens:
|
|
balance = mock_token_agent.get_balance(address=account)
|
|
assert str(NU.from_nunits(balance)) in captured.out
|
|
|
|
if show_eth:
|
|
balance = mock_testerchain.client.get_balance(account=account)
|
|
assert str(Web3.fromWei(balance, 'ether')) in captured.out
|
|
|
|
if show_staking:
|
|
if len(stake_info) == 0:
|
|
assert "No" in captured.out
|
|
else:
|
|
assert 'Yes' in captured.out
|