nucypher/tests/integration/config/test_character_configuratio...

244 lines
7.9 KiB
Python

import json
from pathlib import Path
import pytest
from constant_sorrow.constants import CERTIFICATE_NOT_SAVED, NO_KEYSTORE_ATTACHED
from nucypher_core.umbral import SecretKey
from nucypher.characters.lawful import Alice, Bob, Ursula
from nucypher.cli.actions.configure import destroy_configuration
from nucypher.cli.literature import SUCCESSFUL_DESTRUCTION
from nucypher.config.base import CharacterConfiguration
from nucypher.config.characters import (
AliceConfiguration,
BobConfiguration,
UrsulaConfiguration,
)
from nucypher.config.constants import TEMPORARY_DOMAIN
from nucypher.config.storages import ForgetfulNodeStorage
from nucypher.crypto.keystore import Keystore
from tests.constants import (
INSECURE_DEVELOPMENT_PASSWORD,
MOCK_ETH_PROVIDER_URI,
MOCK_IP_ADDRESS,
)
# Main Cast
configurations = (AliceConfiguration, BobConfiguration, UrsulaConfiguration)
characters = (Alice, Bob, Ursula)
# Assemble
characters_and_configurations = list(zip(characters, configurations))
all_characters = tuple(
characters,
)
all_configurations = tuple(
configurations,
)
@pytest.mark.parametrize("character,configuration", characters_and_configurations)
def test_development_character_configurations(
character, configuration, test_registry_source_manager, mocker, testerchain
):
mocker.patch.object(
CharacterConfiguration, "DEFAULT_PAYMENT_NETWORK", TEMPORARY_DOMAIN
)
params = dict(
dev_mode=True,
lonely=True,
domain=TEMPORARY_DOMAIN,
checksum_address=testerchain.unassigned_accounts[0],
eth_provider_uri=MOCK_ETH_PROVIDER_URI,
)
if character is Ursula:
params.update(dict(operator_address=testerchain.unassigned_accounts[0]))
config = configuration(**params)
assert config.is_me is True
assert config.dev_mode is True
assert config.keystore == NO_KEYSTORE_ATTACHED
# Production
thing_one = config()
# Alternate way to produce a character with a direct call
thing_two = config.produce()
assert isinstance(thing_two, character)
# Ensure we do in fact have a character here
assert isinstance(thing_one, character)
# Ethereum Address
assert len(thing_one.checksum_address) == 42
# Domain
assert TEMPORARY_DOMAIN == thing_one.domain
# Node Storage
assert isinstance(thing_one.node_storage, ForgetfulNodeStorage)
assert ":memory:" in thing_one.node_storage._name
# All development characters are unique
_characters = [thing_one, thing_two]
for _ in range(3):
another_character = config()
assert another_character not in _characters
_characters.append(another_character)
if character is Alice:
for alice in _characters:
alice.disenchant()
@pytest.mark.parametrize("configuration_class", all_configurations)
def test_default_character_configuration_preservation(
configuration_class,
testerchain,
test_registry_source_manager,
tmpdir,
test_registry,
):
configuration_class.DEFAULT_CONFIG_ROOT = Path("/tmp")
fake_address = "0xdeadbeef"
network = TEMPORARY_DOMAIN
expected_filename = (
f"{configuration_class.NAME}.{configuration_class._CONFIG_FILE_EXTENSION}"
)
generated_filename = configuration_class.generate_filename()
assert generated_filename == expected_filename
expected_filepath = Path("/", "tmp", generated_filename)
if expected_filepath.exists():
expected_filepath.unlink()
assert not expected_filepath.exists()
if configuration_class == UrsulaConfiguration:
# special case for rest_host & dev mode
# use keystore
keystore = Keystore.generate(
password=INSECURE_DEVELOPMENT_PASSWORD, keystore_dir=tmpdir
)
keystore.signing_public_key = SecretKey.random().public_key()
character_config = configuration_class(
checksum_address=fake_address,
eth_provider_uri=MOCK_ETH_PROVIDER_URI,
domain=network,
rest_host=MOCK_IP_ADDRESS,
payment_provider=MOCK_ETH_PROVIDER_URI,
policy_registry=test_registry,
payment_network=TEMPORARY_DOMAIN,
keystore=keystore,
)
else:
character_config = configuration_class(
checksum_address=fake_address,
eth_provider_uri=MOCK_ETH_PROVIDER_URI,
domain=network,
payment_network=TEMPORARY_DOMAIN,
policy_registry=test_registry,
)
generated_filepath = character_config.generate_filepath()
assert generated_filepath == expected_filepath
written_filepath = character_config.to_configuration_file()
assert written_filepath == expected_filepath
assert written_filepath.exists()
try:
# Read
with open(character_config.filepath, "r") as f:
_contents = json.loads(
f.read()
) # ensure this can be read and is valid JSON
# Restore from JSON file
restored_configuration = configuration_class.from_configuration_file()
assert json.loads(character_config.serialize()) == json.loads(
restored_configuration.serialize()
)
# File still exists after reading
assert written_filepath.exists()
finally:
if expected_filepath.exists():
expected_filepath.unlink()
def test_ursula_development_configuration(test_registry_source_manager, testerchain):
config = UrsulaConfiguration(
dev_mode=True,
checksum_address=testerchain.unassigned_accounts[0],
operator_address=testerchain.unassigned_accounts[1],
domain=TEMPORARY_DOMAIN,
payment_network=TEMPORARY_DOMAIN,
eth_provider_uri=MOCK_ETH_PROVIDER_URI,
)
assert config.is_me is True
assert config.dev_mode is True
assert config.keystore == NO_KEYSTORE_ATTACHED
# Produce an Ursula
ursula_one = config()
# Ensure we do in fact have an Ursula here
assert isinstance(ursula_one, Ursula)
assert len(ursula_one.checksum_address) == 42
# A Temporary Ursula
port = ursula_one.rest_information()[0].port
assert port == UrsulaConfiguration.DEFAULT_DEVELOPMENT_REST_PORT
assert ursula_one.certificate_filepath is CERTIFICATE_NOT_SAVED
assert isinstance(ursula_one.node_storage, ForgetfulNodeStorage)
assert ":memory:" in ursula_one.node_storage._name
# Alternate way to produce a character with a direct call
ursula_two = config.produce()
assert isinstance(ursula_two, Ursula)
# All development Ursulas are unique
ursulas = [ursula_one, ursula_two]
for _ in range(3):
ursula = config()
assert ursula not in ursulas
ursulas.append(ursula)
for ursula in ursulas:
ursula.stop()
@pytest.mark.skip("See #2016")
def test_destroy_configuration(config, test_emitter, capsys, mocker):
# Setup
config_file = config.filepath
# Isolate from filesystem and Spy on the methods we're testing here
spy_keystore_attached = mocker.spy(CharacterConfiguration, "attach_keystore")
mock_config_destroy = mocker.patch.object(CharacterConfiguration, "destroy")
spy_keystore_destroy = mocker.spy(Keystore, "destroy")
mock_os_remove = mocker.patch("pathlib.Path.unlink")
# Test
destroy_configuration(emitter=test_emitter, character_config=config)
mock_config_destroy.assert_called_once()
captured = capsys.readouterr()
assert SUCCESSFUL_DESTRUCTION in captured.out
spy_keystore_attached.assert_called_once()
spy_keystore_destroy.assert_called_once()
mock_os_remove.assert_called_with(str(config_file))
# Ensure all destroyed files belong to this Ursula
for call in mock_os_remove.call_args_list:
filepath = str(call.args[0])
assert config.checksum_address in filepath
expected_removal = 7 # TODO: Source this number from somewhere else
assert mock_os_remove.call_count == expected_removal