Relay Publish: Extend coverage (#2)

* small improvements

* minor adjustments

* new tests

* try with more runners

* try with more runners2

* tweaks for parallel run

* new tests

* small tweaks

* new tests

* new tests

* test remove defaults from CI

* handle empty strings for env vars in CI

* add nodekey to main node

* add more tests

* finishing touches

* fixes based on Alex suggestions

* revert unwanted change

* add new pause test
This commit is contained in:
Florin Barbu 2023-11-17 08:47:22 +02:00 committed by GitHub
parent 88b1fa3e90
commit 589368f434
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 546 additions and 156 deletions

View File

@ -11,7 +11,7 @@ on:
node1: node1:
required: false required: false
type: string type: string
default: "wakuorg/nwaku:deploy-wakuv2-test" default: "wakuorg/nwaku:latest"
node2: node2:
required: false required: false
type: string type: string
@ -27,8 +27,8 @@ on:
env: env:
FORCE_COLOR: "1" FORCE_COLOR: "1"
NODE_1: ${{ inputs.node1 || 'wakuorg/nwaku:deploy-wakuv2-test' }} NODE_1: ${{ inputs.node1 }}
NODE_2: ${{ inputs.node2 || 'wakuorg/go-waku:latest' }} NODE_2: ${{ inputs.node2 }}
PROTOCOL: ${{ inputs.protocol || 'REST' }} PROTOCOL: ${{ inputs.protocol || 'REST' }}
jobs: jobs:
@ -49,7 +49,7 @@ jobs:
- run: pip install -r requirements.txt - run: pip install -r requirements.txt
- name: Run tests - name: Run tests
run: pytest -n 3 --reruns 1 --alluredir=allure-results run: pytest -n 4 --reruns 2 --alluredir=allure-results
- name: Get allure history - name: Get allure history
if: always() if: always()

View File

@ -5,3 +5,4 @@ log_cli = True
log_file = log/test.log log_file = log/test.log
log_cli_format = %(asctime)s %(name)s %(levelname)s %(message)s log_cli_format = %(asctime)s %(name)s %(levelname)s %(message)s
log_file_format = %(asctime)s %(name)s %(levelname)s %(message)s log_file_format = %(asctime)s %(name)s %(levelname)s %(message)s
timeout = 300

View File

@ -6,8 +6,10 @@ pre-commit
pyright pyright
pytest pytest
pytest-instafail pytest-instafail
pytest-timeout
pytest-xdist pytest-xdist
pytest-rerunfailures pytest-rerunfailures
python-dotenv python-dotenv
requests requests
tenacity tenacity
typeguard

View File

@ -1,13 +1,6 @@
from dataclasses import dataclass, field from dataclasses import dataclass, field
from marshmallow_dataclass import class_schema from marshmallow_dataclass import class_schema
from typing import Optional from typing import Optional, Union
@dataclass
class MessageRpcQuery:
payload: str
contentTopic: str
timestamp: Optional[int] = None
@dataclass @dataclass
@ -15,9 +8,10 @@ class MessageRpcResponse:
payload: str payload: str
contentTopic: str contentTopic: str
version: Optional[int] version: Optional[int]
timestamp: int timestamp: Optional[int]
ephemeral: Optional[bool] ephemeral: Optional[bool]
rateLimitProof: Optional[dict] = field(default_factory=dict) meta: Optional[str]
rateLimitProof: Optional[Union[dict, str]] = field(default_factory=dict)
rate_limit_proof: Optional[dict] = field(default_factory=dict) rate_limit_proof: Optional[dict] = field(default_factory=dict)

View File

@ -6,20 +6,23 @@ load_dotenv() # This will load environment variables from a .env file if it exi
def get_env_var(var_name, default=None): def get_env_var(var_name, default=None):
env_var = os.getenv(var_name, default) env_var = os.getenv(var_name, default)
if env_var is not None: if env_var in [None, ""]:
print(f"{var_name}: {env_var}")
else:
print(f"{var_name} is not set; using default value: {default}") print(f"{var_name} is not set; using default value: {default}")
env_var = default
print(f"{var_name}: {env_var}")
return env_var return env_var
# Configuration constants. Need to be upercase to appear in reports # Configuration constants. Need to be upercase to appear in reports
NODE_1 = get_env_var("NODE_1", "wakuorg/nwaku:latest") NODE_1 = get_env_var("NODE_1", "wakuorg/go-waku:latest")
NODE_2 = get_env_var("NODE_2", "wakuorg/go-waku:latest") NODE_2 = get_env_var("NODE_2", "wakuorg/nwaku:latest")
LOG_DIR = get_env_var("LOG_DIR", "./log") DOCKER_LOG_DIR = get_env_var("DOCKER_LOG_DIR", "./log/docker")
NETWORK_NAME = get_env_var("NETWORK_NAME", "waku") NETWORK_NAME = get_env_var("NETWORK_NAME", "waku")
SUBNET = get_env_var("SUBNET", "172.18.0.0/16") SUBNET = get_env_var("SUBNET", "172.18.0.0/16")
IP_RANGE = get_env_var("IP_RANGE", "172.18.0.0/24") IP_RANGE = get_env_var("IP_RANGE", "172.18.0.0/24")
GATEWAY = get_env_var("GATEWAY", "172.18.0.1") GATEWAY = get_env_var("GATEWAY", "172.18.0.1")
DEFAULT_PUBSUBTOPIC = get_env_var("DEFAULT_PUBSUBTOPIC", "/waku/2/default-waku/proto") DEFAULT_PUBSUB_TOPIC = get_env_var("DEFAULT_PUBSUB_TOPIC", "/waku/2/default-waku/proto")
PROTOCOL = get_env_var("PROTOCOL", "REST") PROTOCOL = get_env_var("PROTOCOL", "REST")
RUNNING_IN_CI = get_env_var("CI")
NODEKEY = get_env_var("NODEKEY", "30348dd51465150e04a5d9d932c72864c8967f806cce60b5d26afeca1e77eb68")
API_REQUEST_TIMEOUT = get_env_var("API_REQUEST_TIMEOUT", 10)

View File

@ -1,9 +1,10 @@
import logging from time import sleep
from src.libs.custom_logger import get_custom_logger
import os import os
import base64 import base64
import allure import allure
logger = logging.getLogger(__name__) logger = get_custom_logger(__name__)
def bytes_to_hex(byte_array): def bytes_to_hex(byte_array):
@ -24,5 +25,10 @@ def to_base64(input_data):
def attach_allure_file(file): def attach_allure_file(file):
logger.debug("Attaching file %s", file) logger.debug(f"Attaching file {file}")
allure.attach.file(file, name=os.path.basename(file), attachment_type=allure.attachment_type.TEXT) allure.attach.file(file, name=os.path.basename(file), attachment_type=allure.attachment_type.TEXT)
def delay(num_seconds):
logger.debug(f"Sleeping for {num_seconds} seconds")
sleep(num_seconds)

24
src/libs/custom_logger.py Normal file
View File

@ -0,0 +1,24 @@
import logging
max_log_line_length = 5000
def log_length_filter(max_length):
class logLengthFilter(logging.Filter):
def filter(self, record):
if len(record.getMessage()) > max_length:
logging.getLogger(record.name).log(
record.levelno, f"Log line was discarded because it's longer than max_log_line_length={max_log_line_length}"
)
return False
return True
return logLengthFilter()
def get_custom_logger(name):
logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("docker").setLevel(logging.WARNING)
logger = logging.getLogger(name)
logger.addFilter(log_length_filter(max_log_line_length))
return logger

View File

@ -1,9 +1,10 @@
import logging
import requests import requests
from tenacity import retry, stop_after_delay, wait_fixed from tenacity import retry, stop_after_delay, wait_fixed
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from src.env_vars import API_REQUEST_TIMEOUT
from src.libs.custom_logger import get_custom_logger
logger = logging.getLogger(__name__) logger = get_custom_logger(__name__)
class BaseClient(ABC): class BaseClient(ABC):
@ -11,20 +12,20 @@ class BaseClient(ABC):
# useful when running tests in parallel, where occasional network-related errors such as # useful when running tests in parallel, where occasional network-related errors such as
# connection drops, timeouts, or temporary unavailability of a service can occur. Retrying # connection drops, timeouts, or temporary unavailability of a service can occur. Retrying
# ensures that such intermittent issues don't cause the tests to fail outright. # ensures that such intermittent issues don't cause the tests to fail outright.
@retry(stop=stop_after_delay(2), wait=wait_fixed(0.1), reraise=True) @retry(stop=stop_after_delay(0.5), wait=wait_fixed(0.1), reraise=True)
def make_request(self, method, url, headers=None, data=None): def make_request(self, method, url, headers=None, data=None):
logger.debug("%s call: %s with payload: %s", method.upper(), url, data) logger.debug(f"{method.upper()} call: {url} with payload: {data}")
response = requests.request(method.upper(), url, headers=headers, data=data) response = requests.request(method.upper(), url, headers=headers, data=data, timeout=API_REQUEST_TIMEOUT)
try: try:
response.raise_for_status() response.raise_for_status()
except requests.HTTPError as http_err: except requests.HTTPError as http_err:
logger.error("HTTP error occurred: %s", http_err) logger.error(f"HTTP error occurred: {http_err}. Response content: {response.content}")
raise raise
except Exception as err: except Exception as err:
logger.error("An error occurred: %s", err) logger.error(f"An error occurred: {err}. Response content: {response.content}")
raise raise
else: else:
logger.info("Response status code: %s", response.status_code) logger.info(f"Response status code: {response.status_code}. Response content: {response.content}")
return response return response
@abstractmethod @abstractmethod

View File

@ -1,10 +1,10 @@
import logging from src.libs.custom_logger import get_custom_logger
import json import json
from dataclasses import asdict from dataclasses import asdict
from urllib.parse import quote from urllib.parse import quote
from src.node.api_clients.base_client import BaseClient from src.node.api_clients.base_client import BaseClient
logger = logging.getLogger(__name__) logger = get_custom_logger(__name__)
class REST(BaseClient): class REST(BaseClient):
@ -24,7 +24,7 @@ class REST(BaseClient):
return self.rest_call("post", "relay/v1/subscriptions", json.dumps(pubsub_topics)) return self.rest_call("post", "relay/v1/subscriptions", json.dumps(pubsub_topics))
def send_message(self, message, pubsub_topic): def send_message(self, message, pubsub_topic):
return self.rest_call("post", f"relay/v1/messages/{quote(pubsub_topic, safe='')}", json.dumps(asdict(message))) return self.rest_call("post", f"relay/v1/messages/{quote(pubsub_topic, safe='')}", json.dumps(message))
def get_messages(self, pubsub_topic): def get_messages(self, pubsub_topic):
get_messages_response = self.rest_call("get", f"relay/v1/messages/{quote(pubsub_topic, safe='')}") get_messages_response = self.rest_call("get", f"relay/v1/messages/{quote(pubsub_topic, safe='')}")

View File

@ -1,9 +1,9 @@
import logging from src.libs.custom_logger import get_custom_logger
import json import json
from dataclasses import asdict from dataclasses import asdict
from src.node.api_clients.base_client import BaseClient from src.node.api_clients.base_client import BaseClient
logger = logging.getLogger(__name__) logger = get_custom_logger(__name__)
class RPC(BaseClient): class RPC(BaseClient):
@ -28,7 +28,7 @@ class RPC(BaseClient):
return self.rpc_call("post_waku_v2_relay_v1_subscription", [pubsub_topics]) return self.rpc_call("post_waku_v2_relay_v1_subscription", [pubsub_topics])
def send_message(self, message, pubsub_topic): def send_message(self, message, pubsub_topic):
return self.rpc_call("post_waku_v2_relay_v1_message", [pubsub_topic, asdict(message)]) return self.rpc_call("post_waku_v2_relay_v1_message", [pubsub_topic, message])
def get_messages(self, pubsub_topic): def get_messages(self, pubsub_topic):
get_messages_response = self.rpc_call("get_waku_v2_relay_v1_messages", [pubsub_topic]) get_messages_response = self.rpc_call("get_waku_v2_relay_v1_messages", [pubsub_topic])

View File

@ -1,5 +1,5 @@
import os import os
import logging from src.libs.custom_logger import get_custom_logger
import random import random
import threading import threading
import docker import docker
@ -7,20 +7,20 @@ from src.env_vars import NETWORK_NAME, SUBNET, IP_RANGE, GATEWAY
from docker.types import IPAMConfig, IPAMPool from docker.types import IPAMConfig, IPAMPool
from docker.errors import NotFound from docker.errors import NotFound
logger = logging.getLogger(__name__) logger = get_custom_logger(__name__)
class DockerManager: class DockerManager:
def __init__(self, image): def __init__(self, image):
self._image = image self._image = image
self._client = docker.from_env() self._client = docker.from_env()
logger.debug("Docker client initialized with image %s", self._image) logger.debug(f"Docker client initialized with image {self._image}")
def create_network(self, network_name=NETWORK_NAME): def create_network(self, network_name=NETWORK_NAME):
logger.debug("Attempting to create or retrieve network %s", network_name) logger.debug(f"Attempting to create or retrieve network {network_name}")
networks = self._client.networks.list(names=[network_name]) networks = self._client.networks.list(names=[network_name])
if networks: if networks:
logger.debug("Network %s already exists", network_name) logger.debug(f"Network {network_name} already exists")
return networks[0] return networks[0]
network = self._client.networks.create( network = self._client.networks.create(
@ -28,7 +28,7 @@ class DockerManager:
driver="bridge", driver="bridge",
ipam=IPAMConfig(driver="default", pool_configs=[IPAMPool(subnet=SUBNET, iprange=IP_RANGE, gateway=GATEWAY)]), ipam=IPAMConfig(driver="default", pool_configs=[IPAMPool(subnet=SUBNET, iprange=IP_RANGE, gateway=GATEWAY)]),
) )
logger.debug("Network %s created", network_name) logger.debug(f"Network {network_name} created")
return network return network
def start_container(self, image_name, ports, args, log_path, container_ip): def start_container(self, image_name, ports, args, log_path, container_ip):
@ -39,14 +39,14 @@ class DockerManager:
else: else:
cli_args.append(f"--{key}={value}") # Add a single command cli_args.append(f"--{key}={value}") # Add a single command
port_bindings = {f"{port}/tcp": ("", port) for port in ports} port_bindings = {f"{port}/tcp": ("", port) for port in ports}
logger.debug("Starting container with image %s", image_name) logger.debug(f"Starting container with image {image_name}")
logger.debug("Using args %s", cli_args) logger.debug(f"Using args {cli_args}")
container = self._client.containers.run(image_name, command=cli_args, ports=port_bindings, detach=True, remove=True, auto_remove=True) container = self._client.containers.run(image_name, command=cli_args, ports=port_bindings, detach=True, remove=True, auto_remove=True)
network = self._client.networks.get(NETWORK_NAME) network = self._client.networks.get(NETWORK_NAME)
network.connect(container, ipv4_address=container_ip) network.connect(container, ipv4_address=container_ip)
logger.debug("Container started with ID %s. Setting up logs at %s", container.short_id, log_path) logger.debug(f"Container started with ID {container.short_id}. Setting up logs at {log_path}")
log_thread = threading.Thread(target=self._log_container_output, args=(container, log_path)) log_thread = threading.Thread(target=self._log_container_output, args=(container, log_path))
log_thread.daemon = True log_thread.daemon = True
log_thread.start() log_thread.start()
@ -63,14 +63,14 @@ class DockerManager:
if base_port is None: if base_port is None:
base_port = random.randint(1024, 65535 - count) base_port = random.randint(1024, 65535 - count)
ports = [base_port + i for i in range(count)] ports = [base_port + i for i in range(count)]
logger.debug("Generated ports %s", ports) logger.debug(f"Generated ports {ports}")
return ports return ports
@staticmethod @staticmethod
def generate_random_ext_ip(): def generate_random_ext_ip():
base_ip_fragments = ["172", "18"] base_ip_fragments = ["172", "18"]
ext_ip = ".".join(base_ip_fragments + [str(random.randint(0, 255)) for _ in range(2)]) ext_ip = ".".join(base_ip_fragments + [str(random.randint(0, 255)) for _ in range(2)])
logger.debug("Generated random external IP %s", ext_ip) logger.debug(f"Generated random external IP {ext_ip}")
return ext_ip return ext_ip
def is_container_running(self, container): def is_container_running(self, container):
@ -78,7 +78,7 @@ class DockerManager:
refreshed_container = self._client.containers.get(container.id) refreshed_container = self._client.containers.get(container.id)
return refreshed_container.status == "running" return refreshed_container.status == "running"
except NotFound: except NotFound:
logger.error("Container with ID %s not found", container.id) logger.error(f"Container with ID {container.id} not found")
return False return False
@property @property

View File

@ -1,27 +1,34 @@
import os import os
import logging from src.libs.common import delay
from src.libs.custom_logger import get_custom_logger
from tenacity import retry, stop_after_delay, wait_fixed from tenacity import retry, stop_after_delay, wait_fixed
from src.node.api_clients.rpc import RPC from src.node.api_clients.rpc import RPC
from src.node.api_clients.rest import REST from src.node.api_clients.rest import REST
from src.node.docker_mananger import DockerManager from src.node.docker_mananger import DockerManager
from src.env_vars import LOG_DIR, DEFAULT_PUBSUBTOPIC, PROTOCOL from src.env_vars import DOCKER_LOG_DIR, DEFAULT_PUBSUB_TOPIC, PROTOCOL
from src.data_storage import DS from src.data_storage import DS
logger = logging.getLogger(__name__) logger = get_custom_logger(__name__)
class WakuNode: class WakuNode:
def __init__(self, docker_image, docker_log_prefix=""): def __init__(self, docker_image, docker_log_prefix=""):
self._image_name = docker_image self._image_name = docker_image
self._log_path = os.path.join(LOG_DIR, f"{docker_log_prefix}__{self._image_name.replace('/', '_')}.log") self._log_path = os.path.join(DOCKER_LOG_DIR, f"{docker_log_prefix}__{self._image_name.replace('/', '_')}.log")
self._docker_manager = DockerManager(self._image_name) self._docker_manager = DockerManager(self._image_name)
self._container = None self._container = None
logger.debug(f"WakuNode instance initialized with log path {self._log_path}")
@retry(stop=stop_after_delay(5), wait=wait_fixed(0.1), reraise=True)
def start(self, **kwargs):
logger.debug("Starting Node...")
self._docker_manager.create_network()
self._ext_ip = self._docker_manager.generate_random_ext_ip() self._ext_ip = self._docker_manager.generate_random_ext_ip()
self._ports = self._docker_manager.generate_ports() self._ports = self._docker_manager.generate_ports()
self._rest_port = self._ports[0] self._rest_port = self._ports[0]
self._rpc_port = self._ports[1] self._rpc_port = self._ports[1]
self._websocket_port = self._ports[2] self._websocket_port = self._ports[2]
logger.debug("WakuNode instance initialized with log path %s", self._log_path)
if PROTOCOL == "RPC": if PROTOCOL == "RPC":
self._api = RPC(self._rpc_port, self._image_name) self._api = RPC(self._rpc_port, self._image_name)
elif PROTOCOL == "REST": elif PROTOCOL == "REST":
@ -29,11 +36,6 @@ class WakuNode:
else: else:
raise ValueError(f"Unknown protocol: {PROTOCOL}") raise ValueError(f"Unknown protocol: {PROTOCOL}")
@retry(stop=stop_after_delay(5), wait=wait_fixed(0.1), reraise=True)
def start(self, **kwargs):
logger.debug("Starting Node...")
self._docker_manager.create_network()
default_args = { default_args = {
"listen-address": "0.0.0.0", "listen-address": "0.0.0.0",
"rpc": "true", "rpc": "true",
@ -42,6 +44,7 @@ class WakuNode:
"rest-admin": "true", "rest-admin": "true",
"websocket-support": "true", "websocket-support": "true",
"log-level": "TRACE", "log-level": "TRACE",
"rest-relay-cache-capacity": "100",
"websocket-port": str(self._ports[3]), "websocket-port": str(self._ports[3]),
"rpc-port": self._rpc_port, "rpc-port": self._rpc_port,
"rest-port": self._rest_port, "rest-port": self._rest_port,
@ -50,12 +53,12 @@ class WakuNode:
"rpc-address": "0.0.0.0", "rpc-address": "0.0.0.0",
"rest-address": "0.0.0.0", "rest-address": "0.0.0.0",
"nat": f"extip:{self._ext_ip}", "nat": f"extip:{self._ext_ip}",
"pubsub-topic": DEFAULT_PUBSUBTOPIC, "pubsub-topic": DEFAULT_PUBSUB_TOPIC,
} }
if "go-waku" in self._docker_manager.image: if "go-waku" in self._docker_manager.image:
go_waku_args = { go_waku_args = {
"min-relay-peers-to-publish": "0", "min-relay-peers-to-publish": "1",
"legacy-filter": "false", "legacy-filter": "false",
"log-level": "DEBUG", "log-level": "DEBUG",
} }
@ -67,35 +70,71 @@ class WakuNode:
self._container = self._docker_manager.start_container(self._docker_manager.image, self._ports, default_args, self._log_path, self._ext_ip) self._container = self._docker_manager.start_container(self._docker_manager.image, self._ports, default_args, self._log_path, self._ext_ip)
logger.debug( logger.debug(
"Started container from image %s. RPC: %s REST: %s WebSocket: %s", self._image_name, self._rpc_port, self._rest_port, self._websocket_port f"Started container from image {self._image_name}. RPC: {self._rpc_port} REST: {self._rest_port} WebSocket: {self._websocket_port}"
) )
DS.waku_nodes.append(self) DS.waku_nodes.append(self)
delay(1) # if we fire requests to soon after starting the node will sometimes fail to start correctly
try: try:
self.ensure_ready() self.ensure_ready()
except Exception as e: except Exception as ex:
logger.error("%s service did not become ready in time: %s", PROTOCOL, e) logger.error(f"{PROTOCOL} service did not become ready in time: {ex}")
raise raise
@retry(stop=stop_after_delay(5), wait=wait_fixed(0.1), reraise=True) @retry(stop=stop_after_delay(5), wait=wait_fixed(0.1), reraise=True)
def stop(self): def stop(self):
if self._container: if self._container:
logger.debug("Stopping container with id %s", self._container.short_id) logger.debug(f"Stopping container with id {self._container.short_id}")
self._container.stop() self._container.stop()
logger.debug("Container stopped.") logger.debug("Container stopped.")
@retry(stop=stop_after_delay(5), wait=wait_fixed(0.05), reraise=True) def restart(self):
if self._container:
logger.debug(f"Restarting container with id {self._container.short_id}")
self._container.restart()
def pause(self):
if self._container:
logger.debug(f"Pausing container with id {self._container.short_id}")
self._container.pause()
def unpause(self):
if self._container:
logger.debug(f"Unpause container with id {self._container.short_id}")
self._container.unpause()
@retry(stop=stop_after_delay(10), wait=wait_fixed(0.1), reraise=True)
def ensure_ready(self): def ensure_ready(self):
self.info() self.info()
logger.debug("RPC service is ready.") logger.info(f"{PROTOCOL} service is ready !!")
def info(self): def info(self):
return self._api.info() return self._api.info()
def set_subscriptions(self, pubsub_topics=[DEFAULT_PUBSUBTOPIC]): def set_subscriptions(self, pubsub_topics=None):
if not pubsub_topics:
pubsub_topics = [DEFAULT_PUBSUB_TOPIC]
return self._api.set_subscriptions(pubsub_topics) return self._api.set_subscriptions(pubsub_topics)
def send_message(self, message, pubsub_topic=DEFAULT_PUBSUBTOPIC): def send_message(self, message, pubsub_topic=DEFAULT_PUBSUB_TOPIC):
return self._api.send_message(message, pubsub_topic) return self._api.send_message(message, pubsub_topic)
def get_messages(self, pubsub_topic=DEFAULT_PUBSUBTOPIC): def get_messages(self, pubsub_topic=DEFAULT_PUBSUB_TOPIC):
return self._api.get_messages(pubsub_topic) return self._api.get_messages(pubsub_topic)
@property
def image(self):
return self._image_name
def type(self):
if self.is_nwaku():
return "nwaku"
elif self.is_gowaku():
return "gowaku"
else:
raise ValueError("Unknown node type!!!")
def is_nwaku(self):
return "nwaku" in self.image
def is_gowaku(self):
return "go-waku" in self.image

View File

@ -1,38 +1,81 @@
import logging from src.libs.custom_logger import get_custom_logger
from time import sleep, time import math
from time import time
import pytest import pytest
import allure import allure
from src.libs.common import to_base64, delay
from src.data_classes import message_rpc_response_schema from src.data_classes import message_rpc_response_schema
from src.env_vars import NODE_1, NODE_2 from src.env_vars import NODE_1, NODE_2, NODEKEY
from src.node.waku_node import WakuNode from src.node.waku_node import WakuNode
from tenacity import retry, stop_after_delay, wait_fixed from tenacity import retry, stop_after_delay, wait_fixed
logger = logging.getLogger(__name__) logger = get_custom_logger(__name__)
class StepsRelay: class StepsRelay:
@pytest.fixture(scope="function", autouse=True) @pytest.fixture(scope="function", autouse=True)
def setup_nodes(self, request): def setup_nodes(self, request):
self.node1 = WakuNode(NODE_1, request.cls.test_id) self.node1 = WakuNode(NODE_1, "node1_" + request.cls.test_id)
self.node1.start(relay="true", discv5_discovery="true", peer_exchange="true") self.node1.start(relay="true", discv5_discovery="true", peer_exchange="true", nodekey=NODEKEY)
enr_uri = self.node1.info()["enrUri"] enr_uri = self.node1.info()["enrUri"]
self.node2 = WakuNode(NODE_2, request.cls.test_id) self.node2 = WakuNode(NODE_2, "node2_" + request.cls.test_id)
self.node2.start(relay="true", discv5_discovery="true", discv5_bootstrap_node=enr_uri, peer_exchange="true") self.node2.start(relay="true", discv5_discovery="true", discv5_bootstrap_node=enr_uri, peer_exchange="true")
self.test_pubsub_topic = "test" self.test_pubsub_topic = "/waku/2/rs/18/1"
self.test_content_topic = "/test/1/waku-relay" self.test_content_topic = "/test/1/waku-relay/proto"
self.test_payload = "Relay works!!" self.test_payload = "Relay works!!"
self.node1.set_subscriptions([self.test_pubsub_topic]) self.node1.set_subscriptions([self.test_pubsub_topic])
self.node2.set_subscriptions([self.test_pubsub_topic]) self.node2.set_subscriptions([self.test_pubsub_topic])
@pytest.fixture(scope="function", autouse=True)
def network_warm_up(self, setup_nodes):
try:
self.wait_for_published_message_to_reach_peer(120)
logger.info("WARM UP successful !!")
except Exception as ex:
raise TimeoutError(f"WARM UP FAILED WITH: {ex}")
@allure.step @allure.step
@retry(stop=stop_after_delay(20), wait=wait_fixed(0.5), reraise=True) def check_published_message_reaches_peer(self, message, pubsub_topic=None, message_propagation_delay=0.1):
def check_published_message_reaches_peer(self, message): self.node1.send_message(message, pubsub_topic or self.test_pubsub_topic)
message.timestamp = int(time() * 1e9) delay(message_propagation_delay)
self.node1.send_message(message, self.test_pubsub_topic) get_messages_response = self.node2.get_messages(pubsub_topic or self.test_pubsub_topic)
sleep(0.1) assert get_messages_response, "Peer node couldn't find any messages"
get_messages_response = self.node2.get_messages(self.test_pubsub_topic)
logger.debug("Got reponse from remote peer %s", get_messages_response)
received_message = message_rpc_response_schema.load(get_messages_response[0]) received_message = message_rpc_response_schema.load(get_messages_response[0])
assert received_message.payload == message.payload self.assert_received_message(message, received_message)
assert received_message.contentTopic == message.contentTopic
assert received_message.timestamp == message.timestamp def assert_received_message(self, sent_message, received_message):
def assert_fail_message(field_name):
return f"Incorrect field: {field_name}. Published: {sent_message[field_name]} Received: {getattr(received_message, field_name)}"
assert received_message.payload == sent_message["payload"], assert_fail_message("payload")
assert received_message.contentTopic == sent_message["contentTopic"], assert_fail_message("contentTopic")
if sent_message.get("timestamp") is not None:
if isinstance(sent_message["timestamp"], float):
assert math.isclose(float(received_message.timestamp), sent_message["timestamp"], rel_tol=1e-9), assert_fail_message("timestamp")
else:
assert str(received_message.timestamp) == str(sent_message["timestamp"]), assert_fail_message("timestamp")
if "version" in sent_message:
assert str(received_message.version) == str(sent_message["version"]), assert_fail_message("version")
if "meta" in sent_message:
assert str(received_message.meta) == str(sent_message["meta"]), assert_fail_message("meta")
if "ephemeral" in sent_message:
assert str(received_message.ephemeral) == str(sent_message["ephemeral"]), assert_fail_message("ephemeral")
if "rateLimitProof" in sent_message:
assert str(received_message.rateLimitProof) == str(sent_message["rateLimitProof"]), assert_fail_message("rateLimitProof")
def wait_for_published_message_to_reach_peer(self, timeout_duration, time_between_retries=1):
@retry(stop=stop_after_delay(timeout_duration), wait=wait_fixed(time_between_retries), reraise=True)
def check_peer_connection():
message = {"payload": to_base64(self.test_payload), "contentTopic": self.test_content_topic, "timestamp": int(time() * 1e9)}
self.check_published_message_reaches_peer(message)
check_peer_connection()
def ensure_subscriptions_on_nodes(self, node_list, pubsub_topic_list):
for node in node_list:
node.set_subscriptions(pubsub_topic_list)
def create_message(self, **kwargs):
message = {"payload": to_base64(self.test_payload), "contentTopic": self.test_content_topic, "timestamp": int(time() * 1e9)}
message.update(kwargs)
return message

View File

@ -1,42 +1,100 @@
from time import time
from datetime import datetime, timedelta
from src.env_vars import DEFAULT_PUBSUB_TOPIC
NOW = datetime.now()
SAMPLE_INPUTS = [ SAMPLE_INPUTS = [
{"description": "A simple string.", "value": "Hello World!"}, {"description": "A simple string", "value": "Hello World!"},
{"description": "An integer.", "value": "1234567890"}, {"description": "An integer", "value": "1234567890"},
{"description": "A dictionary.", "value": '{"key": "value"}'}, {"description": "A dictionary", "value": '{"key": "value"}'},
{"description": "Chinese characters.", "value": "这是一些中文"}, {"description": "Chinese characters", "value": "这是一些中文"},
{"description": "Emojis.", "value": "🚀🌟✨"}, {"description": "Emojis", "value": "🚀🌟✨"},
{"description": "Lorem ipsum text.", "value": "Lorem ipsum dolor sit amet"}, {"description": "Lorem ipsum text", "value": "Lorem ipsum dolor sit amet"},
{"description": "HTML content.", "value": "<html><body>Hello</body></html>"}, {"description": "HTML content", "value": "<html><body>Hello</body></html>"},
{"description": "Cyrillic characters.", "value": "\u041f\u0440\u0438\u0432\u0435\u0442"}, {"description": "Cyrillic characters", "value": "\u041f\u0440\u0438\u0432\u0435\u0442"},
{"description": "Base64 encoded string.", "value": "Base64==dGVzdA=="}, {"description": "Base64 encoded string", "value": "Base64==dGVzdA=="},
{"description": "Binary data.", "value": "d29ya2luZyB3aXRoIGJpbmFyeSBkYXRh: \x50\x51"}, {"description": "Binary data", "value": "d29ya2luZyB3aXRoIGJpbmFyeSBkYXRh: \x50\x51"},
{"description": "Special characters with whitespace.", "value": "\t\nSpecial\tCharacters\n"}, {"description": "Special characters with whitespace", "value": "\t\nSpecial\tCharacters\n"},
{"description": "Boolean false as a string.", "value": "False"}, {"description": "Boolean false as a string", "value": "False"},
{"description": "A float number.", "value": "3.1415926535"}, {"description": "A float number", "value": "3.1415926535"},
{"description": "A list.", "value": "[1, 2, 3, 4, 5]"}, {"description": "A list", "value": "[1, 2, 3, 4, 5]"},
{"description": "Hexadecimal number as a string.", "value": "0xDEADBEEF"}, {"description": "Hexadecimal number as a string", "value": "0xDEADBEEF"},
{"description": "Email format.", "value": "user@example.com"}, {"description": "Email format", "value": "user@example.com"},
{"description": "URL format.", "value": "http://example.com"}, {"description": "URL format", "value": "http://example.com"},
{"description": "Date and time in ISO format.", "value": "2023-11-01T12:00:00Z"}, {"description": "Date and time in ISO format", "value": "2023-11-01T12:00:00Z"},
{"description": "String with escaped quotes.", "value": '"Escaped" \\"quotes\\"'}, {"description": "String with escaped quotes", "value": '"Escaped" \\"quotes\\"'},
{"description": "A regular expression.", "value": "Regular expression: ^[a-z0-9_-]{3,16}$"}, {"description": "A regular expression", "value": "Regular expression: ^[a-z0-9_-]{3,16}$"},
{"description": "A very long string.", "value": "x" * 1000}, {"description": "A very long string", "value": "x" * 1000},
{"description": "A JSON string.", "value": '{"name": "John", "age": 30, "city": "New York"}'}, {"description": "A JSON string", "value": '{"name": "John", "age": 30, "city": "New York"}'},
{"description": "A Unix path.", "value": "/usr/local/bin"}, {"description": "A Unix path", "value": "/usr/local/bin"},
{"description": "A Windows path.", "value": "C:\\Windows\\System32"}, {"description": "A Windows path", "value": "C:\\Windows\\System32"},
{"description": "An SQL query.", "value": "SELECT * FROM users WHERE id = 1;"}, {"description": "An SQL query", "value": "SELECT * FROM users WHERE id = 1;"},
{"description": "JavaScript code snippet.", "value": "function test() { console.log('Hello World'); }"}, {"description": "JavaScript code snippet", "value": "function test() { console.log('Hello World'); }"},
{"description": "A CSS snippet.", "value": "body { background-color: #fff; }"}, {"description": "A CSS snippet", "value": "body { background-color: #fff; }"},
{"description": "A Python one-liner.", "value": "print('Hello World')"}, {"description": "A Python one-liner", "value": "print('Hello World')"},
{"description": "An IP address.", "value": "192.168.1.1"}, {"description": "An IP address", "value": "192.168.1.1"},
{"description": "A domain name.", "value": "www.example.com"}, {"description": "A domain name", "value": "www.example.com"},
{"description": "A user agent string.", "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}, {"description": "A user agent string", "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"},
{"description": "A credit card number.", "value": "1234-5678-9012-3456"}, {"description": "A credit card number", "value": "1234-5678-9012-3456"},
{"description": "A phone number.", "value": "+1234567890"}, {"description": "A phone number", "value": "+1234567890"},
{"description": "A UUID.", "value": "123e4567-e89b-12d3-a456-426614174000"}, {"description": "A UUID", "value": "123e4567-e89b-12d3-a456-426614174000"},
{"description": "A hashtag.", "value": "#helloWorld"}, {"description": "A hashtag", "value": "#helloWorld"},
{"description": "A Twitter handle.", "value": "@username"}, {"description": "A Twitter handle", "value": "@username"},
{"description": "A password.", "value": "P@ssw0rd!"}, {"description": "A password", "value": "P@ssw0rd!"},
{"description": "A date in common format.", "value": "01/11/2023"}, {"description": "A date in common format", "value": "01/11/2023"},
{"description": "A time string.", "value": "12:00:00"}, {"description": "A time string", "value": "12:00:00"},
{"description": "A mathematical equation.", "value": "E = mc^2"}, {"description": "A mathematical equation", "value": "E = mc^2"},
]
INVALID_PAYLOADS = [
{"description": "Empty string", "value": ""},
{"description": "Unecoded text", "value": "Hello World!"},
{"description": "A dictionary", "value": {"key": "YWFh"}},
{"description": "An integer", "value": 1234567890},
{"description": "A list", "value": ["YWFh"]},
{"description": "A bool", "value": True},
]
INVALID_CONTENT_TOPICS = [
{"description": "Empty string", "value": ""},
{"description": "A dictionary", "value": {"key": "YWFh"}},
{"description": "An integer", "value": 1234567890},
{"description": "A list", "value": ["YWFh"]},
{"description": "A bool", "value": True},
]
VALID_PUBSUB_TOPICS = [
DEFAULT_PUBSUB_TOPIC,
"/waku/2/rs/18/1",
"/test/2/rs/18/1",
"/waku/3/rs/18/1",
"/waku/2/test/18/1",
"/waku/2/rs/66/1",
"/waku/2/rs/18/50",
"/waku/18/50",
"test",
]
SAMPLE_TIMESTAMPS = [
{"description": "Now", "value": int(time() * 1e9), "valid_for": ["nwaku", "gowaku"]},
{
"description": "Far future",
"value": int((NOW + timedelta(days=365 * 10)).timestamp() * 1e9),
"valid_for": ["nwaku", "gowaku"],
}, # 10 years from now
{"description": "Recent past", "value": int((NOW - timedelta(hours=1)).timestamp() * 1e9), "valid_for": ["nwaku", "gowaku"]}, # 1 hour ago
{"description": "Near future", "value": int((NOW + timedelta(hours=1)).timestamp() * 1e9), "valid_for": ["nwaku", "gowaku"]}, # 1 hour ahead
{"description": "Positive number", "value": 1, "valid_for": ["nwaku", "gowaku"]},
{"description": "Negative number", "value": -1, "valid_for": ["nwaku", "gowaku"]},
{"description": "DST change", "value": int(datetime(2020, 3, 8, 2, 0, 0).timestamp() * 1e9), "valid_for": ["nwaku", "gowaku"]}, # DST starts
{"description": "Timestamp as string number", "value": str(int(time() * 1e9)), "valid_for": []},
{"description": "Invalid large number", "value": 2**63, "valid_for": []},
{"description": "Float number", "value": float(time() * 1e9), "valid_for": []},
{"description": "Array instead of timestamp", "value": [int(time() * 1e9)], "valid_for": []},
{"description": "Object instead of timestamp", "value": {"time": int(time() * 1e9)}, "valid_for": []},
{"description": "ISO 8601 timestamp", "value": "2023-12-26T10:58:51", "valid_for": []},
{"description": "Missing", "value": None, "valid_for": ["gowaku"]},
] ]

View File

@ -1,4 +0,0 @@
import logging
logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("docker").setLevel(logging.WARNING)

View File

@ -1,15 +1,16 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import glob import glob
import logging from src.libs.custom_logger import get_custom_logger
import os import os
import pytest import pytest
from datetime import datetime from datetime import datetime
from time import time
from uuid import uuid4 from uuid import uuid4
from src.libs.common import attach_allure_file from src.libs.common import attach_allure_file
import src.env_vars as env_vars import src.env_vars as env_vars
from src.data_storage import DS from src.data_storage import DS
logger = logging.getLogger(__name__) logger = get_custom_logger(__name__)
# See https://docs.pytest.org/en/latest/example/simple.html#making-test-result-information-available-in-fixtures # See https://docs.pytest.org/en/latest/example/simple.html#making-test-result-information-available-in-fixtures
@ -42,21 +43,36 @@ def test_id(request):
@pytest.fixture(scope="function", autouse=True) @pytest.fixture(scope="function", autouse=True)
def test_setup(request, test_id): def test_setup(request, test_id):
logger.debug("Running test: %s with id: %s", request.node.name, request.cls.test_id) logger.debug(f"Running test: {request.node.name} with id: {request.cls.test_id}")
yield
for file in glob.glob(os.path.join(env_vars.DOCKER_LOG_DIR, "*")):
if os.path.getmtime(file) < time() - 3600:
logger.debug(f"Deleting old log file: {file}")
try:
os.remove(file)
except:
logger.error("Could not delete file")
@pytest.fixture(scope="function", autouse=True) @pytest.fixture(scope="function", autouse=True)
def attach_logs_on_fail(request): def attach_logs_on_fail(request):
yield yield
if request.node.rep_call.failed: if env_vars.RUNNING_IN_CI and hasattr(request.node, "rep_call") and request.node.rep_call.failed:
logger.debug("Test failed, attempting to attach logs to the allure reports") logger.debug("Test failed, attempting to attach logs to the allure reports")
for file in glob.glob(os.path.join(env_vars.LOG_DIR, request.cls.test_id + "*")): for file in glob.glob(os.path.join(env_vars.DOCKER_LOG_DIR, "*" + request.cls.test_id + "*")):
attach_allure_file(file) attach_allure_file(file)
@pytest.fixture(scope="function", autouse=True) @pytest.fixture(scope="function", autouse=True)
def close_open_nodes(): def close_open_nodes(attach_logs_on_fail):
DS.waku_nodes = [] DS.waku_nodes = []
yield yield
crashed_containers = []
for node in DS.waku_nodes: for node in DS.waku_nodes:
node.stop() try:
node.stop()
except Exception as ex:
if "No such container" in str(ex):
crashed_containers.append(node.image)
logger.error(f"Failed to stop container because of error {ex}")
assert not crashed_containers, f"Containers {crashed_containers} crashed during the test!!!"

View File

@ -1,34 +1,241 @@
import logging from src.libs.custom_logger import get_custom_logger
from time import time
from src.libs.common import to_base64 from src.libs.common import delay, to_base64
from src.data_classes import MessageRpcQuery
from src.steps.relay import StepsRelay from src.steps.relay import StepsRelay
from src.test_data import SAMPLE_INPUTS from src.test_data import INVALID_CONTENT_TOPICS, INVALID_PAYLOADS, SAMPLE_INPUTS, SAMPLE_TIMESTAMPS, VALID_PUBSUB_TOPICS
from src.data_classes import message_rpc_response_schema
logger = logging.getLogger(__name__) logger = get_custom_logger(__name__)
class TestRelayPublish(StepsRelay): class TestRelayPublish(StepsRelay):
def test_publish_with_various_payloads(self): def test_publish_with_valid_payloads(self):
failed_payloads = [] failed_payloads = []
for payload in SAMPLE_INPUTS: for payload in SAMPLE_INPUTS:
logger.debug("Running test with payload %s", payload["description"]) logger.debug(f'Running test with payload {payload["description"]}')
message = MessageRpcQuery(payload=to_base64(payload["value"]), contentTopic=self.test_content_topic) message = self.create_message(payload=to_base64(payload["value"]))
try: try:
self.check_published_message_reaches_peer(message) self.check_published_message_reaches_peer(message)
except Exception as e: except Exception as e:
logger.error("Payload %s failed: %s", {payload["description"]}, {str(e)}) logger.error(f'Payload {payload["description"]} failed: {str(e)}')
failed_payloads.append(payload) failed_payloads.append(payload["description"])
assert not failed_payloads, f"Payloads failed: {failed_payloads}" assert not failed_payloads, f"Payloads failed: {failed_payloads}"
def test_publish_with_various_content_topics(self): def test_publish_with_invalid_payloads(self):
success_payloads = []
for payload in INVALID_PAYLOADS:
logger.debug(f'Running test with payload {payload["description"]}')
message = self.create_message(payload=payload["value"])
try:
self.node1.send_message(message, self.test_pubsub_topic)
success_payloads.append(payload)
except Exception as ex:
assert "Bad Request" in str(ex) or "Internal Server Error" in str(ex)
assert not success_payloads, f"Invalid Payloads that didn't failed: {success_payloads}"
def test_publish_with_missing_payload(self):
message = {"contentTopic": self.test_content_topic, "timestamp": int(time() * 1e9)}
try:
self.node1.send_message(message, self.test_pubsub_topic)
raise AssertionError("Publish with missing payload worked!!!")
except Exception as ex:
assert "Bad Request" in str(ex) or "Internal Server Error" in str(ex)
def test_publish_with_payload_less_than_one_mb(self):
payload_length = 1024 * 1023
logger.debug(f"Running test with payload length of {payload_length} bytes")
message = self.create_message(payload=to_base64("a" * (payload_length)))
self.check_published_message_reaches_peer(message, message_propagation_delay=2)
def test_publish_with_payload_equal_or_more_than_one_mb(self):
for payload_length in [1024 * 1024, 1024 * 1024 * 10]:
logger.debug(f"Running test with payload length of {payload_length} bytes")
message = self.create_message(payload=to_base64("a" * (payload_length)))
try:
self.check_published_message_reaches_peer(message, message_propagation_delay=2)
raise AssertionError("Duplicate message was retrieved twice")
except Exception as ex:
assert "Peer node couldn't find any messages" in str(ex)
def test_publish_with_valid_content_topics(self):
failed_content_topics = [] failed_content_topics = []
for content_topic in SAMPLE_INPUTS: for content_topic in SAMPLE_INPUTS:
logger.debug("Running test with content topic %s", content_topic["description"]) logger.debug(f'Running test with content topic {content_topic["description"]}')
message = MessageRpcQuery(payload=to_base64(self.test_payload), contentTopic=content_topic["value"]) message = self.create_message(contentTopic=content_topic["value"])
try: try:
self.check_published_message_reaches_peer(message) self.check_published_message_reaches_peer(message)
except Exception as e: except Exception as e:
logger.error("ContentTopic %s failed: %s", {content_topic["description"]}, {str(e)}) logger.error(f'ContentTopic {content_topic["description"]} failed: {str(e)}')
failed_content_topics.append(content_topic) failed_content_topics.append(content_topic)
assert not failed_content_topics, f"ContentTopics failed: {failed_content_topics}" assert not failed_content_topics, f"ContentTopics failed: {failed_content_topics}"
def test_publish_with_invalid_content_topics(self):
success_content_topics = []
for content_topic in INVALID_CONTENT_TOPICS:
logger.debug(f'Running test with contetn topic {content_topic["description"]}')
message = self.create_message(contentTopic=content_topic["value"])
try:
self.node1.send_message(message, self.test_pubsub_topic)
success_content_topics.append(content_topic)
except Exception as ex:
assert "Bad Request" in str(ex) or "Internal Server Error" in str(ex)
assert not success_content_topics, f"Invalid Content topics that didn't failed: {success_content_topics}"
def test_publish_with_missing_content_topic(self):
message = {"payload": to_base64(self.test_payload), "timestamp": int(time() * 1e9)}
try:
self.node1.send_message(message, self.test_pubsub_topic)
raise AssertionError("Publish with missing content_topic worked!!!")
except Exception as ex:
assert "Bad Request" in str(ex) or "Internal Server Error" in str(ex)
def test_publish_on_multiple_pubsub_topics(self):
self.ensure_subscriptions_on_nodes([self.node1, self.node2], VALID_PUBSUB_TOPICS)
failed_pubsub_topics = []
for pubsub_topic in VALID_PUBSUB_TOPICS:
logger.debug(f"Running test with pubsub topic {pubsub_topic}")
try:
self.check_published_message_reaches_peer(self.create_message(), pubsub_topic=pubsub_topic)
except Exception as e:
logger.error(f"PubusubTopic {pubsub_topic} failed: {str(e)}")
failed_pubsub_topics.append(pubsub_topic)
assert not failed_pubsub_topics, f"PubusubTopic failed: {failed_pubsub_topics}"
def test_message_published_on_different_pubsub_topic_is_not_retrieved(self):
self.ensure_subscriptions_on_nodes([self.node1, self.node2], VALID_PUBSUB_TOPICS)
self.node1.send_message(self.create_message(), VALID_PUBSUB_TOPICS[0])
delay(0.1)
messages = self.node2.get_messages(VALID_PUBSUB_TOPICS[1])
assert not messages, "Message was retrieved on wrong pubsub_topic"
def test_publish_on_unsubscribed_pubsub_topic(self):
try:
self.check_published_message_reaches_peer(self.create_message(), pubsub_topic="/waku/2/rs/19/1")
raise AssertionError("Publish on unsubscribed pubsub_topic worked!!!")
except Exception as ex:
assert "Bad Request" in str(ex) or "Internal Server Error" in str(ex)
def test_publish_with_valid_timestamps(self):
failed_timestamps = []
for timestamp in SAMPLE_TIMESTAMPS:
if self.node1.type() in timestamp["valid_for"]:
logger.debug(f'Running test with timestamp {timestamp["description"]}')
message = self.create_message(timestamp=timestamp["value"])
try:
self.check_published_message_reaches_peer(message)
except Exception as ex:
logger.error(f'Timestamp {timestamp["description"]} failed: {str(ex)}')
failed_timestamps.append(timestamp)
assert not failed_timestamps, f"Timestamps failed: {failed_timestamps}"
def test_publish_with_invalid_timestamps(self):
success_timestamps = []
for timestamp in SAMPLE_TIMESTAMPS:
if self.node1.type() not in timestamp["valid_for"]:
logger.debug(f'Running test with timestamp {timestamp["description"]}')
message = self.create_message(timestamp=timestamp["value"])
try:
self.check_published_message_reaches_peer(message)
success_timestamps.append(timestamp)
except Exception as e:
pass
assert not success_timestamps, f"Invalid Timestamps that didn't failed: {success_timestamps}"
def test_publish_with_no_timestamp(self):
message = {"payload": to_base64(self.test_payload), "contentTopic": self.test_content_topic}
self.check_published_message_reaches_peer(message)
def test_publish_with_valid_version(self):
self.check_published_message_reaches_peer(self.create_message(version=10))
def test_publish_with_invalid_version(self):
try:
self.check_published_message_reaches_peer(self.create_message(version=2.1))
raise AssertionError("Publish with invalid version worked!!!")
except Exception as ex:
assert "Bad Request" in str(ex)
def test_publish_with_valid_meta(self):
self.check_published_message_reaches_peer(self.create_message(meta=to_base64(self.test_payload)))
def test_publish_with_invalid_meta(self):
try:
self.check_published_message_reaches_peer(self.create_message(meta=self.test_payload))
raise AssertionError("Publish with invalid meta worked!!!")
except Exception as ex:
assert "Bad Request" in str(ex)
def test_publish_with_ephemeral(self):
failed_ephemeral = []
for ephemeral in [True, False]:
logger.debug(f"Running test with Ephemeral {ephemeral}")
try:
self.check_published_message_reaches_peer(self.create_message(ephemeral=ephemeral))
except Exception as e:
logger.error(f"Massage with Ephemeral {ephemeral} failed: {str(e)}")
failed_ephemeral.append(ephemeral)
assert not failed_ephemeral, f"Ephemeral that failed: {failed_ephemeral}"
def test_publish_with_rate_limit_proof(self):
rate_limit_proof = {
"proof": to_base64("proofData"),
"epoch": to_base64("epochData"),
"nullifier": to_base64("nullifierData"),
}
self.check_published_message_reaches_peer(self.create_message(rateLimitProof=rate_limit_proof))
def test_publish_with_extra_field(self):
self.check_published_message_reaches_peer(self.create_message(extraField="extraValue"))
def test_publish_and_retrieve_duplicate_message(self):
message = self.create_message()
self.check_published_message_reaches_peer(message)
try:
self.check_published_message_reaches_peer(message)
raise AssertionError("Duplicate message was retrieved twice")
except Exception as ex:
assert "Peer node couldn't find any messages" in str(ex)
def test_publish_while_peer_is_paused(self):
message = self.create_message()
self.node2.pause()
self.node1.send_message(message, self.test_pubsub_topic)
self.node2.unpause()
get_messages_response = self.node2.get_messages(self.test_pubsub_topic)
assert get_messages_response, "Peer node couldn't find any messages"
received_message = message_rpc_response_schema.load(get_messages_response[0])
self.assert_received_message(message, received_message)
def test_publish_after_node_pauses_and_pauses(self):
self.check_published_message_reaches_peer(self.create_message())
self.node1.pause()
self.node1.unpause()
self.check_published_message_reaches_peer(self.create_message(payload=to_base64("M1")))
self.node2.pause()
self.node2.unpause()
self.check_published_message_reaches_peer(self.create_message(payload=to_base64("M2")))
def test_publish_after_node1_restarts(self):
self.check_published_message_reaches_peer(self.create_message())
self.node1.restart()
self.ensure_subscriptions_on_nodes([self.node1, self.node2], [self.test_pubsub_topic])
self.wait_for_published_message_to_reach_peer(20)
def test_publish_after_node2_restarts(self):
self.check_published_message_reaches_peer(self.create_message())
self.node2.restart()
self.ensure_subscriptions_on_nodes([self.node1, self.node2], [self.test_pubsub_topic])
self.wait_for_published_message_to_reach_peer(20)
def test_publish_and_retrieve_100_messages(self):
num_messages = 100 # if increase this number make sure to also increase rest-relay-cache-capacity flag
for index in range(num_messages):
message = self.create_message(payload=to_base64(f"M_{index}"))
self.node1.send_message(message, self.test_pubsub_topic)
delay(1)
messages = self.node2.get_messages(self.test_pubsub_topic)
assert len(messages) == num_messages
for index, message in enumerate(messages):
assert message["payload"] == to_base64(
f"M_{index}"
), f'Incorrect payload at index: {index}. Published {to_base64(f"M_{index}")} Received {message["payload"]}'