From e366ea468e9d0809650656e3177179635e244501 Mon Sep 17 00:00:00 2001 From: Salvatore Ingala <6681844+bigspider@users.noreply.github.com> Date: Mon, 19 Oct 2020 12:36:55 +0200 Subject: [PATCH] Renamed test_basic_e2e to test_client_e2e; added skeleton of cli e2e tests; restructured teosd setup fixtures to be reusable --- teos/cli/rpc_client.py | 4 +- test/teos/conftest.py | 13 +- test/teos/e2e/conftest.py | 4 +- test/teos/e2e/test_cli_e2e.py | 11 ++ .../{test_basic_e2e.py => test_client_e2e.py} | 135 ++++++++++-------- test/teos/unit/cli/test_rpc_client.py | 17 +++ test/teos/unit/conftest.py | 4 +- test/teos/unit/test_watcher.py | 2 +- 8 files changed, 119 insertions(+), 71 deletions(-) create mode 100644 test/teos/e2e/test_cli_e2e.py rename test/teos/e2e/{test_basic_e2e.py => test_client_e2e.py} (85%) create mode 100644 test/teos/unit/cli/test_rpc_client.py diff --git a/teos/cli/rpc_client.py b/teos/cli/rpc_client.py index e2d22a69..c68e053f 100644 --- a/teos/cli/rpc_client.py +++ b/teos/cli/rpc_client.py @@ -44,8 +44,8 @@ class RPCClient: Errors from the grpc calls are not handled. Args: - rpc_host (:obj:`str`): the IP or host where the RPC server will be hosted. - rpc_port (:obj:`int`): the port where the RPC server will be hosted. + rpc_host (:obj:`str`): the IP or host where the RPC server is hosted. + rpc_port (:obj:`int`): the port where the RPC server is hosted. Attributes: stub: The rpc client stub. diff --git a/test/teos/conftest.py b/test/teos/conftest.py index 90e47080..11bfd341 100644 --- a/test/teos/conftest.py +++ b/test/teos/conftest.py @@ -35,21 +35,30 @@ def prng_seed(): random.seed(0) -@pytest.fixture(scope="session") +@pytest.fixture(scope="module") def run_bitcoind(dirname=".test_bitcoin"): + global btc_addr, utxos # Run bitcoind in a separate folder makedirs(dirname, exist_ok=True) bitcoind = os.getenv("BITCOIND", "bitcoind") copy(os.path.join(os.path.dirname(__file__), "bitcoin.conf"), dirname) - subprocess.Popen([bitcoind, f"--datadir={dirname}"]) + + bitcoind_popen = subprocess.Popen([bitcoind, f"--datadir={dirname}"]) # Generate some initial blocks setup_node() yield bitcoin_cli.stop() + bitcoind_popen.wait() + sleep(1) + + # cleanup global variables in case we do another run later + btc_addr = None + utxos = list() + rmtree(dirname) diff --git a/test/teos/e2e/conftest.py b/test/teos/e2e/conftest.py index abddd103..83b3238f 100644 --- a/test/teos/e2e/conftest.py +++ b/test/teos/e2e/conftest.py @@ -15,8 +15,8 @@ # This fixture needs to be manually run on the first E2E. -@pytest.fixture(scope="session") -def teosd(): +@pytest.fixture(scope="module") +def teosd(run_bitcoind): teosd_process, teos_id = run_teosd() yield teosd_process, teos_id diff --git a/test/teos/e2e/test_cli_e2e.py b/test/teos/e2e/test_cli_e2e.py new file mode 100644 index 00000000..df80f443 --- /dev/null +++ b/test/teos/e2e/test_cli_e2e.py @@ -0,0 +1,11 @@ +import pytest + +from contrib.client import teos_client + +from teos.cli.teos_cli import CLI +from teos.cli.rpc_client import RPCClient + + +def test_start_teosd(teosd): + # All commands should fail if the user is not registered + teosd_process, teos_id = teosd \ No newline at end of file diff --git a/test/teos/e2e/test_basic_e2e.py b/test/teos/e2e/test_client_e2e.py similarity index 85% rename from test/teos/e2e/test_basic_e2e.py rename to test/teos/e2e/test_client_e2e.py index 017ca544..2a7b2b34 100644 --- a/test/teos/e2e/test_basic_e2e.py +++ b/test/teos/e2e/test_client_e2e.py @@ -12,7 +12,7 @@ from common.appointment import Appointment, AppointmentStatus from common.cryptographer import Cryptographer -from teos.cli.teos_cli import RPCClient +from teos.cli.rpc_client import RPCClient from teos.utils.auth_proxy import JSONRPCException from test.teos.conftest import ( @@ -40,22 +40,18 @@ appointments_in_responder = 0 -teosd_process, teos_id = None, None - - -def get_appointment_info(locator, sk=user_sk): +def get_appointment_info(teos_id, locator, sk=user_sk): sleep(1) # Let's add a bit of delay so the state can be updated return teos_client.get_appointment(locator, sk, teos_id, teos_base_endpoint) -def add_appointment(appointment_data, sk=user_sk): +def add_appointment(teos_id, appointment_data, sk=user_sk): return teos_client.add_appointment(appointment_data, sk, teos_id, teos_base_endpoint) -def test_commands_non_registered(run_bitcoind, teosd): +def test_commands_non_registered(teosd): # All commands should fail if the user is not registered - global teosd_process, teos_id - teosd_process, teos_id = teosd + _, teos_id = teosd # Add appointment commitment_tx, commitment_tx_id, penalty_tx = create_txs() @@ -63,15 +59,16 @@ def test_commands_non_registered(run_bitcoind, teosd): with pytest.raises(TowerResponseError): appointment = teos_client.create_appointment(appointment_data) - add_appointment(appointment) + add_appointment(teos_id, appointment) # Get appointment with pytest.raises(TowerResponseError): - assert get_appointment_info(appointment_data.get("locator")) + assert get_appointment_info(teos_id, appointment_data.get("locator")) -def test_commands_registered(run_bitcoind): +def test_commands_registered(teosd): global appointments_in_watcher + _, teos_id = teosd # Test registering and trying again teos_client.register(user_id, teos_id, teos_base_endpoint) @@ -81,18 +78,20 @@ def test_commands_registered(run_bitcoind): appointment_data = build_appointment_data(commitment_txid, penalty_tx) appointment = teos_client.create_appointment(appointment_data) - add_appointment(appointment) + add_appointment(teos_id, appointment) # Get appointment - r = get_appointment_info(appointment_data.get("locator")) + r = get_appointment_info(teos_id, appointment_data.get("locator")) assert r.get("locator") == appointment.locator assert r.get("appointment") == appointment.to_dict() appointments_in_watcher += 1 -def test_appointment_life_cycle(run_bitcoind): +def test_appointment_life_cycle(teosd): global appointments_in_watcher, appointments_in_responder + _, teos_id = teosd + # First of all we need to register available_slots, subscription_expiry = teos_client.register(user_id, teos_id, teos_base_endpoint) @@ -101,11 +100,11 @@ def test_appointment_life_cycle(run_bitcoind): appointment_data = build_appointment_data(commitment_txid, penalty_tx) locator = compute_locator(commitment_txid) appointment = teos_client.create_appointment(appointment_data) - add_appointment(appointment) + add_appointment(teos_id, appointment) appointments_in_watcher += 1 # Get the information from the tower to check that it matches - appointment_info = get_appointment_info(locator) + appointment_info = get_appointment_info(teos_id, locator) assert appointment_info.get("status") == AppointmentStatus.BEING_WATCHED assert appointment_info.get("locator") == locator assert appointment_info.get("appointment") == appointment.to_dict() @@ -120,7 +119,7 @@ def test_appointment_life_cycle(run_bitcoind): # Trigger a breach and check again generate_block_with_transactions(commitment_tx) - appointment_info = get_appointment_info(locator) + appointment_info = get_appointment_info(teos_id, locator) assert appointment_info.get("status") == AppointmentStatus.DISPUTE_RESPONDED assert appointment_info.get("locator") == locator appointments_in_watcher -= 1 @@ -148,7 +147,7 @@ def test_appointment_life_cycle(run_bitcoind): # The appointment is no longer in the tower with pytest.raises(TowerResponseError): - get_appointment_info(locator) + get_appointment_info(teos_id, locator) # Check that the appointment is not in the Gatekeeper by checking the available slots (should have increase by 1) # We can do so by topping up the subscription (FIXME: find a better way to check this). @@ -159,8 +158,9 @@ def test_appointment_life_cycle(run_bitcoind): ) -def test_multiple_appointments_life_cycle(run_bitcoind): +def test_multiple_appointments_life_cycle(teosd): global appointments_in_watcher, appointments_in_responder + _, teos_id = teosd # Tests that get_all_appointments returns all the appointments the tower is storing at various stages in the # appointment lifecycle. appointments = [] @@ -184,7 +184,7 @@ def test_multiple_appointments_life_cycle(run_bitcoind): # Send all of them to watchtower. for appt in appointments: appointment = teos_client.create_appointment(appt.get("appointment_data")) - add_appointment(appointment) + add_appointment(teos_id, appointment) appointments_in_watcher += 1 # Two of these appointments are breached, and the watchtower responds to them. @@ -212,10 +212,11 @@ def test_multiple_appointments_life_cycle(run_bitcoind): # The appointment is no longer in the tower with pytest.raises(TowerResponseError): for appointment in appointments: - get_appointment_info(appointment["locator"]) + get_appointment_info(teos_id, appointment["locator"]) -def test_appointment_malformed_penalty(run_bitcoind): +def test_appointment_malformed_penalty(teosd): + _, teos_id = teosd # Lets start by creating two valid transaction commitment_tx, commitment_txid, penalty_tx = create_txs() @@ -226,10 +227,10 @@ def test_appointment_malformed_penalty(run_bitcoind): locator = compute_locator(commitment_txid) appointment = teos_client.create_appointment(appointment_data) - add_appointment(appointment) + add_appointment(teos_id, appointment) # Get the information from the tower to check that it matches - appointment_info = get_appointment_info(locator) + appointment_info = get_appointment_info(teos_id, locator) assert appointment_info.get("status") == AppointmentStatus.BEING_WATCHED assert appointment_info.get("locator") == locator assert appointment_info.get("appointment") == appointment.to_dict() @@ -239,10 +240,12 @@ def test_appointment_malformed_penalty(run_bitcoind): # The appointment should have been removed since the penalty_tx was malformed. with pytest.raises(TowerResponseError): - get_appointment_info(locator) + get_appointment_info(teos_id, locator) + +def test_appointment_wrong_decryption_key(teosd): + _, teos_id = teosd -def test_appointment_wrong_decryption_key(run_bitcoind): # This tests an appointment encrypted with a key that has not been derived from the same source as the locator. # Therefore the tower won't be able to decrypt the blob once the appointment is triggered. commitment_tx, _, penalty_tx = create_txs() @@ -275,10 +278,11 @@ def test_appointment_wrong_decryption_key(run_bitcoind): # The appointment should have been removed since the decryption failed. with pytest.raises(TowerResponseError): - get_appointment_info(appointment.locator) + get_appointment_info(teos_id, appointment.locator) -def test_two_identical_appointments(run_bitcoind): +def test_two_identical_appointments(teosd): + _, teos_id = teosd # Tests sending two identical appointments to the tower. # This tests sending an appointment with two valid transaction with the same locator. # If they come from the same user, the last one will be kept. @@ -289,14 +293,14 @@ def test_two_identical_appointments(run_bitcoind): # Send the appointment twice appointment = teos_client.create_appointment(appointment_data) - add_appointment(appointment) - add_appointment(appointment) + add_appointment(teos_id, appointment) + add_appointment(teos_id, appointment) # Broadcast the commitment transaction and mine a block generate_block_with_transactions(commitment_tx) # The last appointment should have made it to the Responder - appointment_info = get_appointment_info(locator) + appointment_info = get_appointment_info(teos_id, locator) assert appointment_info.get("status") == AppointmentStatus.DISPUTE_RESPONDED assert appointment_info.get("appointment").get("penalty_rawtx") == penalty_tx @@ -304,7 +308,8 @@ def test_two_identical_appointments(run_bitcoind): # FIXME: This test won't work since we're still passing appointment replicas to the Responder. # Uncomment when #88 is addressed -# def test_two_identical_appointments_different_users(run_bitcoind): +# def test_two_identical_appointments_different_users(teosd): +# _, teos_id = teosd # # Tests sending two identical appointments from different users to the tower. # # This tests sending an appointment with two valid transaction with the same locator. # # If they come from different users, both will be kept, but one will be dropped fro double-spending when passing @@ -320,13 +325,13 @@ def test_two_identical_appointments(run_bitcoind): # teos_client.register(tmp_user_id, teos_base_endpoint) # # # Send the appointment twice -# assert add_appointment(appointment_data) is True -# assert add_appointment(appointment_data, sk=tmp_user_sk) is True +# assert add_appointment(teos_id, appointment_data) is True +# assert add_appointment(teos_id, appointment_data, sk=tmp_user_sk) is True # # # Check that we can get it from both users -# appointment_info = get_appointment_info(locator) +# appointment_info = get_appointment_info(teos_id, locator) # assert appointment_info.get("status") == AppointmentStatus.BEING_WATCHED -# appointment_info = get_appointment_info(locator, sk=tmp_user_sk) +# appointment_info = get_appointment_info(teos_id, locator, sk=tmp_user_sk) # assert appointment_info.get("status") == AppointmentStatus.BEING_WATCHED # # # Broadcast the commitment transaction and mine a block @@ -334,8 +339,8 @@ def test_two_identical_appointments(run_bitcoind): # # # The last appointment should have made it to the Responder # sleep(1) -# appointment_info = get_appointment_info(locator) -# appointment_dup_info = get_appointment_info(locator, sk=tmp_user_sk) +# appointment_info = get_appointment_info(teos_id, locator) +# appointment_dup_info = get_appointment_info(teos_id, locator, sk=tmp_user_sk) # # # One of the two request must be None, while the other must be valid # assert (appointment_info is None and appointment_dup_info is not None) or ( @@ -348,7 +353,9 @@ def test_two_identical_appointments(run_bitcoind): # assert appointment_info.get("appointment").get("penalty_rawtx") == penalty_tx -def test_two_appointment_same_locator_different_penalty_different_users(run_bitcoind): +def test_two_appointment_same_locator_different_penalty_different_users(teosd): + _, teos_id = teosd + # This tests sending an appointment with two valid transaction with the same locator from different users commitment_tx, commitment_txid, penalty_tx1 = create_txs() @@ -367,9 +374,9 @@ def test_two_appointment_same_locator_different_penalty_different_users(run_bitc teos_client.register(tmp_user_id, teos_id, teos_base_endpoint) appointment_1 = teos_client.create_appointment(appointment1_data) - add_appointment(appointment_1) + add_appointment(teos_id, appointment_1) appointment_2 = teos_client.create_appointment(appointment2_data) - add_appointment(appointment_2, sk=tmp_user_sk) + add_appointment(teos_id, appointment_2, sk=tmp_user_sk) # Broadcast the commitment transaction and mine a block generate_block_with_transactions(commitment_tx) @@ -378,8 +385,8 @@ def test_two_appointment_same_locator_different_penalty_different_users(run_bitc # double-spending. That means that one of the responses from the tower should fail appointment_info = None with pytest.raises(TowerResponseError): - appointment_info = get_appointment_info(locator) - appointment2_info = get_appointment_info(locator, sk=tmp_user_sk) + appointment_info = get_appointment_info(teos_id, locator) + appointment2_info = get_appointment_info(teos_id, locator, sk=tmp_user_sk) if appointment_info is None: appointment_info = appointment2_info @@ -390,7 +397,8 @@ def test_two_appointment_same_locator_different_penalty_different_users(run_bitc assert appointment_info.get("appointment").get("penalty_tx") == appointment1_data.get("penalty_tx") -def test_add_appointment_trigger_on_cache(run_bitcoind): +def test_add_appointment_trigger_on_cache(teosd): + _, teos_id = teosd # This tests sending an appointment whose trigger is in the cache commitment_tx, commitment_txid, penalty_tx = create_txs() appointment_data = build_appointment_data(commitment_txid, penalty_tx) @@ -401,11 +409,12 @@ def test_add_appointment_trigger_on_cache(run_bitcoind): # Send the data to the tower and request it back. It should have gone straightaway to the Responder appointment = teos_client.create_appointment(appointment_data) - add_appointment(appointment) - assert get_appointment_info(locator).get("status") == AppointmentStatus.DISPUTE_RESPONDED + add_appointment(teos_id, appointment) + assert get_appointment_info(teos_id, locator).get("status") == AppointmentStatus.DISPUTE_RESPONDED -def test_add_appointment_invalid_trigger_on_cache(run_bitcoind): +def test_add_appointment_invalid_trigger_on_cache(teosd): + _, teos_id = teosd # This tests sending an invalid appointment which trigger is in the cache commitment_tx, commitment_txid, penalty_tx = create_txs() @@ -419,12 +428,14 @@ def test_add_appointment_invalid_trigger_on_cache(run_bitcoind): # Send the data to the tower and request it back. It should get accepted but the data will be dropped. appointment = teos_client.create_appointment(appointment_data) - add_appointment(appointment) + add_appointment(teos_id, appointment) with pytest.raises(TowerResponseError): - get_appointment_info(locator) + get_appointment_info(teos_id, locator) + +def test_add_appointment_trigger_on_cache_cannot_decrypt(teosd): + _, teos_id = teosd -def test_add_appointment_trigger_on_cache_cannot_decrypt(run_bitcoind): commitment_tx, _, penalty_tx = create_txs() # Let's send the commitment to the network and mine a block @@ -455,11 +466,11 @@ def test_add_appointment_trigger_on_cache_cannot_decrypt(run_bitcoind): # The appointment should should have been immediately dropped with pytest.raises(TowerResponseError): - get_appointment_info(appointment_data["locator"]) + get_appointment_info(teos_id, appointment_data["locator"]) -def test_appointment_shutdown_teos_trigger_back_online(run_bitcoind): - global teosd_process +def test_appointment_shutdown_teos_trigger_back_online(teosd): + teosd_process, teos_id = teosd # This tests data persistence. An appointment is sent to the tower, the tower is restarted and the appointment is # then triggered. teos_pid = teosd_process.pid @@ -469,7 +480,7 @@ def test_appointment_shutdown_teos_trigger_back_online(run_bitcoind): locator = compute_locator(commitment_txid) appointment = teos_client.create_appointment(appointment_data) - add_appointment(appointment) + add_appointment(teos_id, appointment) # Restart teos rpc_client = RPCClient(config.get("RPC_BIND"), config.get("RPC_PORT")) @@ -481,7 +492,7 @@ def test_appointment_shutdown_teos_trigger_back_online(run_bitcoind): assert teos_pid != teosd_process.pid # Check that the appointment is still in the Watcher - appointment_info = get_appointment_info(locator) + appointment_info = get_appointment_info(teos_id, locator) assert appointment_info.get("status") == AppointmentStatus.BEING_WATCHED assert appointment_info.get("appointment") == appointment.to_dict() @@ -490,12 +501,12 @@ def test_appointment_shutdown_teos_trigger_back_online(run_bitcoind): generate_block_with_transactions(commitment_tx) # The appointment should have been moved to the Responder - appointment_info = get_appointment_info(locator) + appointment_info = get_appointment_info(teos_id, locator) assert appointment_info.get("status") == AppointmentStatus.DISPUTE_RESPONDED -def test_appointment_shutdown_teos_trigger_while_offline(run_bitcoind): - global teosd_process +def test_appointment_shutdown_teos_trigger_while_offline(teosd): + teosd_process, teos_id = teosd # This tests data persistence. An appointment is sent to the tower and the tower is stopped. The appointment is then # triggered with the tower offline, and then the tower is brought back online. teos_pid = teosd_process.pid @@ -505,10 +516,10 @@ def test_appointment_shutdown_teos_trigger_while_offline(run_bitcoind): locator = compute_locator(commitment_txid) appointment = teos_client.create_appointment(appointment_data) - add_appointment(appointment) + add_appointment(teos_id, appointment) # Check that the appointment is still in the Watcher - appointment_info = get_appointment_info(locator) + appointment_info = get_appointment_info(teos_id, locator) assert appointment_info.get("status") == AppointmentStatus.BEING_WATCHED assert appointment_info.get("appointment") == appointment.to_dict() @@ -524,5 +535,5 @@ def test_appointment_shutdown_teos_trigger_while_offline(run_bitcoind): assert teos_pid != teosd_process.pid # The appointment should have been moved to the Responder - appointment_info = get_appointment_info(locator) + appointment_info = get_appointment_info(teos_id, locator) assert appointment_info.get("status") == AppointmentStatus.DISPUTE_RESPONDED diff --git a/test/teos/unit/cli/test_rpc_client.py b/test/teos/unit/cli/test_rpc_client.py new file mode 100644 index 00000000..48da73d6 --- /dev/null +++ b/test/teos/unit/cli/test_rpc_client.py @@ -0,0 +1,17 @@ +import pytest + +from teos.cli.rpc_client import RPCClient +from common.exceptions import InvalidParameter + +test_host = "test" +test_port = 4242 + + +@pytest.fixture +def rpc_client(): + return RPCClient(test_host, test_port) + + +def test_get_user_invalid_user_id(rpc_client): + with pytest.raises(InvalidParameter): + rpc_client.get_user("1234") # invalid user_id diff --git a/test/teos/unit/conftest.py b/test/teos/unit/conftest.py index 4f25e551..c874fc3b 100644 --- a/test/teos/unit/conftest.py +++ b/test/teos/unit/conftest.py @@ -83,7 +83,7 @@ def fork(block_hash, blocks): bitcoin_cli.generatetoaddress(blocks, bitcoin_cli.getnewaddress()) -@pytest.fixture(scope="session") +@pytest.fixture(scope="module") def generate_dummy_appointment(run_bitcoind): def _generate_dummy_appointment(): commitment_tx, commitment_txid, penalty_tx = create_txs() @@ -104,7 +104,7 @@ def _generate_dummy_appointment(): return _generate_dummy_appointment -@pytest.fixture(scope="session") +@pytest.fixture(scope="module") def generate_dummy_tracker(run_bitcoind): def _generate_dummy_tracker(commitment_tx=None): if not commitment_tx: diff --git a/test/teos/unit/test_watcher.py b/test/teos/unit/test_watcher.py index 6e70d4db..3a709859 100644 --- a/test/teos/unit/test_watcher.py +++ b/test/teos/unit/test_watcher.py @@ -50,7 +50,7 @@ MAX_APPOINTMENTS = 100 -@pytest.fixture(scope="session") +@pytest.fixture(scope="module") def temp_db_manager(): db_name = get_random_value_hex(8) db_manager = AppointmentsDBM(db_name)