From 386a772e0d90463b165a6b124778338c90ab75fb Mon Sep 17 00:00:00 2001 From: shashank sanket Date: Sun, 3 Nov 2024 16:26:33 +0530 Subject: [PATCH] test_: Code Migration from status-cli-tests for 1 on 1 message (#6022) * test_: Code Migration from status-cli-tests 1_1 message test_: Code Migration from status-cli-tests 1_1 message test_: Code Migration from status-cli-tests addressing review comments test_: Code Migration from status-cli-tests 1_1 message rebase * test_: Code Migration from status-cli-tests for 1 on 1 message and added event validation * test_: Code Migration from status-cli-tests for 1 on 1 logger fix --- tests-functional/constants.py | 5 +- tests-functional/src/node/status_node.py | 2 +- .../tests/test_contact_request.py | 2 +- .../tests/test_one_to_one_messages.py | 114 ++++++++++++++++++ .../validators/contact_request_validator.py | 26 +++- .../validators/message_validator.py | 54 +++++++++ 6 files changed, 193 insertions(+), 10 deletions(-) create mode 100644 tests-functional/tests/test_one_to_one_messages.py create mode 100644 tests-functional/validators/message_validator.py diff --git a/tests-functional/constants.py b/tests-functional/constants.py index a5e5da8d7..aaae0b54b 100644 --- a/tests-functional/constants.py +++ b/tests-functional/constants.py @@ -47,5 +47,6 @@ PACKET_LOSS_CMD = "sudo tc qdisc add dev eth0 root netem loss 50%" LOW_BANDWIDTH_CMD = "sudo tc qdisc add dev eth0 root tbf rate 1kbit burst 1kbit" REMOVE_TC_CMD = "sudo tc qdisc del dev eth0 root" NUM_CONTACT_REQUESTS = int(os.getenv("NUM_CONTACT_REQUESTS", "5")) -NUM_MESSAGES = int(os.getenv("NUM_MESSAGES", "25")) -DELAY_BETWEEN_MESSAGES = int(os.getenv("NUM_MESSAGES", "1")) \ No newline at end of file +NUM_MESSAGES = int(os.getenv("NUM_MESSAGES", "20")) +DELAY_BETWEEN_MESSAGES = int(os.getenv("NUM_MESSAGES", "1")) +EVENT_SIGNAL_TIMEOUT_SEC = int(os.getenv("EVENT_SIGNAL_TIMEOUT_SEC", "4")) diff --git a/tests-functional/src/node/status_node.py b/tests-functional/src/node/status_node.py index c87fdcfb1..872944f22 100644 --- a/tests-functional/src/node/status_node.py +++ b/tests-functional/src/node/status_node.py @@ -81,7 +81,7 @@ class StatusNode: def start_signal_client(self): ws_url = f"ws://localhost:{self.port}" - await_signals = ["history.request.started", "messages.new", "history.request.completed"] + await_signals = ["history.request.started", "messages.new", "message.delivered", "history.request.completed"] self.signal_client = SignalClient(ws_url, await_signals) websocket_thread = threading.Thread(target=self.signal_client._connect) diff --git a/tests-functional/tests/test_contact_request.py b/tests-functional/tests/test_contact_request.py index 482dc3f20..9450f8fe2 100644 --- a/tests-functional/tests/test_contact_request.py +++ b/tests-functional/tests/test_contact_request.py @@ -12,7 +12,7 @@ logger = get_custom_logger(__name__) class TestContactRequest(StepsCommon): def test_contact_request_baseline(self): - timeout_secs = 3 + timeout_secs = 5 num_contact_requests = NUM_CONTACT_REQUESTS project_root = get_project_root() nodes = [] diff --git a/tests-functional/tests/test_one_to_one_messages.py b/tests-functional/tests/test_one_to_one_messages.py new file mode 100644 index 000000000..61dd082ca --- /dev/null +++ b/tests-functional/tests/test_one_to_one_messages.py @@ -0,0 +1,114 @@ +from uuid import uuid4 +import pytest +from constants import * +from src.libs.common import delay +from src.libs.custom_logger import get_custom_logger +from src.steps.common import StepsCommon +from validators.message_validator import MessageValidator + +logger = get_custom_logger(__name__) + + +@pytest.mark.usefixtures("start_2_nodes") +class TestOneToOneMessages(StepsCommon): + def test_one_to_one_message_baseline(self): + timeout_secs = EVENT_SIGNAL_TIMEOUT_SEC + num_messages = NUM_MESSAGES + nodes = [ + (self.first_node, self.second_node, "second_node_user"), + (self.second_node, self.first_node, "first_node_user") + ] + messages = [] + self.accept_contact_request() + + missing_messages = [] + + for i in range(num_messages): + sending_node, receiving_node, receiving_display_name = nodes[i % 2] + result = self.send_and_wait_for_message( + sending_node, receiving_node, receiving_display_name, i, timeout_secs) + timestamp, message_text, message_id, response = result + + if not response: + missing_messages.append((timestamp, message_text, message_id, sending_node.name)) + else: + messages.append((timestamp, message_text, message_id, sending_node.name)) + + self.first_node.stop() + self.second_node.stop() + + if missing_messages: + formatted_missing_messages = [ + f"Timestamp: {ts}, Message: {msg}, ID: {mid}, Sender: {snd}" + for ts, msg, mid, snd in missing_messages + ] + raise AssertionError( + f"{len(missing_messages)} messages out of {num_messages} were not received: " + + "\n".join(formatted_missing_messages) + ) + + def send_and_wait_for_message(self, sending_node, receiving_node, display_name, index, timeout=10): + receiving_node_pubkey = receiving_node.get_pubkey(display_name) + message_text = f"message_from_{sending_node.name}_{index}" + + timestamp, message_id, response = self.send_with_timestamp( + sending_node.send_message, receiving_node_pubkey, message_text + ) + + validator = MessageValidator(response) + validator.run_all_validations( + expected_chat_id=receiving_node_pubkey, + expected_display_name=display_name, + expected_text=message_text + ) + + try: + messages_new_events = receiving_node.wait_for_complete_signal("messages.new", timeout) + receiving_node.wait_for_signal("message.delivered", timeout) + + messages_new_event = None + for event in messages_new_events: + if "chats" in event.get("event", {}): + messages_new_event = event + try: + validator.validate_event_against_response( + messages_new_event, + fields_to_validate={ + "text": "text", + "displayName": "displayName", + "id": "id" + } + ) + break + except AssertionError as validation_error: + logger.error(f"Validation failed for event: {messages_new_event}, Error: {validation_error}") + continue + + if messages_new_event is None: + raise ValueError("No 'messages.new' event with 'chats' data found within the timeout period.") + + except (TimeoutError, ValueError) as e: + logger.error(f"Signal validation failed: {str(e)}") + return timestamp, message_text, message_id, None + + return timestamp, message_text, message_id, response + + def test_one_to_one_message_with_latency(self): + with self.add_latency(): + self.test_one_to_one_message_baseline() + + def test_one_to_one_message_with_packet_loss(self): + with self.add_packet_loss(): + self.test_one_to_one_message_baseline() + + def test_one_to_one_message_with_low_bandwidth(self): + with self.add_low_bandwidth(): + self.test_one_to_one_message_baseline() + + def test_one_to_one_message_with_node_pause_30_seconds(self): + self.accept_contact_request() + with self.node_pause(self.first_node): + message = str(uuid4()) + self.second_node.send_message(self.first_node_pubkey, message) + delay(30) + assert self.second_node.wait_for_signal("messages.new") diff --git a/tests-functional/validators/contact_request_validator.py b/tests-functional/validators/contact_request_validator.py index f0bfdb727..f98d56fc5 100644 --- a/tests-functional/validators/contact_request_validator.py +++ b/tests-functional/validators/contact_request_validator.py @@ -2,7 +2,6 @@ from src.libs.custom_logger import get_custom_logger logger = get_custom_logger(__name__) - class ContactRequestValidator: def __init__(self, response): self.response = response @@ -16,12 +15,27 @@ class ContactRequestValidator: assert len(chats) > 0, "No chats found in the response" chat = chats[0] - assert chat.get("id") == expected_chat_id, f"Chat ID mismatch: Expected {expected_chat_id}" - assert chat.get("name").startswith("0x"), "Invalid chat name format" + actual_chat_id = chat.get("id") + assert actual_chat_id == expected_chat_id, ( + f"Chat ID mismatch: Expected '{expected_chat_id}', found '{actual_chat_id}'" + ) + + actual_chat_name = chat.get("name") + assert actual_chat_name.startswith("0x"), ( + f"Invalid chat name format: Expected name to start with '0x', found '{actual_chat_name}'" + ) last_message = chat.get("lastMessage", {}) - assert last_message.get("text") == expected_text, "Message text mismatch" - assert last_message.get("contactRequestState") == 1, "Unexpected contact request state" + actual_text = last_message.get("text") + assert actual_text == expected_text, ( + f"Message text mismatch: Expected '{expected_text}', found '{actual_text}'" + ) + + actual_contact_request_state = last_message.get("contactRequestState") + assert actual_contact_request_state == 1, ( + f"Unexpected contact request state: Expected '1', found '{actual_contact_request_state}'" + ) + assert "compressedKey" in last_message, "Missing 'compressedKey' in last message" def validate_event_against_response(self, event, fields_to_validate): @@ -35,7 +49,7 @@ class ContactRequestValidator: response_value = response_chat.get("lastMessage", {}).get(response_field) event_value = event_chat.get("lastMessage", {}).get(event_field) assert response_value == event_value, ( - f"Mismatch for '{response_field}': expected '{response_value}', got '{event_value}'" + f"Mismatch for '{response_field}': Expected '{response_value}', found '{event_value}'" ) def run_all_validations(self, expected_chat_id, expected_display_name, expected_text): diff --git a/tests-functional/validators/message_validator.py b/tests-functional/validators/message_validator.py new file mode 100644 index 000000000..48397c0ce --- /dev/null +++ b/tests-functional/validators/message_validator.py @@ -0,0 +1,54 @@ +from src.libs.custom_logger import get_custom_logger + +logger = get_custom_logger(__name__) + + +class MessageValidator: + def __init__(self, response): + self.response = response + + def validate_response_structure(self): + assert self.response.get("jsonrpc") == "2.0", "Invalid JSON-RPC version" + assert "result" in self.response, "Missing 'result' in response" + + def validate_chat_data(self, expected_chat_id, expected_display_name, expected_text): + chats = self.response["result"].get("chats", []) + assert len(chats) > 0, "No chats found in the response" + + chat = chats[0] + actual_chat_id = chat.get("id") + assert actual_chat_id == expected_chat_id, ( + f"Chat ID mismatch: Expected '{expected_chat_id}', found '{actual_chat_id}'" + ) + + actual_chat_name = chat.get("name") + assert actual_chat_name.startswith("0x"), ( + f"Invalid chat name format: Expected name to start with '0x', found '{actual_chat_name}'" + ) + + last_message = chat.get("lastMessage", {}) + actual_text = last_message.get("text") + assert actual_text == expected_text, ( + f"Message text mismatch: Expected '{expected_text}', found '{actual_text}'" + ) + + assert "compressedKey" in last_message, "Missing 'compressedKey' in last message" + + def validate_event_against_response(self, event, fields_to_validate): + chats_in_event = event.get("event", {}).get("chats", []) + assert len(chats_in_event) > 0, "No chats found in the event" + + response_chat = self.response["result"]["chats"][0] + event_chat = chats_in_event[0] + + for response_field, event_field in fields_to_validate.items(): + response_value = response_chat.get("lastMessage", {}).get(response_field) + event_value = event_chat.get("lastMessage", {}).get(event_field) + assert response_value == event_value, ( + f"Mismatch for '{response_field}': Expected '{response_value}', found '{event_value}'" + ) + + def run_all_validations(self, expected_chat_id, expected_display_name, expected_text): + self.validate_response_structure() + self.validate_chat_data(expected_chat_id, expected_display_name, expected_text) + logger.info("All validations passed for the one-to-one message response.")