def test_initialize_alice_with_custom_configuration_root(custom_filepath, click_runner, monkeypatch): monkeypatch.delenv(NUCYPHER_ENVVAR_KEYRING_PASSWORD, raising=False) # Use a custom local filepath for configuration init_args = ('alice', 'init', '--network', TEMPORARY_DOMAIN, '--federated-only', '--config-root', custom_filepath) result = click_runner.invoke(nucypher_cli, init_args, input=FAKE_PASSWORD_CONFIRMED, catch_exceptions=False) assert result.exit_code == 0 # CLI Output assert str(MOCK_CUSTOM_INSTALLATION_PATH) in result.output, "Configuration not in system temporary directory" assert "nucypher alice run" in result.output, 'Help message is missing suggested command' assert 'IPv4' not in result.output # Files and Directories assert os.path.isdir(custom_filepath), 'Configuration file does not exist' assert os.path.isdir(os.path.join(custom_filepath, 'keyring')), 'Keyring does not exist' assert os.path.isdir(os.path.join(custom_filepath, 'known_nodes')), 'known_nodes directory does not exist' custom_config_filepath = os.path.join(custom_filepath, AliceConfiguration.generate_filename()) assert os.path.isfile(custom_config_filepath), 'Configuration file does not exist' # Auth assert 'Enter NuCypher keyring password' in result.output, 'WARNING: User was not prompted for password' assert 'Repeat for confirmation:' in result.output, 'User was not prompted to confirm password'
def test_initialize_alice_with_custom_configuration_root(custom_filepath, click_runner): # Use a custom local filepath for configuration init_args = ('alice', 'init', '--network', TEMPORARY_DOMAIN, '--federated-only', '--config-root', custom_filepath) user_input = '{password}\n{password}'.format(password=INSECURE_DEVELOPMENT_PASSWORD, ip=MOCK_IP_ADDRESS) result = click_runner.invoke(nucypher_cli, init_args, input=user_input, catch_exceptions=False) assert result.exit_code == 0 # CLI Output assert MOCK_CUSTOM_INSTALLATION_PATH in result.output, "Configuration not in system temporary directory" assert "nucypher alice run" in result.output, 'Help message is missing suggested command' assert 'IPv4' not in result.output # Files and Directories assert os.path.isdir(custom_filepath), 'Configuration file does not exist' assert os.path.isdir(os.path.join(custom_filepath, 'keyring')), 'Keyring does not exist' assert os.path.isdir(os.path.join(custom_filepath, 'known_nodes')), 'known_nodes directory does not exist' custom_config_filepath = os.path.join(custom_filepath, AliceConfiguration.generate_filename()) assert os.path.isfile(custom_config_filepath), 'Configuration file does not exist' # Auth assert 'Enter NuCypher keyring password' in result.output, 'WARNING: User was not prompted for password' assert 'Repeat for confirmation:' in result.output, 'User was not prompted to confirm password'
def test_alice_destroy(click_runner, custom_filepath): """Should be the last test since it deletes the configuration file""" custom_config_filepath = custom_filepath / AliceConfiguration.generate_filename() destroy_args = ('alice', 'destroy', '--config-file', custom_config_filepath, '--force') result = click_runner.invoke(nucypher_cli, destroy_args, catch_exceptions=False) assert result.exit_code == 0 assert SUCCESSFUL_DESTRUCTION in result.output assert not custom_config_filepath.exists(), "Alice config file was deleted"
def test_alice_view_preexisting_configuration(click_runner, custom_filepath): custom_config_filepath = os.path.join(custom_filepath, AliceConfiguration.generate_filename()) view_args = ('alice', 'config', '--config-file', custom_config_filepath) result = click_runner.invoke(nucypher_cli, view_args, input=FAKE_PASSWORD_CONFIRMED) assert result.exit_code == 0 assert "checksum_address" in result.output assert "domain" in result.output assert TEMPORARY_DOMAIN in result.output assert str(custom_filepath) in result.output
def test_alice_destroy(click_runner, custom_filepath): custom_config_filepath = os.path.join(custom_filepath, AliceConfiguration.generate_filename()) destroy_args = ('alice', 'destroy', '--config-file', custom_config_filepath, '--force') result = click_runner.invoke(nucypher_cli, destroy_args, catch_exceptions=False) assert result.exit_code == 0 assert SUCCESSFUL_DESTRUCTION in result.output assert not os.path.exists(custom_config_filepath), "Alice config file was deleted"
def test_alice_control_starts_with_preexisting_configuration( click_runner, custom_filepath): custom_config_filepath = custom_filepath / AliceConfiguration.generate_filename( ) run_args = ('alice', 'run', '--dry-run', '--lonely', '--config-file', str(custom_config_filepath.absolute())) result = click_runner.invoke(nucypher_cli, run_args, input=FAKE_PASSWORD_CONFIRMED) assert result.exit_code == 0, result.exception
def test_alice_control_starts_with_preexisting_configuration(click_runner, custom_filepath): custom_config_filepath = os.path.join(custom_filepath, AliceConfiguration.generate_filename()) run_args = ('alice', 'run', '--dry-run', '--config-file', custom_config_filepath) user_input = '{password}\n{password}\n'.format(password=INSECURE_DEVELOPMENT_PASSWORD) result = click_runner.invoke(nucypher_cli, run_args, input=user_input) assert result.exit_code == 0
def test_alice_make_card(click_runner, custom_filepath): custom_config_filepath = os.path.join( custom_filepath, AliceConfiguration.generate_filename()) command = ('alice', 'make-card', '--nickname', 'flora', '--config-file', custom_config_filepath) result = click_runner.invoke(nucypher_cli, command, input=FAKE_PASSWORD_CONFIRMED, catch_exceptions=False) assert result.exit_code == 0 assert "Saved new character card " in result.output
def test_alice_make_card(click_runner, custom_filepath, mocker): mock_save_card = mocker.patch.object(Card, 'save') custom_config_filepath = custom_filepath / AliceConfiguration.generate_filename( ) command = ('alice', 'make-card', '--nickname', 'flora', '--config-file', str(custom_config_filepath.absolute())) result = click_runner.invoke(nucypher_cli, command, input=FAKE_PASSWORD_CONFIRMED, catch_exceptions=False) assert result.exit_code == 0 mock_save_card.assert_called_once() assert "Saved new character card " in result.output
def test_alice_view_with_preexisting_configuration(click_runner, custom_filepath): custom_config_filepath = os.path.join(custom_filepath, AliceConfiguration.generate_filename()) view_args = ('alice', 'view', '--config-file', custom_config_filepath) user_input = '{password}\n{password}\n'.format(password=INSECURE_DEVELOPMENT_PASSWORD) result = click_runner.invoke(nucypher_cli, view_args, input=user_input) assert result.exit_code == 0 assert "checksum_address" in result.output assert "domains" in result.output assert TEMPORARY_DOMAIN in result.output assert custom_filepath in result.output
def test_cli_lifecycle(click_runner, testerchain, random_policy_label, federated_ursulas, blockchain_ursulas, custom_filepath, custom_filepath_2, federated): """ This is an end to end integration test that runs each cli call in it's own process using only CLI character control entry points, and a mock side channel that runs in the control process """ # Boring Setup Stuff alice_config_root = custom_filepath bob_config_root = custom_filepath_2 envvars = {'NUCYPHER_KEYRING_PASSWORD': INSECURE_DEVELOPMENT_PASSWORD} # A side channel exists - Perhaps a dApp side_channel = MockSideChannel() shutil.rmtree(custom_filepath, ignore_errors=True) shutil.rmtree(custom_filepath_2, ignore_errors=True) """ Scene 1: Alice Installs nucypher to a custom filepath and examines her configuration """ # Alice performs an installation for the first time alice_init_args = ('alice', 'init', '--network', TEMPORARY_DOMAIN, '--config-root', alice_config_root) if federated: alice_init_args += ('--federated-only', ) else: alice_init_args += ('--provider', TEST_PROVIDER_URI, '--no-registry', '--pay-with', testerchain.alice_account) alice_init_response = click_runner.invoke(nucypher_cli, alice_init_args, catch_exceptions=False, env=envvars) assert alice_init_response.exit_code == 0 # Alice uses her configuration file to run the character "view" command alice_configuration_file_location = os.path.join( alice_config_root, AliceConfiguration.generate_filename()) alice_view_args = ('alice', 'public-keys', '--json-ipc', '--config-file', alice_configuration_file_location) alice_view_result = click_runner.invoke( nucypher_cli, alice_view_args, input=INSECURE_DEVELOPMENT_PASSWORD, catch_exceptions=False, env=envvars) assert alice_view_result.exit_code == 0 alice_view_response = json.loads(alice_view_result.output) # Alice expresses her desire to participate in data sharing with nucypher # by saving her public key somewhere Bob and Enrico can find it. side_channel.save_alice_pubkey( alice_view_response['result']['alice_verifying_key']) """ Scene 2: Bob installs nucypher, examines his configuration and expresses his interest to participate in data retrieval by posting his public keys somewhere public (side-channel). """ bob_init_args = ('bob', 'init', '--network', TEMPORARY_DOMAIN, '--config-root', bob_config_root) if federated: bob_init_args += ('--federated-only', ) else: bob_init_args += ('--provider', TEST_PROVIDER_URI, '--no-registry', '--checksum-address', testerchain.bob_account) bob_init_response = click_runner.invoke(nucypher_cli, bob_init_args, catch_exceptions=False, env=envvars) assert bob_init_response.exit_code == 0 # Alice uses her configuration file to run the character "view" command bob_configuration_file_location = os.path.join( bob_config_root, BobConfiguration.generate_filename()) bob_view_args = ('bob', 'public-keys', '--json-ipc', '--config-file', bob_configuration_file_location) bob_view_result = click_runner.invoke(nucypher_cli, bob_view_args, catch_exceptions=False, env=envvars) assert bob_view_result.exit_code == 0 bob_view_response = json.loads(bob_view_result.output) # Bob interacts with the sidechannel bob_public_keys = MockSideChannel.BobPublicKeys( bob_view_response['result']['bob_encrypting_key'], bob_view_response['result']['bob_verifying_key']) side_channel.save_bob_public_keys(bob_public_keys) """ Scene 3: Alice derives a policy keypair, and saves it's public key to a sidechannel. """ random_label = random_policy_label.decode() # Unicode string derive_args = ('alice', 'derive-policy-pubkey', '--mock-networking', '--json-ipc', '--config-file', alice_configuration_file_location, '--label', random_label) derive_response = click_runner.invoke(nucypher_cli, derive_args, catch_exceptions=False, env=envvars) assert derive_response.exit_code == 0 derive_response = json.loads(derive_response.output) assert derive_response['result']['label'] == random_label # Alice and the sidechannel: at Tinagre policy = MockSideChannel.PolicyAndLabel( encrypting_key=derive_response['result']['policy_encrypting_key'], label=derive_response['result']['label']) side_channel.save_policy(policy=policy) """ Scene 4: Enrico encrypts some data for some policy public key and saves it to a side channel. """ def enrico_encrypts(): # Fetch! policy = side_channel.fetch_policy() enrico_args = ('enrico', 'encrypt', '--json-ipc', '--policy-encrypting-key', policy.encrypting_key, '--message', PLAINTEXT) encrypt_result = click_runner.invoke(nucypher_cli, enrico_args, catch_exceptions=False, env=envvars) assert encrypt_result.exit_code == 0 encrypt_result = json.loads(encrypt_result.output) encrypted_message = encrypt_result['result'][ 'message_kit'] # type: str side_channel.save_message_kit(message_kit=encrypted_message) return encrypt_result def _alice_decrypts(encrypt_result): """ alice forgot what exactly she encrypted for bob. she decrypts it just to make sure. """ policy = side_channel.fetch_policy() alice_signing_key = side_channel.fetch_alice_pubkey() message_kit = encrypt_result['result']['message_kit'] decrypt_args = ( 'alice', 'decrypt', '--mock-networking', '--json-ipc', '--config-file', alice_configuration_file_location, '--message-kit', message_kit, '--label', policy.label, ) if federated: decrypt_args += ('--federated-only', ) decrypt_response_fail = click_runner.invoke(nucypher_cli, decrypt_args[0:7], catch_exceptions=False, env=envvars) assert decrypt_response_fail.exit_code == 2 decrypt_response = click_runner.invoke(nucypher_cli, decrypt_args, catch_exceptions=False, env=envvars) decrypt_result = json.loads(decrypt_response.output) for cleartext in decrypt_result['result']['cleartexts']: assert b64decode(cleartext.encode()).decode() == PLAINTEXT # replenish the side channel side_channel.save_policy(policy=policy) side_channel.save_alice_pubkey(alice_signing_key) return encrypt_result """ Scene 5: Alice grants access to Bob: We catch up with Alice later on, but before she has learned about existing Ursulas... """ if federated: teacher = list(federated_ursulas)[0] else: teacher = list(blockchain_ursulas)[1] teacher_uri = teacher.seed_node_metadata(as_teacher_uri=True) # Some Ursula is running somewhere def _run_teacher(_encrypt_result): start_pytest_ursula_services(ursula=teacher) return teacher_uri def _grant(teacher_uri): # Alice fetched Bob's public keys from the side channel bob_keys = side_channel.fetch_bob_public_keys() bob_encrypting_key = bob_keys.bob_encrypting_key bob_verifying_key = bob_keys.bob_verifying_key grant_args = ('alice', 'grant', '--mock-networking', '--json-ipc', '--network', TEMPORARY_DOMAIN, '--teacher', teacher_uri, '--config-file', alice_configuration_file_location, '--m', 2, '--n', 3, '--value', Web3.toWei(1, 'ether'), '--expiration', (maya.now() + datetime.timedelta(days=3)).iso8601(), '--label', random_label, '--bob-encrypting-key', bob_encrypting_key, '--bob-verifying-key', bob_verifying_key) if federated: grant_args += ('--federated-only', ) else: grant_args += ('--provider', TEST_PROVIDER_URI) grant_result = click_runner.invoke(nucypher_cli, grant_args, catch_exceptions=False, env=envvars) assert grant_result.exit_code == 0 grant_result = json.loads(grant_result.output) # TODO: Expand test to consider manual treasure map handing # # Alice puts the Treasure Map somewhere Bob can get it. # side_channel.save_treasure_map(treasure_map=grant_result['result']['treasure_map']) return grant_result def _bob_retrieves(_grant_result): """ Scene 6: Bob retrieves encrypted data from the side channel and uses nucypher to re-encrypt it """ # Bob interacts with a sidechannel ciphertext_message_kit = side_channel.fetch_message_kit() policy = side_channel.fetch_policy() policy_encrypting_key, label = policy alice_signing_key = side_channel.fetch_alice_pubkey() retrieve_args = ('bob', 'retrieve', '--mock-networking', '--json-ipc', '--teacher', teacher_uri, '--config-file', bob_configuration_file_location, '--message-kit', ciphertext_message_kit, '--label', label, '--policy-encrypting-key', policy_encrypting_key, '--alice-verifying-key', alice_signing_key) if federated: retrieve_args += ('--federated-only', ) retrieve_response = click_runner.invoke(nucypher_cli, retrieve_args, catch_exceptions=False, env=envvars) assert retrieve_response.exit_code == 0 retrieve_response = json.loads(retrieve_response.output) for cleartext in retrieve_response['result']['cleartexts']: assert b64decode(cleartext.encode()).decode() == PLAINTEXT return # Run the Callbacks d = threads.deferToThread(enrico_encrypts) # scene 4 d.addCallback(_alice_decrypts) # scene 5 (uncertainty) d.addCallback(_run_teacher) # scene 6 (preamble) d.addCallback(_grant) # scene 7 d.addCallback(_bob_retrieves) # scene 8 yield d
def test_coexisting_configurations(click_runner, custom_filepath, testerchain, agency_local_registry, mocker): # # Setup # if custom_filepath.exists(): shutil.rmtree(str(custom_filepath), ignore_errors=True) assert not custom_filepath.exists() # Parse node addresses # TODO: Is testerchain & Full contract deployment needed here (causes massive slowdown)? alice, ursula, another_ursula, staking_provider, *all_yall = testerchain.unassigned_accounts envvars = { NUCYPHER_ENVVAR_KEYSTORE_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD, NUCYPHER_ENVVAR_ALICE_ETH_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD, NUCYPHER_ENVVAR_BOB_ETH_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD } # Future configuration filepaths for assertions... public_keys_dir = custom_filepath / 'keystore' / 'public' known_nodes_dir = custom_filepath / 'known_nodes' # ... Ensure they do not exist to begin with. # No keys have been generated... assert not public_keys_dir.exists() # No known nodes exist... assert not known_nodes_dir.exists() # Not the configuration root... assert not custom_filepath.is_dir() # ... nothing None # # Create # # Expected config files alice_file_location = custom_filepath / AliceConfiguration.generate_filename( ) ursula_file_location = custom_filepath / UrsulaConfiguration.generate_filename( ) # Use a custom local filepath to init a persistent Alice alice_init_args = ('alice', 'init', '--network', TEMPORARY_DOMAIN, '--payment-network', TEMPORARY_DOMAIN, '--eth-provider', TEST_ETH_PROVIDER_URI, '--pay-with', alice, '--registry-filepath', str(agency_local_registry.filepath.absolute()), '--config-root', str(custom_filepath.absolute())) result = click_runner.invoke(nucypher_cli, alice_init_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 # All configuration files still exist. assert alice_file_location.is_file() # Use the same local filepath to init a persistent Ursula init_args = ('ursula', 'init', '--network', TEMPORARY_DOMAIN, '--payment-network', TEMPORARY_DOMAIN, '--eth-provider', TEST_ETH_PROVIDER_URI, '--operator-address', ursula, '--rest-host', MOCK_IP_ADDRESS, '--registry-filepath', str(agency_local_registry.filepath.absolute()), '--config-root', str(custom_filepath.absolute())) result = click_runner.invoke(nucypher_cli, init_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0, result.output # All configuration files still exist. assert alice_file_location.is_file() assert ursula_file_location.is_file() key_spy = mocker.spy(Keystore, 'generate') # keystore signing key # Use the same local filepath to init another persistent Ursula init_args = ('ursula', 'init', '--network', TEMPORARY_DOMAIN, '--payment-network', TEMPORARY_DOMAIN, '--operator-address', another_ursula, '--rest-host', MOCK_IP_ADDRESS_2, '--registry-filepath', str(agency_local_registry.filepath.absolute()), '--eth-provider', TEST_ETH_PROVIDER_URI, '--config-root', str(custom_filepath.absolute())) result = click_runner.invoke(nucypher_cli, init_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 # All configuration files still exist. assert alice_file_location.is_file() kid = key_spy.spy_return.id[:8] another_ursula_configuration_file_location = custom_filepath / UrsulaConfiguration.generate_filename( modifier=kid) assert another_ursula_configuration_file_location.is_file() assert ursula_file_location.is_file() # # Run # # Run an Ursula amidst the other configuration files run_args = ('ursula', 'run', '--dry-run', '--no-ip-checkup', '--config-file', str(another_ursula_configuration_file_location.absolute())) user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}\n' * 2 Operator.READY_POLL_RATE = 1 Operator.READY_TIMEOUT = 1 with pytest.raises(Operator.ActorError): # Operator init success, but not bonded. result = click_runner.invoke(nucypher_cli, run_args, input=user_input, catch_exceptions=False) assert result.exit_code == 0 Operator.READY_TIMEOUT = None # All configuration files still exist. assert alice_file_location.is_file() assert another_ursula_configuration_file_location.is_file() assert ursula_file_location.is_file() # Check that the proper Ursula console is attached assert another_ursula in result.output # # Destroy # another_ursula_destruction_args = ( 'ursula', 'destroy', '--force', '--config-file', str(another_ursula_configuration_file_location.absolute())) result = click_runner.invoke(nucypher_cli, another_ursula_destruction_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 assert not another_ursula_configuration_file_location.is_file() ursula_destruction_args = ('ursula', 'destroy', '--config-file', str(ursula_file_location.absolute())) result = click_runner.invoke(nucypher_cli, ursula_destruction_args, input='Y', catch_exceptions=False, env=envvars) assert result.exit_code == 0 assert 'y/N' in result.output assert not ursula_file_location.is_file() alice_destruction_args = ('alice', 'destroy', '--force', '--config-file', str(alice_file_location.absolute())) result = click_runner.invoke(nucypher_cli, alice_destruction_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 assert not alice_file_location.is_file()
def test_coexisting_configurations(click_runner, custom_filepath, testerchain, agency_local_registry): # # Setup # if custom_filepath.exists(): shutil.rmtree(str(custom_filepath), ignore_errors=True) assert not custom_filepath.exists() # Parse node addresses # TODO: Is testerchain & Full contract deployment needed here (causes massive slowdown)? alice, ursula, another_ursula, felix, staker, *all_yall = testerchain.unassigned_accounts envvars = {NUCYPHER_ENVVAR_KEYRING_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD, 'NUCYPHER_FELIX_DB_SECRET': INSECURE_DEVELOPMENT_PASSWORD} # Future configuration filepaths for assertions... public_keys_dir = custom_filepath / 'keyring' / 'public' known_nodes_dir = custom_filepath / 'known_nodes' # ... Ensure they do not exist to begin with. # No keys have been generated... assert not public_keys_dir.exists() # No known nodes exist... assert not known_nodes_dir.exists() # Not the configuration root... assert not os.path.isdir(custom_filepath) # ... nothing None # # Create # # Expected config files felix_file_location = custom_filepath / FelixConfiguration.generate_filename() alice_file_location = custom_filepath / AliceConfiguration.generate_filename() ursula_file_location = custom_filepath / UrsulaConfiguration.generate_filename() another_ursula_configuration_file_location = custom_filepath / UrsulaConfiguration.generate_filename(modifier=another_ursula) # Felix creates a system configuration felix_init_args = ('felix', 'init', '--config-root', custom_filepath, '--network', TEMPORARY_DOMAIN, '--provider', TEST_PROVIDER_URI, '--checksum-address', felix, '--registry-filepath', agency_local_registry.filepath, '--debug') result = click_runner.invoke(nucypher_cli, felix_init_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 # All configuration files still exist. assert os.path.isdir(custom_filepath) assert os.path.isfile(felix_file_location) assert os.path.isdir(public_keys_dir) assert len(os.listdir(public_keys_dir)) == 3 # Use a custom local filepath to init a persistent Alice alice_init_args = ('alice', 'init', '--network', TEMPORARY_DOMAIN, '--provider', TEST_PROVIDER_URI, '--pay-with', alice, '--registry-filepath', agency_local_registry.filepath, '--config-root', custom_filepath) result = click_runner.invoke(nucypher_cli, alice_init_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 # All configuration files still exist. assert os.path.isfile(felix_file_location) assert os.path.isfile(alice_file_location) assert len(os.listdir(public_keys_dir)) == 5 # Use the same local filepath to init a persistent Ursula init_args = ('ursula', 'init', '--network', TEMPORARY_DOMAIN, '--provider', TEST_PROVIDER_URI, '--worker-address', ursula, '--rest-host', MOCK_IP_ADDRESS, '--registry-filepath', agency_local_registry.filepath, '--config-root', custom_filepath) result = click_runner.invoke(nucypher_cli, init_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 # All configuration files still exist. assert len(os.listdir(public_keys_dir)) == 8 assert os.path.isfile(felix_file_location) assert os.path.isfile(alice_file_location) assert os.path.isfile(ursula_file_location) # Use the same local filepath to init another persistent Ursula init_args = ('ursula', 'init', '--network', TEMPORARY_DOMAIN, '--worker-address', another_ursula, '--rest-host', MOCK_IP_ADDRESS_2, '--registry-filepath', agency_local_registry.filepath, '--provider', TEST_PROVIDER_URI, '--config-root', custom_filepath) result = click_runner.invoke(nucypher_cli, init_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 # All configuration files still exist. assert os.path.isfile(felix_file_location) assert os.path.isfile(alice_file_location) assert os.path.isfile(another_ursula_configuration_file_location) assert os.path.isfile(ursula_file_location) assert len(os.listdir(public_keys_dir)) == 11 # # Run # # Run an Ursula amidst the other configuration files run_args = ('ursula', 'run', '--dry-run', '--config-file', another_ursula_configuration_file_location) user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}\n' * 2 Worker.BONDING_POLL_RATE = 1 Worker.BONDING_TIMEOUT = 1 with pytest.raises(Teacher.UnbondedWorker): # TODO: Why is this being checked here? # Worker init success, but not bonded. result = click_runner.invoke(nucypher_cli, run_args, input=user_input, catch_exceptions=False) assert result.exit_code == 0 Worker.BONDING_TIMEOUT = None # All configuration files still exist. assert os.path.isfile(felix_file_location) assert os.path.isfile(alice_file_location) assert os.path.isfile(another_ursula_configuration_file_location) assert os.path.isfile(ursula_file_location) assert len(os.listdir(public_keys_dir)) == 11 # Check that the proper Ursula console is attached assert another_ursula in result.output # # Destroy # another_ursula_destruction_args = ('ursula', 'destroy', '--force', '--config-file', another_ursula_configuration_file_location) result = click_runner.invoke(nucypher_cli, another_ursula_destruction_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 assert len(os.listdir(public_keys_dir)) == 8 assert not os.path.isfile(another_ursula_configuration_file_location) ursula_destruction_args = ('ursula', 'destroy', '--config-file', ursula_file_location) result = click_runner.invoke(nucypher_cli, ursula_destruction_args, input='Y', catch_exceptions=False, env=envvars) assert result.exit_code == 0 assert 'y/N' in result.output assert len(os.listdir(public_keys_dir)) == 5 assert not os.path.isfile(ursula_file_location) alice_destruction_args = ('alice', 'destroy', '--force', '--config-file', alice_file_location) result = click_runner.invoke(nucypher_cli, alice_destruction_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 assert len(os.listdir(public_keys_dir)) == 3 assert not os.path.isfile(alice_file_location) felix_destruction_args = ('felix', 'destroy', '--force', '--config-file', felix_file_location) result = click_runner.invoke(nucypher_cli, felix_destruction_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 assert len(os.listdir(public_keys_dir)) == 0 assert not os.path.isfile(felix_file_location)
def run_entire_cli_lifecycle(click_runner, random_policy_label, ursulas, custom_filepath, custom_filepath_2, registry_filepath=None, testerchain=None): """ This is an end to end integration test that runs each cli call in it's own process using only CLI character control entry points, and a mock side channel that runs in the control process """ federated = list(ursulas)[0].federated_only # Boring Setup Stuff alice_config_root = custom_filepath bob_config_root = custom_filepath_2 envvars = { NUCYPHER_ENVVAR_KEYSTORE_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD, NUCYPHER_ENVVAR_ALICE_ETH_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD, NUCYPHER_ENVVAR_BOB_ETH_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD } # A side channel exists - Perhaps a dApp side_channel = MockSideChannel() shutil.rmtree(str(custom_filepath), ignore_errors=True) shutil.rmtree(str(custom_filepath_2), ignore_errors=True) """ Scene 1: Alice Installs nucypher to a custom filepath and examines her configuration """ # Alice performs an installation for the first time alice_init_args = ('alice', 'init', '--network', TEMPORARY_DOMAIN, '--config-root', str(alice_config_root.absolute())) if federated: alice_init_args += ('--federated-only', ) else: alice_init_args += ('--eth-provider', TEST_ETH_PROVIDER_URI, '--pay-with', testerchain.alice_account, '--payment-network', TEMPORARY_DOMAIN, '--registry-filepath', str(registry_filepath.absolute())) alice_init_response = click_runner.invoke(nucypher_cli, alice_init_args, catch_exceptions=False, env=envvars) assert alice_init_response.exit_code == 0 # Prevent previous global logger settings set by above command from writing non-IPC messages to stdout GlobalLoggerSettings.stop_console_logging() # Alice uses her configuration file to run the character "view" command alice_configuration_file_location = alice_config_root / AliceConfiguration.generate_filename( ) alice_view_args = ('alice', 'public-keys', '--json-ipc', '--config-file', str(alice_configuration_file_location.absolute())) alice_view_result = click_runner.invoke( nucypher_cli, alice_view_args, input=INSECURE_DEVELOPMENT_PASSWORD, catch_exceptions=False, env=envvars) assert alice_view_result.exit_code == 0 try: alice_view_response = json.loads(alice_view_result.output) except JSONDecodeError: pytest.fail("Invalid JSON response from JSON-RPC Character process.") # Alice expresses her desire to participate in data sharing with nucypher # by saving her public key somewhere Bob and Enrico can find it. side_channel.save_alice_pubkey( alice_view_response['result']['alice_verifying_key']) """ Scene 2: Bob installs nucypher, examines his configuration and expresses his interest to participate in data retrieval by posting his public keys somewhere public (side-channel). """ bob_init_args = ('bob', 'init', '--network', TEMPORARY_DOMAIN, '--config-root', str(bob_config_root.absolute())) if federated: bob_init_args += ('--federated-only', ) else: bob_init_args += ('--eth-provider', TEST_ETH_PROVIDER_URI, '--registry-filepath', str(registry_filepath.absolute()), '--checksum-address', testerchain.bob_account) bob_init_response = click_runner.invoke(nucypher_cli, bob_init_args, catch_exceptions=False, env=envvars) assert bob_init_response.exit_code == 0 # Alice uses her configuration file to run the character "view" command bob_configuration_file_location = bob_config_root / BobConfiguration.generate_filename( ) bob_view_args = ( 'bob', 'public-keys', '--json-ipc', '--mock-networking', # TODO: It's absurd for this public-keys command to connect at all. 1710 '--lonely', # TODO: This needs to be implied by `public-keys`. '--config-file', str(bob_configuration_file_location.absolute())) bob_view_result = click_runner.invoke(nucypher_cli, bob_view_args, catch_exceptions=False, env=envvars) assert bob_view_result.exit_code == 0 bob_view_response = json.loads(bob_view_result.output) # Bob interacts with the sidechannel bob_public_keys = MockSideChannel.BobPublicKeys( bob_view_response['result']['bob_encrypting_key'], bob_view_response['result']['bob_verifying_key']) side_channel.save_bob_public_keys(bob_public_keys) """ Scene 3: Alice derives a policy keypair, and saves its public key to a sidechannel. """ random_label = random_policy_label.decode() # Unicode string derive_args = ('alice', 'derive-policy-pubkey', '--mock-networking', '--json-ipc', '--config-file', str(alice_configuration_file_location.absolute()), '--label', random_label) derive_response = click_runner.invoke(nucypher_cli, derive_args, catch_exceptions=False, env=envvars) assert derive_response.exit_code == 0 derive_response = json.loads(derive_response.output) assert derive_response['result']['label'] == random_label # Alice and the sidechannel: at Tinagre policy = MockSideChannel.PolicyAndLabel( encrypting_key=derive_response['result']['policy_encrypting_key'], label=derive_response['result']['label']) side_channel.save_policy(policy=policy) """ Scene 4: Enrico encrypts some data for some policy public key and saves it to a side channel. """ def enrico_encrypts(): # Fetch! policy = side_channel.fetch_policy() enrico_args = ('enrico', 'encrypt', '--json-ipc', '--policy-encrypting-key', policy.encrypting_key, '--message', PLAINTEXT) encrypt_result = click_runner.invoke(nucypher_cli, enrico_args, catch_exceptions=False, env=envvars) assert encrypt_result.exit_code == 0 encrypt_result = json.loads(encrypt_result.output) encrypted_message = encrypt_result['result'][ 'message_kit'] # type: str side_channel.save_message_kit(message_kit=encrypted_message) return encrypt_result def _alice_decrypts(encrypt_result): """ alice forgot what exactly she encrypted for bob. she decrypts it just to make sure. """ policy = side_channel.fetch_policy() alice_signing_key = side_channel.fetch_alice_pubkey() message_kit = encrypt_result['result']['message_kit'] decrypt_args = ( 'alice', 'decrypt', '--mock-networking', '--json-ipc', '--config-file', str(alice_configuration_file_location.absolute()), '--message-kit', message_kit, '--label', policy.label, ) if federated: decrypt_args += ('--federated-only', ) decrypt_response_fail = click_runner.invoke(nucypher_cli, decrypt_args[0:7], catch_exceptions=False, env=envvars) assert decrypt_response_fail.exit_code == 2 decrypt_response = click_runner.invoke(nucypher_cli, decrypt_args, catch_exceptions=False, env=envvars) decrypt_result = json.loads(decrypt_response.output) for cleartext in decrypt_result['result']['cleartexts']: assert b64decode(cleartext.encode()).decode() == PLAINTEXT # replenish the side channel side_channel.save_policy(policy=policy) side_channel.save_alice_pubkey(alice_signing_key) return encrypt_result """ Scene 5: Alice grants access to Bob: We catch up with Alice later on, but before she has learned about existing Ursulas... """ if federated: teacher = list(ursulas)[0] else: teacher = list(ursulas)[1] teacher_uri = teacher.seed_node_metadata(as_teacher_uri=True) # Some Ursula is running somewhere def _run_teacher(_encrypt_result): # start_pytest_ursula_services(ursula=teacher) return teacher_uri def _grant(teacher_uri): # Alice fetched Bob's public keys from the side channel bob_keys = side_channel.fetch_bob_public_keys() bob_encrypting_key = bob_keys.bob_encrypting_key bob_verifying_key = bob_keys.bob_verifying_key if federated: current_time = maya.now() else: current_time = maya.MayaDT( epoch=testerchain.client.get_blocktime()) expiration = (current_time + datetime.timedelta(days=2) ).datetime().strftime("%Y-%m-%d %H:%M:%S") grant_args = ('alice', 'grant', '--mock-networking', '--json-ipc', '--network', TEMPORARY_DOMAIN, '--teacher', teacher_uri, '--config-file', str(alice_configuration_file_location.absolute()), '-m', 2, '-n', 3, '--expiration', expiration, '--label', random_label, '--bob-encrypting-key', bob_encrypting_key, '--bob-verifying-key', bob_verifying_key) if federated: grant_args += ('--federated-only', ) else: grant_args += ('--eth-provider', TEST_ETH_PROVIDER_URI, '--value', Web3.toWei(9, 'gwei')) grant_result = click_runner.invoke(nucypher_cli, grant_args, catch_exceptions=False, env=envvars) assert grant_result.exit_code == 0, (grant_result.output, grant_result.exception) grant_result = json.loads(grant_result.output) # Alice puts the Treasure Map somewhere Bob can get it. side_channel.save_treasure_map( treasure_map=grant_result['result']['treasure_map']) return grant_result def _bob_retrieves(_grant_result): """ Scene 6: Bob retrieves encrypted data from the side channel and uses nucypher to re-encrypt it """ # Bob interacts with a sidechannel ciphertext_message_kit = side_channel.fetch_message_kit() policy = side_channel.fetch_policy() policy_encrypting_key, label = policy alice_signing_key = side_channel.fetch_alice_pubkey() retrieve_args = ('bob', 'retrieve-and-decrypt', '--mock-networking', '--json-ipc', '--teacher', teacher_uri, '--config-file', str(bob_configuration_file_location.absolute()), '--message-kit', ciphertext_message_kit, '--treasure-map', side_channel.fetch_treasure_map(), '--alice-verifying-key', alice_signing_key) retrieve_response = click_runner.invoke(nucypher_cli, retrieve_args, catch_exceptions=False, env=envvars) assert retrieve_response.exit_code == 0 retrieve_response = json.loads(retrieve_response.output) for cleartext in retrieve_response['result']['cleartexts']: assert b64decode(cleartext.encode()).decode() == PLAINTEXT return # Run the Callbacks d = threads.deferToThread(enrico_encrypts) # scene 4 d.addCallback(_alice_decrypts) # scene 5 (uncertainty) d.addCallback(_run_teacher) # scene 6 (preamble) d.addCallback(_grant) # scene 7 d.addCallback(_bob_retrieves) # scene 8 return d
def test_coexisting_configurations(click_runner, custom_filepath, mock_primary_registry_filepath, testerchain, deploy_user_input): # Parse node addresses deployer, alice, ursula, another_ursula, *all_yall = testerchain.client.accounts envvars = { 'NUCYPHER_KEYRING_PASSWORD': INSECURE_DEVELOPMENT_PASSWORD, # Upgradeable Contracts 'NUCYPHER_STAKING_ESCROW_SECRET': INSECURE_DEVELOPMENT_PASSWORD, 'NUCYPHER_POLICY_MANAGER_SECRET': INSECURE_DEVELOPMENT_PASSWORD, 'NUCYPHER_ADJUDICATOR_SECRET': INSECURE_DEVELOPMENT_PASSWORD, 'NUCYPHER_USER_ESCROW_PROXY_SECRET': INSECURE_DEVELOPMENT_PASSWORD, # Auxiliary 'NUCYPHER_FELIX_DB_SECRET': INSECURE_DEVELOPMENT_PASSWORD } # Future configuration filepaths for assertions... public_keys_dir = os.path.join(custom_filepath, 'keyring', 'public') known_nodes_dir = os.path.join(custom_filepath, 'known_nodes') # ... Ensure they do not exist to begin with. assert not os.path.isdir(public_keys_dir) assert not os.path.isfile(known_nodes_dir) # Deploy contracts deploy_args = ('contracts', '--registry-outfile', mock_primary_registry_filepath, '--provider-uri', TEST_PROVIDER_URI, '--config-root', custom_filepath, '--poa') result = click_runner.invoke(deploy.deploy, deploy_args, input=f'0\nY\nDEPLOY', catch_exceptions=False, env=envvars) assert result.exit_code == 0 # No keys have been generated... with pytest.raises(FileNotFoundError): assert len(os.listdir(public_keys_dir)) == 0 # No known nodes exist... with pytest.raises(FileNotFoundError): assert len(os.listdir(known_nodes_dir)) == 0 # Just the configuration root... assert os.path.isdir(custom_filepath) # and the fresh registry. assert os.path.isfile(mock_primary_registry_filepath) # # Create # # Expected config files felix_file_location = os.path.join(custom_filepath, FelixConfiguration.generate_filename()) alice_file_location = os.path.join(custom_filepath, AliceConfiguration.generate_filename()) ursula_file_location = os.path.join( custom_filepath, UrsulaConfiguration.generate_filename()) another_ursula_configuration_file_location = os.path.join( custom_filepath, UrsulaConfiguration.generate_filename(modifier=another_ursula)) # Felix creates a system configuration felix_init_args = ('felix', 'init', '--config-root', custom_filepath, '--network', TEMPORARY_DOMAIN, '--provider-uri', TEST_PROVIDER_URI, '--checksum-address', deployer, '--registry-filepath', mock_primary_registry_filepath) result = click_runner.invoke(nucypher_cli, felix_init_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 assert os.path.isfile(felix_file_location) assert len(os.listdir(public_keys_dir)) == 3 # Use a custom local filepath to init a persistent Alice alice_init_args = ('alice', 'init', '--network', TEMPORARY_DOMAIN, '--provider-uri', TEST_PROVIDER_URI, '--pay-with', alice, '--registry-filepath', mock_primary_registry_filepath, '--config-root', custom_filepath) result = click_runner.invoke(nucypher_cli, alice_init_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 assert os.path.isfile(alice_file_location) assert len(os.listdir(public_keys_dir)) == 5 # Use the same local filepath to init a persistent Ursula init_args = ('ursula', 'init', '--network', TEMPORARY_DOMAIN, '--provider-uri', TEST_PROVIDER_URI, '--checksum-address', ursula, '--rest-host', MOCK_IP_ADDRESS, '--registry-filepath', mock_primary_registry_filepath, '--config-root', custom_filepath) result = click_runner.invoke(nucypher_cli, init_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 assert len(os.listdir(public_keys_dir)) == 8 assert os.path.isfile(ursula_file_location) # Use the same local filepath to init another persistent Ursula init_args = ('ursula', 'init', '--network', TEMPORARY_DOMAIN, '--checksum-address', another_ursula, '--rest-host', MOCK_IP_ADDRESS_2, '--registry-filepath', mock_primary_registry_filepath, '--provider-uri', TEST_PROVIDER_URI, '--config-root', custom_filepath) result = click_runner.invoke(nucypher_cli, init_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 assert os.path.isfile(another_ursula_configuration_file_location) assert os.path.isfile(ursula_file_location) assert len(os.listdir(public_keys_dir)) == 11 # # Run # # Run an Ursula amidst the other configuration files run_args = ('ursula', 'run', '--dry-run', '--interactive', '--config-file', another_ursula_configuration_file_location) user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}' result = click_runner.invoke(nucypher_cli, run_args, input=user_input, catch_exceptions=False) assert result.exit_code == 0 # Check that the proper Ursula console is attached assert another_ursula in result.output # # Destroy # another_ursula_destruction_args = ( 'ursula', 'destroy', '--force', '--config-file', another_ursula_configuration_file_location) result = click_runner.invoke(nucypher_cli, another_ursula_destruction_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 assert len(os.listdir(public_keys_dir)) == 8 assert not os.path.isfile(another_ursula_configuration_file_location) ursula_destruction_args = ('ursula', 'destroy', '--config-file', ursula_file_location) result = click_runner.invoke(nucypher_cli, ursula_destruction_args, input='Y', catch_exceptions=False, env=envvars) assert result.exit_code == 0 assert 'y/N' in result.output assert len(os.listdir(public_keys_dir)) == 5 assert not os.path.isfile(ursula_file_location) felix_destruction_args = ('alice', 'destroy', '--force', '--config-file', alice_file_location) result = click_runner.invoke(nucypher_cli, felix_destruction_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 assert len(os.listdir(public_keys_dir)) == 3 assert not os.path.isfile(alice_file_location) felix_destruction_args = ('felix', 'destroy', '--force', '--config-file', felix_file_location) result = click_runner.invoke(nucypher_cli, felix_destruction_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 assert len(os.listdir(public_keys_dir)) == 0 assert not os.path.isfile(felix_file_location)
def test_coexisting_configurations(click_runner, custom_filepath, mock_primary_registry_filepath, testerchain, test_registry, agency): # # Setup # # Parse node addresses alice, ursula, another_ursula, felix, staker, *all_yall = testerchain.unassigned_accounts envvars = { 'NUCYPHER_KEYRING_PASSWORD': INSECURE_DEVELOPMENT_PASSWORD, 'NUCYPHER_FELIX_DB_SECRET': INSECURE_DEVELOPMENT_PASSWORD } # Future configuration filepaths for assertions... public_keys_dir = os.path.join(custom_filepath, 'keyring', 'public') known_nodes_dir = os.path.join(custom_filepath, 'known_nodes') # ... Ensure they do not exist to begin with. assert not os.path.isdir(public_keys_dir) assert not os.path.isfile(known_nodes_dir) # No keys have been generated... with pytest.raises(FileNotFoundError): assert len(os.listdir(public_keys_dir)) == 0 # No known nodes exist... with pytest.raises(FileNotFoundError): assert len(os.listdir(known_nodes_dir)) == 0 # Not the configuration root... assert not os.path.isdir(custom_filepath) # # Create # # Expected config files felix_file_location = os.path.join(custom_filepath, FelixConfiguration.generate_filename()) alice_file_location = os.path.join(custom_filepath, AliceConfiguration.generate_filename()) ursula_file_location = os.path.join( custom_filepath, UrsulaConfiguration.generate_filename()) another_ursula_configuration_file_location = os.path.join( custom_filepath, UrsulaConfiguration.generate_filename(modifier=another_ursula)) # Felix creates a system configuration felix_init_args = ('felix', 'init', '--config-root', custom_filepath, '--network', TEMPORARY_DOMAIN, '--provider', TEST_PROVIDER_URI, '--checksum-address', felix, '--registry-filepath', mock_primary_registry_filepath, '--debug') result = click_runner.invoke(nucypher_cli, felix_init_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 # All configuration files still exist. assert os.path.isdir(custom_filepath) assert os.path.isfile(felix_file_location) assert os.path.isdir(public_keys_dir) assert len(os.listdir(public_keys_dir)) == 3 # Use a custom local filepath to init a persistent Alice alice_init_args = ('alice', 'init', '--network', TEMPORARY_DOMAIN, '--provider', TEST_PROVIDER_URI, '--pay-with', alice, '--registry-filepath', mock_primary_registry_filepath, '--config-root', custom_filepath) result = click_runner.invoke(nucypher_cli, alice_init_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 # All configuration files still exist. assert os.path.isfile(felix_file_location) assert os.path.isfile(alice_file_location) assert len(os.listdir(public_keys_dir)) == 5 # Use the same local filepath to init a persistent Ursula init_args = ('ursula', 'init', '--network', TEMPORARY_DOMAIN, '--provider', TEST_PROVIDER_URI, '--worker-address', ursula, '--staker-address', staker, '--rest-host', MOCK_IP_ADDRESS, '--registry-filepath', mock_primary_registry_filepath, '--config-root', custom_filepath) result = click_runner.invoke(nucypher_cli, init_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 # All configuration files still exist. assert len(os.listdir(public_keys_dir)) == 8 assert os.path.isfile(felix_file_location) assert os.path.isfile(alice_file_location) assert os.path.isfile(ursula_file_location) # Use the same local filepath to init another persistent Ursula init_args = ('ursula', 'init', '--network', TEMPORARY_DOMAIN, '--worker-address', another_ursula, '--staker-address', staker, '--rest-host', MOCK_IP_ADDRESS_2, '--registry-filepath', mock_primary_registry_filepath, '--provider', TEST_PROVIDER_URI, '--config-root', custom_filepath) result = click_runner.invoke(nucypher_cli, init_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 # All configuration files still exist. assert os.path.isfile(felix_file_location) assert os.path.isfile(alice_file_location) assert os.path.isfile(another_ursula_configuration_file_location) assert os.path.isfile(ursula_file_location) assert len(os.listdir(public_keys_dir)) == 11 # # Run # # Run an Ursula amidst the other configuration files run_args = ('ursula', 'run', '--dry-run', '--interactive', '--config-file', another_ursula_configuration_file_location) user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}\n' * 2 with pytest.raises(Teacher.DetachedWorker): # Worker init success, but unassigned. result = click_runner.invoke(nucypher_cli, run_args, input=user_input, catch_exceptions=False) assert result.exit_code == 0 # All configuration files still exist. assert os.path.isfile(felix_file_location) assert os.path.isfile(alice_file_location) assert os.path.isfile(another_ursula_configuration_file_location) assert os.path.isfile(ursula_file_location) assert len(os.listdir(public_keys_dir)) == 11 # Check that the proper Ursula console is attached assert another_ursula in result.output # # Destroy # another_ursula_destruction_args = ( 'ursula', 'destroy', '--force', '--config-file', another_ursula_configuration_file_location) result = click_runner.invoke(nucypher_cli, another_ursula_destruction_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 assert len(os.listdir(public_keys_dir)) == 8 assert not os.path.isfile(another_ursula_configuration_file_location) ursula_destruction_args = ('ursula', 'destroy', '--config-file', ursula_file_location) result = click_runner.invoke(nucypher_cli, ursula_destruction_args, input='Y', catch_exceptions=False, env=envvars) assert result.exit_code == 0 assert 'y/N' in result.output assert len(os.listdir(public_keys_dir)) == 5 assert not os.path.isfile(ursula_file_location) alice_destruction_args = ('alice', 'destroy', '--force', '--config-file', alice_file_location) result = click_runner.invoke(nucypher_cli, alice_destruction_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 assert len(os.listdir(public_keys_dir)) == 3 assert not os.path.isfile(alice_file_location) felix_destruction_args = ('felix', 'destroy', '--force', '--config-file', felix_file_location) result = click_runner.invoke(nucypher_cli, felix_destruction_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 assert len(os.listdir(public_keys_dir)) == 0 assert not os.path.isfile(felix_file_location)