283 lines
11 KiB
Python
Raw Normal View History

from contextlib import contextmanager
import json
2024-10-11 11:08:39 +03:00
import logging
import threading
import time
2024-10-11 11:08:39 +03:00
from collections import namedtuple
from uuid import uuid4
2024-10-11 11:08:39 +03:00
import pytest
2024-12-09 12:18:34 +00:00
from clients.services.wallet import WalletService
from clients.signals import SignalClient, SignalType
2024-10-23 22:48:33 +03:00
from clients.status_backend import RpcClient, StatusBackend
2024-10-11 11:08:39 +03:00
from conftest import option
from resources.constants import user_1, user_2
from resources.enums import MessageContentType
2024-10-11 11:08:39 +03:00
class StatusDTestCase:
network_id = 31337
def setup_method(self):
self.rpc_client = RpcClient(option.rpc_url_statusd)
2024-10-11 11:08:39 +03:00
2024-10-23 22:48:33 +03:00
class StatusBackendTestCase:
reuse_container = True # Skip close_status_backend_containers cleanup
await_signals = [SignalType.NODE_LOGIN.value]
2024-11-12 15:30:13 +02:00
2024-12-09 12:18:34 +00:00
network_id = 31337
2024-10-23 22:48:33 +03:00
def setup_class(self):
2024-11-12 15:30:13 +02:00
self.rpc_client = StatusBackend(await_signals=self.await_signals)
2024-12-09 12:18:34 +00:00
self.wallet_service = WalletService(self.rpc_client)
self.rpc_client.init_status_backend()
self.rpc_client.restore_account_and_login()
self.rpc_client.wait_for_login()
def teardown_class(self):
for container in option.status_backend_containers:
container.kill()
2024-10-23 22:48:33 +03:00
class WalletTestCase(StatusBackendTestCase):
2024-09-18 18:54:17 +02:00
def wallet_create_multi_transaction(self, **kwargs):
method = "wallet_createMultiTransaction"
2024-10-23 22:48:33 +03:00
transfer_tx_data = {
"data": "",
"from": user_1.address,
"gas": "0x5BBF",
"input": "",
"maxFeePerGas": "0xbcc0f04fd",
"maxPriorityFeePerGas": "0xbcc0f04fd",
"to": user_2.address,
"type": "0x02",
"value": "0x5af3107a4000",
}
2024-09-18 18:54:17 +02:00
for key, new_value in kwargs.items():
2024-10-23 22:48:33 +03:00
if key in transfer_tx_data:
transfer_tx_data[key] = new_value
2024-09-18 18:54:17 +02:00
else:
logging.info(f"Warning: The key '{key}' does not exist in the transferTx parameters and will be ignored.")
params = [
{
"fromAddress": user_1.address,
"fromAmount": "0x5af3107a4000",
"fromAsset": "ETH",
2024-10-11 11:08:39 +03:00
"type": 0, # MultiTransactionSend
"toAddress": user_2.address,
"toAsset": "ETH",
},
[
{
"bridgeName": "Transfer",
"chainID": 31337,
"transferTx": transfer_tx_data,
}
],
f"{option.password}",
]
return self.rpc_client.rpc_request(method, params)
def send_valid_multi_transaction(self, **kwargs):
response = self.wallet_create_multi_transaction(**kwargs)
tx_hash = None
self.rpc_client.verify_is_valid_json_rpc_response(response)
try:
tx_hash = response.json()["result"]["hashes"][str(self.network_id)][0]
except (KeyError, json.JSONDecodeError):
raise Exception(response.content)
return tx_hash
2024-10-11 11:08:39 +03:00
class TransactionTestCase(WalletTestCase):
def setup_method(self):
self.tx_hash = self.send_valid_multi_transaction()
2024-10-11 11:08:39 +03:00
class EthRpcTestCase(WalletTestCase):
@pytest.fixture(autouse=True, scope="class")
def tx_data(self):
tx_hash = self.send_valid_multi_transaction()
self.wait_until_tx_not_pending(tx_hash)
receipt = self.get_transaction_receipt(tx_hash)
try:
block_number = receipt.json()["result"]["blockNumber"]
block_hash = receipt.json()["result"]["blockHash"]
except (KeyError, json.JSONDecodeError):
raise Exception(receipt.content)
2024-10-11 11:08:39 +03:00
2024-10-23 22:48:33 +03:00
tx_data = namedtuple("TxData", ["tx_hash", "block_number", "block_hash"])
return tx_data(tx_hash, block_number, block_hash)
def get_block_header(self, block_number):
method = "ethclient_headerByNumber"
params = [self.network_id, block_number]
return self.rpc_client.rpc_valid_request(method, params)
def get_transaction_receipt(self, tx_hash):
method = "ethclient_transactionReceipt"
params = [self.network_id, tx_hash]
return self.rpc_client.rpc_valid_request(method, params)
def wait_until_tx_not_pending(self, tx_hash, timeout=10):
method = "ethclient_transactionByHash"
params = [self.network_id, tx_hash]
response = self.rpc_client.rpc_valid_request(method, params)
start_time = time.time()
while response.json()["result"]["isPending"] is True:
time_passed = time.time() - start_time
if time_passed >= timeout:
raise TimeoutError(f"Tx {tx_hash} is still pending after {timeout} seconds")
time.sleep(0.5)
response = self.rpc_client.rpc_valid_request(method, params)
return response.json()["result"]["tx"]
2024-10-11 11:08:39 +03:00
class SignalTestCase(StatusDTestCase):
await_signals = []
def setup_method(self):
super().setup_method()
self.signal_client = SignalClient(option.ws_url_statusd, self.await_signals)
websocket_thread = threading.Thread(target=self.signal_client._connect)
websocket_thread.daemon = True
websocket_thread.start()
class NetworkConditionTestCase:
@contextmanager
def add_latency(self, node, latency=300, jitter=50):
logging.info("Entering context manager: add_latency")
node.container_exec(f"apk add iproute2 && tc qdisc add dev eth0 root netem delay {latency}ms {jitter}ms distribution normal")
try:
yield
finally:
logging.info("Exiting context manager: add_latency")
node.container_exec("tc qdisc del dev eth0 root")
@contextmanager
def add_packet_loss(self, node, packet_loss=2):
logging.info("Entering context manager: add_packet_loss")
node.container_exec(f"apk add iproute2 && tc qdisc add dev eth0 root netem loss {packet_loss}%")
try:
yield
finally:
logging.info("Exiting context manager: add_packet_loss")
node.container_exec("tc qdisc del dev eth0 root netem")
@contextmanager
def add_low_bandwith(self, node, rate="1mbit", burst="32kbit", limit="12500"):
logging.info("Entering context manager: add_low_bandwith")
node.container_exec(f"apk add iproute2 && tc qdisc add dev eth0 root tbf rate {rate} burst {burst} limit {limit}")
try:
yield
finally:
logging.info("Exiting context manager: add_low_bandwith")
node.container_exec("tc qdisc del dev eth0 root")
@contextmanager
def node_pause(self, node):
logging.info("Entering context manager: node_pause")
node.container_pause()
try:
yield
finally:
logging.info("Exiting context manager: node_pause")
node.container_unpause()
class MessengerTestCase(NetworkConditionTestCase):
await_signals = [
SignalType.MESSAGES_NEW.value,
SignalType.MESSAGE_DELIVERED.value,
SignalType.NODE_LOGIN.value,
]
@pytest.fixture(scope="function", autouse=False)
def setup_two_nodes(self, request):
request.cls.sender = self.sender = self.initialize_backend(await_signals=self.await_signals)
request.cls.receiver = self.receiver = self.initialize_backend(await_signals=self.await_signals)
def initialize_backend(self, await_signals):
backend = StatusBackend(await_signals=await_signals, privileged=True)
backend.init_status_backend()
backend.create_account_and_login()
backend.find_public_key()
backend.wakuext_service.start_messenger()
return backend
def make_contacts(self):
existing_contacts = self.receiver.wakuext_service.get_contacts()
if self.sender.public_key in str(existing_contacts):
return
response = self.sender.wakuext_service.send_contact_request(self.receiver.public_key, "contact_request")
expected_message = self.get_message_by_content_type(response, content_type=MessageContentType.CONTACT_REQUEST.value)[0]
self.receiver.find_signal_containing_pattern(SignalType.MESSAGES_NEW.value, event_pattern=expected_message.get("id"))
self.receiver.wakuext_service.accept_contact_request(expected_message.get("id"))
accepted_signal = f"@{self.receiver.public_key} accepted your contact request"
self.sender.find_signal_containing_pattern(SignalType.MESSAGES_NEW.value, event_pattern=accepted_signal)
def validate_signal_event_against_response(self, signal_event, fields_to_validate, expected_message):
expected_message_id = expected_message.get("id")
signal_event_messages = signal_event.get("event", {}).get("messages")
assert len(signal_event_messages) > 0, "No messages found in the signal event"
message = next(
(message for message in signal_event_messages if message.get("id") == expected_message_id),
None,
)
assert message, f"Message with ID {expected_message_id} not found in the signal event"
message_mismatch = []
for response_field, event_field in fields_to_validate.items():
response_value = expected_message[response_field]
event_value = message[event_field]
if response_value != event_value:
message_mismatch.append(f"Field '{response_field}': Expected '{response_value}', Found '{event_value}'")
if not message_mismatch:
return
raise AssertionError(
"Some Sender RPC responses are not matching the signals received by the receiver.\n"
"Details of mismatches:\n" + "\n".join(message_mismatch)
)
def get_message_by_content_type(self, response, content_type, message_pattern=""):
matched_messages = []
messages = response.get("result", {}).get("messages", [])
for message in messages:
if message.get("contentType") != content_type:
continue
if not message_pattern or message_pattern in str(message):
matched_messages.append(message)
if matched_messages:
return matched_messages
else:
raise ValueError(f"Failed to find a message with contentType '{content_type}' in response")
def join_private_group(self):
private_group_name = f"private_group_{uuid4()}"
response = self.sender.wakuext_service.create_group_chat_with_members([self.receiver.public_key], private_group_name)
expected_group_creation_msg = f"@{self.sender.public_key} created the group {private_group_name}"
expected_message = self.get_message_by_content_type(
response, content_type=MessageContentType.SYSTEM_MESSAGE_CONTENT_PRIVATE_GROUP.value, message_pattern=expected_group_creation_msg
)[0]
self.receiver.find_signal_containing_pattern(SignalType.MESSAGES_NEW.value, event_pattern=expected_message.get("id"), timeout=60)
return response.get("result", {}).get("chats", [])[0].get("id")