Skip to content

Commit

Permalink
test_: one to one messages (#6119)
Browse files Browse the repository at this point in the history
* test_: one to one messages

* test_: use default display name

* test_: fix f-string format

* test_: fix signal log save

* test_: put signal saving under flag

* test_: addressed review comments

* test_: address review comment
  • Loading branch information
fbarbu15 authored Dec 3, 2024
1 parent cffd2cf commit ec90b2f
Show file tree
Hide file tree
Showing 10 changed files with 257 additions and 32 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,11 @@ __pycache__/
report/results.xml
tests-functional/coverage
tests-functional/reports
tests-functional/signals
tests-functional/*.log
pyrightconfig.json
.venv


# generated files
mock
Expand Down
8 changes: 6 additions & 2 deletions tests-functional/clients/rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import logging
import jsonschema
import requests

from tenacity import retry, stop_after_delay, wait_fixed
from conftest import option
from json import JSONDecodeError

Expand Down Expand Up @@ -42,6 +42,7 @@ def verify_is_json_rpc_error(self, response):
assert response.content
self._check_decode_and_key_errors_in_response(response, "error")

@retry(stop=stop_after_delay(10), wait=wait_fixed(0.5), reraise=True)
def rpc_request(self, method, params=[], request_id=13, url=None):
url = url if url else self.rpc_url
data = {"jsonrpc": "2.0", "method": method, "id": request_id}
Expand All @@ -50,7 +51,10 @@ def rpc_request(self, method, params=[], request_id=13, url=None):
logging.info(f"Sending POST request to url {url} with data: {json.dumps(data, sort_keys=True, indent=4)}")
response = self.client.post(url, json=data)
try:
logging.info(f"Got response: {json.dumps(response.json(), sort_keys=True, indent=4)}")
resp_json = response.json()
logging.info(f"Got response: {json.dumps(resp_json, sort_keys=True, indent=4)}")
if resp_json.get("error"):
assert "JSON-RPC client is unavailable" != resp_json["error"]
except JSONDecodeError:
logging.info(f"Got response: {response.content}")
return response
Expand Down
55 changes: 50 additions & 5 deletions tests-functional/clients/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,26 @@
import time

import websocket
import os
from pathlib import Path
from constants import SIGNALS_DIR, LOG_SIGNALS_TO_FILE
from datetime import datetime
from enum import Enum

class SignalType(Enum):
MESSAGES_NEW = "messages.new"
MESSAGE_DELIVERED = "message.delivered"
NODE_READY = "node.ready"
NODE_STARTED = "node.started"
NODE_LOGIN = "node.login"
MEDIASERVER_STARTED = "mediaserver.started"
WALLET_SUGGESTED_ROUTES = "wallet.suggested.routes"
WALLET_ROUTER_SIGN_TRANSACTIONS = "wallet.router.sign-transactions"
WALLET_ROUTER_SENDING_TRANSACTIONS_STARTED = "wallet.router.sending-transactions-started"
WALLET_TRANSACTION_STATUS_CHANGED = "wallet.transaction.status-changed"
WALLET_ROUTER_TRANSACTIONS_SENT = "wallet.router.transactions-sent"

class SignalClient:

def __init__(self, ws_url, await_signals):
self.url = f"{ws_url}/signals"

Expand All @@ -24,14 +40,20 @@ def __init__(self, ws_url, await_signals):
"accept_fn": None
} for signal in self.await_signals
}
if LOG_SIGNALS_TO_FILE:
self.signal_file_path = os.path.join(SIGNALS_DIR, f"signal_{ws_url.split(':')[-1]}_{datetime.now().strftime('%H%M%S')}.log")
Path(SIGNALS_DIR).mkdir(parents=True, exist_ok=True)

def on_message(self, ws, signal):
signal = json.loads(signal)
signal_type = signal.get("type")
signal_data = json.loads(signal)
if LOG_SIGNALS_TO_FILE:
self.write_signal_to_file(signal_data)

signal_type = signal_data.get("type")
if signal_type in self.await_signals:
accept_fn = self.received_signals[signal_type]["accept_fn"]
if not accept_fn or accept_fn(signal):
self.received_signals[signal_type]["received"].append(signal)
if not accept_fn or accept_fn(signal_data):
self.received_signals[signal_type]["received"].append(signal_data)

def check_signal_type(self, signal_type):
if signal_type not in self.await_signals:
Expand Down Expand Up @@ -65,6 +87,24 @@ def wait_for_signal(self, signal_type, timeout=20):
return self.received_signals[signal_type]["received"][-1]
return self.received_signals[signal_type]["received"][-delta_count:]

def find_signal_containing_pattern(self, signal_type, event_pattern, timeout=20):
start_time = time.time()
while True:
if time.time() - start_time >= timeout:
raise TimeoutError(
f"Signal {signal_type} containing {event_pattern} is not received in {timeout} seconds"
)
if not self.received_signals.get(signal_type):
time.sleep(0.2)
continue
for event in self.received_signals[signal_type]["received"]:
if event_pattern in str(event):
logging.info(
f"Signal {signal_type} containing {event_pattern} is received in {round(time.time() - start_time)} seconds"
)
return event
time.sleep(0.2)

def _on_error(self, ws, error):
logging.error(f"Error: {error}")

Expand All @@ -81,3 +121,8 @@ def _connect(self):
on_close=self._on_close)
ws.on_open = self._on_open
ws.run_forever()

def write_signal_to_file(self, signal_data):
with open(self.signal_file_path, "a+") as file:
json.dump(signal_data, file)
file.write("\n")
34 changes: 31 additions & 3 deletions tests-functional/clients/status_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
import random
import threading
import requests
from tenacity import retry, stop_after_delay, wait_fixed

from clients.signals import SignalClient
from clients.rpc import RpcClient
from datetime import datetime
from conftest import option
from constants import user_1
from constants import user_1, DEFAULT_DISPLAY_NAME



Expand Down Expand Up @@ -66,7 +67,7 @@ def init_status_backend(self, data_dir="/"):
}
return self.api_valid_request(method, data)

def create_account_and_login(self, display_name="Mr_Meeseeks", password=user_1.password):
def create_account_and_login(self, display_name=DEFAULT_DISPLAY_NAME, password=user_1.password):
data_dir = f"dataDir_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
method = "CreateAccountAndLogin"
data = {
Expand All @@ -78,7 +79,7 @@ def create_account_and_login(self, display_name="Mr_Meeseeks", password=user_1.p
}
return self.api_valid_request(method, data)

def restore_account_and_login(self, display_name="Mr_Meeseeks", user=user_1):
def restore_account_and_login(self, display_name=DEFAULT_DISPLAY_NAME, user=user_1):
method = "RestoreAccountAndLogin"
data = {
"rootDataDir": "/",
Expand Down Expand Up @@ -118,6 +119,7 @@ def restore_account_and_wait_for_rpc_client_to_start(self, timeout=60):
time.sleep(3)
raise TimeoutError(f"RPC client was not started after {timeout} seconds")

@retry(stop=stop_after_delay(10), wait=wait_fixed(0.5), reraise=True)
def start_messenger(self, params=[]):
method = "wakuext_startMessenger"
response = self.rpc_request(method, params)
Expand All @@ -139,3 +141,29 @@ def get_settings(self, params=[]):
method = "settings_getSettings"
response = self.rpc_request(method, params)
self.verify_is_valid_json_rpc_response(response)

def get_accounts(self, params=[]):
method = "accounts_getAccounts"
response = self.rpc_request(method, params)
self.verify_is_valid_json_rpc_response(response)
return response.json()

def get_pubkey(self, display_name):
response = self.get_accounts()
accounts = response.get("result", [])
for account in accounts:
if account.get("name") == display_name:
return account.get("public-key")
raise ValueError(f"Public key not found for display name: {display_name}")

def send_contact_request(self, params=[]):
method = "wakuext_sendContactRequest"
response = self.rpc_request(method, params)
self.verify_is_valid_json_rpc_response(response)
return response.json()

def send_message(self, params=[]):
method = "wakuext_sendOneToOneMessage"
response = self.rpc_request(method, params)
self.verify_is_valid_json_rpc_response(response)
return response.json()
6 changes: 6 additions & 0 deletions tests-functional/constants.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from dataclasses import dataclass
import os


@dataclass
Expand All @@ -21,3 +22,8 @@ class Account:
password="Strong12345",
passphrase="test test test test test test test test test test nest junk"
)
DEFAULT_DISPLAY_NAME = "Mr_Meeseeks"
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "../"))
TESTS_DIR = os.path.join(PROJECT_ROOT, "tests-functional")
SIGNALS_DIR = os.path.join(TESTS_DIR, "signals")
LOG_SIGNALS_TO_FILE = False # used for debugging purposes
2 changes: 2 additions & 0 deletions tests-functional/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ pytest==6.2.4
requests==2.31.0
genson~=1.2.2
websocket-client~=1.4.2
tenacity~=9.0.0
pytest-dependency~=0.6.0
69 changes: 65 additions & 4 deletions tests-functional/tests/test_cases.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from contextlib import contextmanager
import json
import logging
import threading
Expand All @@ -6,10 +7,10 @@

import pytest

from clients.signals import SignalClient
from clients.signals import SignalClient, SignalType
from clients.status_backend import RpcClient, StatusBackend
from conftest import option
from constants import user_1, user_2
from constants import user_1, user_2, DEFAULT_DISPLAY_NAME


class StatusDTestCase:
Expand All @@ -24,15 +25,15 @@ def setup_method(self):
class StatusBackendTestCase:

await_signals = [
"node.ready"
SignalType.NODE_READY.value
]

def setup_class(self):
self.rpc_client = StatusBackend(await_signals=self.await_signals)

self.rpc_client.init_status_backend()
self.rpc_client.restore_account_and_login()
self.rpc_client.wait_for_signal("node.ready")
self.rpc_client.wait_for_signal(SignalType.NODE_READY.value)

self.network_id = 31337

Expand Down Expand Up @@ -150,3 +151,63 @@ def setup_method(self):
websocket_thread = threading.Thread(target=self.signal_client._connect)
websocket_thread.daemon = True
websocket_thread.start()


class NetworkConditionTestCase:

@contextmanager
def add_latency(self):
pass
#TODO: To be implemented when we have docker exec capability

@contextmanager
def add_packet_loss(self):
pass
#TODO: To be implemented when we have docker exec capability

@contextmanager
def add_low_bandwith(self):
pass
#TODO: To be implemented when we have docker exec capability

@contextmanager
def node_pause(self, node):
pass
#TODO: To be implemented when we have docker exec capability

class OneToOneMessageTestCase(NetworkConditionTestCase):

def initialize_backend(self, await_signals, display_name=DEFAULT_DISPLAY_NAME, url=None):
backend = StatusBackend(await_signals=await_signals, url=url)
backend.init_status_backend()
backend.create_account_and_login(display_name=display_name)
backend.start_messenger()
return backend


def validate_event_against_response(self, event, fields_to_validate, response):
messages_in_event = event["event"]["messages"]
assert len(messages_in_event) > 0, "No messages found in the event"
response_chat = response["result"]["chats"][0]

message_id = response_chat["lastMessage"]["id"]
message = next((message for message in messages_in_event if message["id"] == message_id), None)
assert message, f"Message with ID {message_id} not found in the event"

message_mismatch = []
for response_field, event_field in fields_to_validate.items():
response_value = response_chat["lastMessage"][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)
)
18 changes: 9 additions & 9 deletions tests-functional/tests/test_init_status_app.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from test_cases import StatusBackend
import pytest
from clients.signals import SignalType
import os


@pytest.mark.create_account
@pytest.mark.rpc
class TestInitialiseApp:
Expand All @@ -12,10 +12,10 @@ def test_init_app(self):

await_signals = [

"mediaserver.started",
"node.started",
"node.ready",
"node.login",
SignalType.MEDIASERVER_STARTED.value,
SignalType.NODE_STARTED.value,
SignalType.NODE_READY.value,
SignalType.NODE_LOGIN.value,
]

backend_client = StatusBackend(await_signals)
Expand All @@ -24,13 +24,13 @@ def test_init_app(self):

assert backend_client is not None
backend_client.verify_json_schema(
backend_client.wait_for_signal("mediaserver.started"), "signal_mediaserver_started")
backend_client.wait_for_signal(SignalType.MEDIASERVER_STARTED.value), "signal_mediaserver_started")
backend_client.verify_json_schema(
backend_client.wait_for_signal("node.started"), "signal_node_started")
backend_client.wait_for_signal(SignalType.NODE_STARTED.value), "signal_node_started")
backend_client.verify_json_schema(
backend_client.wait_for_signal("node.ready"), "signal_node_ready")
backend_client.wait_for_signal(SignalType.NODE_READY.value), "signal_node_ready")
backend_client.verify_json_schema(
backend_client.wait_for_signal("node.login"), "signal_node_login")
backend_client.wait_for_signal(SignalType.NODE_LOGIN.value), "signal_node_login")


@pytest.mark.rpc
Expand Down
Loading

0 comments on commit ec90b2f

Please sign in to comment.