test_: run functional tests on host (no container) (#6159)
* test_: run on host
This commit is contained in:
parent
ef177c1c63
commit
1795620df0
|
@ -30,6 +30,7 @@ LABEL source="https://github.com/status-im/status-go"
|
||||||
LABEL description="status-go is an underlying part of Status - a browser, messenger, and gateway to a decentralized world."
|
LABEL description="status-go is an underlying part of Status - a browser, messenger, and gateway to a decentralized world."
|
||||||
|
|
||||||
RUN apk add --no-cache ca-certificates bash libgcc libstdc++ curl
|
RUN apk add --no-cache ca-certificates bash libgcc libstdc++ curl
|
||||||
|
RUN mkdir -p /usr/status-user && chmod -R 777 /usr/status-user
|
||||||
RUN mkdir -p /static/keys
|
RUN mkdir -p /static/keys
|
||||||
RUN mkdir -p /static/configs
|
RUN mkdir -p /static/configs
|
||||||
|
|
||||||
|
|
|
@ -26,15 +26,35 @@ mkdir -p "${test_results_path}"
|
||||||
all_compose_files="-f ${root_path}/docker-compose.anvil.yml -f ${root_path}/docker-compose.test.status-go.yml"
|
all_compose_files="-f ${root_path}/docker-compose.anvil.yml -f ${root_path}/docker-compose.test.status-go.yml"
|
||||||
project_name="status-go-func-tests-$(date +%s)"
|
project_name="status-go-func-tests-$(date +%s)"
|
||||||
|
|
||||||
export STATUS_BACKEND_COUNT=10
|
|
||||||
export STATUS_BACKEND_URLS=$(eval echo http://${project_name}-status-backend-{1..${STATUS_BACKEND_COUNT}}:3333 | tr ' ' ,)
|
export STATUS_BACKEND_URLS=$(eval echo http://${project_name}-status-backend-{1..${STATUS_BACKEND_COUNT}}:3333 | tr ' ' ,)
|
||||||
|
|
||||||
# Run functional tests
|
# Remove orphans
|
||||||
echo -e "${GRN}Running tests${RST}, HEAD: $(git rev-parse HEAD)"
|
docker ps -a --filter "name=status-go-func-tests-*-status-backend-*" --filter "status=exited" -q | xargs -r docker rm
|
||||||
docker compose -p ${project_name} ${all_compose_files} up -d --build --scale status-backend=${STATUS_BACKEND_COUNT} --remove-orphans
|
|
||||||
|
|
||||||
echo -e "${GRN}Running tests-rpc${RST}" # Follow the logs, wait for them to finish
|
# Run docker
|
||||||
docker compose -p ${project_name} ${all_compose_files} logs -f tests-rpc > "${root_path}/tests-rpc.log"
|
echo -e "${GRN}Running tests${RST}, HEAD: $(git rev-parse HEAD)"
|
||||||
|
docker compose -p ${project_name} ${all_compose_files} up -d --build --remove-orphans
|
||||||
|
|
||||||
|
# Set up virtual environment
|
||||||
|
venv_path="${root_path}/.venv"
|
||||||
|
|
||||||
|
if [[ -d "${venv_path}" ]]; then
|
||||||
|
echo -e "${GRN}Using existing virtual environment${RST}"
|
||||||
|
else
|
||||||
|
echo -e "${GRN}Creating new virtual environment${RST}"
|
||||||
|
python3 -m venv "${venv_path}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
source "${venv_path}/bin/activate"
|
||||||
|
|
||||||
|
# Upgrade pip and install requirements
|
||||||
|
echo -e "${GRN}Installing dependencies${RST}"
|
||||||
|
pip install --upgrade pip
|
||||||
|
pip install -r "${root_path}/requirements.txt"
|
||||||
|
|
||||||
|
# Run functional tests
|
||||||
|
pytest -m rpc --docker_project_name=${project_name} --codecov_dir=${binary_coverage_reports_path} --junitxml=${test_results_path}/report.xml
|
||||||
|
exit_code=$?
|
||||||
|
|
||||||
# Stop containers
|
# Stop containers
|
||||||
echo -e "${GRN}Stopping docker containers${RST}"
|
echo -e "${GRN}Stopping docker containers${RST}"
|
||||||
|
@ -45,9 +65,6 @@ echo -e "${GRN}Saving logs${RST}"
|
||||||
docker compose -p ${project_name} ${all_compose_files} logs status-go > "${root_path}/statusd.log"
|
docker compose -p ${project_name} ${all_compose_files} logs status-go > "${root_path}/statusd.log"
|
||||||
docker compose -p ${project_name} ${all_compose_files} logs status-backend > "${root_path}/status-backend.log"
|
docker compose -p ${project_name} ${all_compose_files} logs status-backend > "${root_path}/status-backend.log"
|
||||||
|
|
||||||
# Retrieve exit code
|
|
||||||
exit_code=$(docker inspect ${project_name}-tests-rpc-1 -f '{{.State.ExitCode}}');
|
|
||||||
|
|
||||||
# Cleanup containers
|
# Cleanup containers
|
||||||
echo -e "${GRN}Removing docker containers${RST}"
|
echo -e "${GRN}Removing docker containers${RST}"
|
||||||
docker compose -p ${project_name} ${all_compose_files} down
|
docker compose -p ${project_name} ${all_compose_files} down
|
||||||
|
|
|
@ -25,12 +25,13 @@ Functional tests for status-go
|
||||||
* Status-im contracts will be deployed to the network
|
* Status-im contracts will be deployed to the network
|
||||||
|
|
||||||
### Run tests
|
### Run tests
|
||||||
- In `./tests-functional` run `docker compose -f docker-compose.anvil.yml -f docker-compose.test.status-go.yml -f docker-compose.status-go.local.yml up --build --scale status-backend=10 --remove-orphans`, as result:
|
- In `./tests-functional` run `docker compose -f docker-compose.anvil.yml -f docker-compose.test.status-go.yml -f docker-compose.status-go.local.yml up --build --remove-orphans`, as result:
|
||||||
* a container with [status-go as daemon](https://github.com/status-im/status-go/issues/5175) will be created with APIModules exposed on `0.0.0.0:3333`
|
* a container with [status-backend](https://github.com/status-im/status-go/pull/5847) will be created with endpoint exposed on `0.0.0.0:3333`
|
||||||
* status-go will use [anvil](https://book.getfoundry.sh/reference/anvil/) as RPCURL with ChainID 31337
|
* status-go will use [anvil](https://book.getfoundry.sh/reference/anvil/) as RPCURL with ChainID 31337
|
||||||
* all Status-im contracts will be deployed to the network
|
* Status-im contracts will be deployed to the network
|
||||||
|
|
||||||
* In `./tests-functional/tests` directory run `pytest -m wallet`
|
* In `./tests-functional/tests` directory run `pytest -m rpc`
|
||||||
|
* To run tests against binary run `pytest -m <your mark> --url=http:<binary_url>:<binary_port> --user_dir=/<path>`
|
||||||
|
|
||||||
## Implementation details
|
## Implementation details
|
||||||
|
|
||||||
|
|
|
@ -4,23 +4,31 @@ import time
|
||||||
import random
|
import random
|
||||||
import threading
|
import threading
|
||||||
import requests
|
import requests
|
||||||
from tenacity import retry, stop_after_delay, wait_fixed
|
import docker
|
||||||
|
import os
|
||||||
|
|
||||||
|
from tenacity import retry, stop_after_delay, wait_fixed
|
||||||
from clients.signals import SignalClient
|
from clients.signals import SignalClient
|
||||||
from clients.rpc import RpcClient
|
from clients.rpc import RpcClient
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from conftest import option
|
from conftest import option
|
||||||
from constants import user_1, DEFAULT_DISPLAY_NAME
|
from constants import user_1, DEFAULT_DISPLAY_NAME, USER_DIR
|
||||||
|
|
||||||
|
|
||||||
class StatusBackend(RpcClient, SignalClient):
|
class StatusBackend(RpcClient, SignalClient):
|
||||||
|
|
||||||
def __init__(self, await_signals=[], url=None):
|
def __init__(self, await_signals=[]):
|
||||||
try:
|
|
||||||
url = url if url else random.choice(option.status_backend_urls)
|
if option.status_backend_url:
|
||||||
except IndexError:
|
url = option.status_backend_url
|
||||||
raise Exception("Not enough status-backend containers, please add more")
|
else:
|
||||||
option.status_backend_urls.remove(url)
|
self.docker_client = docker.from_env()
|
||||||
|
host_port = random.choice(option.status_backend_port_range)
|
||||||
|
|
||||||
|
self.container = self._start_container(host_port)
|
||||||
|
url = f"http://127.0.0.1:{host_port}"
|
||||||
|
option.status_backend_port_range.remove(host_port)
|
||||||
|
|
||||||
|
|
||||||
self.api_url = f"{url}/statusgo"
|
self.api_url = f"{url}/statusgo"
|
||||||
self.ws_url = f"{url}".replace("http", "ws")
|
self.ws_url = f"{url}".replace("http", "ws")
|
||||||
|
@ -29,14 +37,70 @@ class StatusBackend(RpcClient, SignalClient):
|
||||||
RpcClient.__init__(self, self.rpc_url)
|
RpcClient.__init__(self, self.rpc_url)
|
||||||
SignalClient.__init__(self, self.ws_url, await_signals)
|
SignalClient.__init__(self, self.ws_url, await_signals)
|
||||||
|
|
||||||
|
self._health_check()
|
||||||
|
|
||||||
websocket_thread = threading.Thread(target=self._connect)
|
websocket_thread = threading.Thread(target=self._connect)
|
||||||
websocket_thread.daemon = True
|
websocket_thread.daemon = True
|
||||||
websocket_thread.start()
|
websocket_thread.start()
|
||||||
|
|
||||||
|
def _start_container(self, host_port):
|
||||||
|
docker_project_name = option.docker_project_name
|
||||||
|
|
||||||
|
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
|
||||||
|
image_name = f"{docker_project_name}-status-backend:latest"
|
||||||
|
container_name = f"{docker_project_name}-status-backend-{timestamp}"
|
||||||
|
|
||||||
|
coverage_path = option.codecov_dir if option.codecov_dir else os.path.abspath("./coverage/binary")
|
||||||
|
|
||||||
|
container_args = {
|
||||||
|
"image": image_name,
|
||||||
|
"detach": True,
|
||||||
|
"name": container_name,
|
||||||
|
"labels": {"com.docker.compose.project": docker_project_name},
|
||||||
|
"entrypoint": [
|
||||||
|
"status-backend",
|
||||||
|
"--address", "0.0.0.0:3333",
|
||||||
|
],
|
||||||
|
"ports": {"3333/tcp": host_port},
|
||||||
|
"environment": {
|
||||||
|
"GOCOVERDIR": "/coverage/binary",
|
||||||
|
},
|
||||||
|
"volumes": {
|
||||||
|
coverage_path: {
|
||||||
|
"bind": "/coverage/binary",
|
||||||
|
"mode": "rw",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if "FUNCTIONAL_TESTS_DOCKER_UID" in os.environ:
|
||||||
|
container_args["user"] = os.environ["FUNCTIONAL_TESTS_DOCKER_UID"]
|
||||||
|
|
||||||
|
container = self.docker_client.containers.run(**container_args)
|
||||||
|
|
||||||
|
network = self.docker_client.networks.get(
|
||||||
|
f"{docker_project_name}_default")
|
||||||
|
network.connect(container)
|
||||||
|
|
||||||
|
option.status_backend_containers.append(container.id)
|
||||||
|
return container
|
||||||
|
|
||||||
|
def _health_check(self):
|
||||||
|
start_time = time.time()
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
self.api_valid_request(method="Fleets", data=[])
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
if time.time() - start_time > 20:
|
||||||
|
raise Exception(e)
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
def api_request(self, method, data, url=None):
|
def api_request(self, method, data, url=None):
|
||||||
url = url if url else self.api_url
|
url = url if url else self.api_url
|
||||||
url = f"{url}/{method}"
|
url = f"{url}/{method}"
|
||||||
logging.info(f"Sending POST request to url {url} with data: {json.dumps(data, sort_keys=True, indent=4)}")
|
logging.info(
|
||||||
|
f"Sending POST request to url {url} with data: {json.dumps(data, sort_keys=True, indent=4)}")
|
||||||
response = requests.post(url, json=data)
|
response = requests.post(url, json=data)
|
||||||
logging.info(f"Got response: {response.content}")
|
logging.info(f"Got response: {response.content}")
|
||||||
return response
|
return response
|
||||||
|
@ -46,7 +110,8 @@ class StatusBackend(RpcClient, SignalClient):
|
||||||
assert response.content
|
assert response.content
|
||||||
logging.info(f"Got response: {response.content}")
|
logging.info(f"Got response: {response.content}")
|
||||||
try:
|
try:
|
||||||
assert not response.json()["error"]
|
error = response.json()["error"]
|
||||||
|
assert not error, f"Error: {error}"
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
raise AssertionError(
|
raise AssertionError(
|
||||||
f"Invalid JSON in response: {response.content}")
|
f"Invalid JSON in response: {response.content}")
|
||||||
|
@ -58,7 +123,7 @@ class StatusBackend(RpcClient, SignalClient):
|
||||||
self.verify_is_valid_api_response(response)
|
self.verify_is_valid_api_response(response)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def init_status_backend(self, data_dir="/"):
|
def init_status_backend(self, data_dir=USER_DIR):
|
||||||
method = "InitializeApplication"
|
method = "InitializeApplication"
|
||||||
data = {
|
data = {
|
||||||
"dataDir": data_dir,
|
"dataDir": data_dir,
|
||||||
|
@ -68,7 +133,7 @@ class StatusBackend(RpcClient, SignalClient):
|
||||||
}
|
}
|
||||||
return self.api_valid_request(method, data)
|
return self.api_valid_request(method, data)
|
||||||
|
|
||||||
def create_account_and_login(self, data_dir="/", display_name=DEFAULT_DISPLAY_NAME, password=user_1.password):
|
def create_account_and_login(self, data_dir=USER_DIR, display_name=DEFAULT_DISPLAY_NAME, password=user_1.password):
|
||||||
method = "CreateAccountAndLogin"
|
method = "CreateAccountAndLogin"
|
||||||
data = {
|
data = {
|
||||||
"rootDataDir": data_dir,
|
"rootDataDir": data_dir,
|
||||||
|
@ -81,7 +146,7 @@ class StatusBackend(RpcClient, SignalClient):
|
||||||
}
|
}
|
||||||
return self.api_valid_request(method, data)
|
return self.api_valid_request(method, data)
|
||||||
|
|
||||||
def restore_account_and_login(self, data_dir="/",display_name=DEFAULT_DISPLAY_NAME, user=user_1,
|
def restore_account_and_login(self, data_dir=USER_DIR, display_name=DEFAULT_DISPLAY_NAME, user=user_1,
|
||||||
network_id=31337):
|
network_id=31337):
|
||||||
method = "RestoreAccountAndLogin"
|
method = "RestoreAccountAndLogin"
|
||||||
data = {
|
data = {
|
||||||
|
@ -136,7 +201,8 @@ class StatusBackend(RpcClient, SignalClient):
|
||||||
return
|
return
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
raise TimeoutError(f"RPC client was not started after {timeout} seconds")
|
raise TimeoutError(
|
||||||
|
f"RPC client was not started after {timeout} seconds")
|
||||||
|
|
||||||
@retry(stop=stop_after_delay(10), wait=wait_fixed(0.5), reraise=True)
|
@retry(stop=stop_after_delay(10), wait=wait_fixed(0.5), reraise=True)
|
||||||
def start_messenger(self, params=[]):
|
def start_messenger(self, params=[]):
|
||||||
|
@ -173,7 +239,8 @@ class StatusBackend(RpcClient, SignalClient):
|
||||||
for account in accounts:
|
for account in accounts:
|
||||||
if account.get("name") == display_name:
|
if account.get("name") == display_name:
|
||||||
return account.get("public-key")
|
return account.get("public-key")
|
||||||
raise ValueError(f"Public key not found for display name: {display_name}")
|
raise ValueError(
|
||||||
|
f"Public key not found for display name: {display_name}")
|
||||||
|
|
||||||
def send_contact_request(self, params=[]):
|
def send_contact_request(self, params=[]):
|
||||||
method = "wakuext_sendContactRequest"
|
method = "wakuext_sendContactRequest"
|
||||||
|
|
|
@ -1,32 +1,16 @@
|
||||||
import os
|
import os
|
||||||
import threading
|
import docker
|
||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
import pytest as pytest
|
import pytest as pytest
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
def pytest_addoption(parser):
|
def pytest_addoption(parser):
|
||||||
parser.addoption(
|
parser.addoption(
|
||||||
"--rpc_url_statusd",
|
"--status_backend_url",
|
||||||
action="store",
|
action="store",
|
||||||
help="",
|
help="",
|
||||||
default="http://0.0.0.0:3333",
|
default=None,
|
||||||
)
|
|
||||||
parser.addoption(
|
|
||||||
"--ws_url_statusd",
|
|
||||||
action="store",
|
|
||||||
help="",
|
|
||||||
default="ws://0.0.0.0:8354",
|
|
||||||
)
|
|
||||||
parser.addoption(
|
|
||||||
"--status_backend_urls",
|
|
||||||
action="store",
|
|
||||||
help="",
|
|
||||||
default=[
|
|
||||||
f"http://0.0.0.0:{3314 + i}" for i in range(
|
|
||||||
int(os.getenv("STATUS_BACKEND_COUNT", 10))
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
parser.addoption(
|
parser.addoption(
|
||||||
"--anvil_url",
|
"--anvil_url",
|
||||||
|
@ -40,6 +24,24 @@ def pytest_addoption(parser):
|
||||||
help="",
|
help="",
|
||||||
default="Strong12345",
|
default="Strong12345",
|
||||||
)
|
)
|
||||||
|
parser.addoption(
|
||||||
|
"--docker_project_name",
|
||||||
|
action="store",
|
||||||
|
help="",
|
||||||
|
default="tests-functional",
|
||||||
|
)
|
||||||
|
parser.addoption(
|
||||||
|
"--codecov_dir",
|
||||||
|
action="store",
|
||||||
|
help="",
|
||||||
|
default=None,
|
||||||
|
)
|
||||||
|
parser.addoption(
|
||||||
|
"--user_dir",
|
||||||
|
action="store",
|
||||||
|
help="",
|
||||||
|
default=None,
|
||||||
|
)
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Option:
|
class Option:
|
||||||
|
@ -52,6 +54,24 @@ option = Option()
|
||||||
def pytest_configure(config):
|
def pytest_configure(config):
|
||||||
global option
|
global option
|
||||||
option = config.option
|
option = config.option
|
||||||
if type(option.status_backend_urls) is str:
|
|
||||||
option.status_backend_urls = option.status_backend_urls.split(",")
|
executor_number = int(os.getenv('EXECUTOR_NUMBER', 5))
|
||||||
|
base_port = 7000
|
||||||
|
range_size = 100
|
||||||
|
|
||||||
|
start_port = base_port + (executor_number * range_size)
|
||||||
|
|
||||||
|
option.status_backend_port_range = list(range(start_port, start_port + range_size - 1))
|
||||||
|
option.status_backend_containers = []
|
||||||
|
|
||||||
option.base_dir = os.path.dirname(os.path.abspath(__file__))
|
option.base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
def pytest_unconfigure():
|
||||||
|
docker_client = docker.from_env()
|
||||||
|
for container_id in option.status_backend_containers:
|
||||||
|
try:
|
||||||
|
container = docker_client.containers.get(container_id)
|
||||||
|
container.stop(timeout=30)
|
||||||
|
container.remove()
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from conftest import option
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,4 +27,5 @@ DEFAULT_DISPLAY_NAME = "Mr_Meeseeks"
|
||||||
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "../"))
|
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "../"))
|
||||||
TESTS_DIR = os.path.join(PROJECT_ROOT, "tests-functional")
|
TESTS_DIR = os.path.join(PROJECT_ROOT, "tests-functional")
|
||||||
SIGNALS_DIR = os.path.join(TESTS_DIR, "signals")
|
SIGNALS_DIR = os.path.join(TESTS_DIR, "signals")
|
||||||
LOG_SIGNALS_TO_FILE = False # used for debugging purposes
|
LOG_SIGNALS_TO_FILE = False # used for debugging purposes
|
||||||
|
USER_DIR = option.user_dir if option.user_dir else "/usr/status-user"
|
||||||
|
|
|
@ -2,10 +2,6 @@ services:
|
||||||
anvil:
|
anvil:
|
||||||
ports:
|
ports:
|
||||||
- 8545:8545
|
- 8545:8545
|
||||||
status-go:
|
|
||||||
ports:
|
|
||||||
- 3333:3333
|
|
||||||
- 8354:8354
|
|
||||||
status-backend:
|
status-backend:
|
||||||
ports:
|
ports:
|
||||||
- 3314-3324:3333
|
- 3314-3324:3333
|
||||||
|
|
|
@ -1,32 +1,4 @@
|
||||||
services:
|
services:
|
||||||
status-go:
|
|
||||||
user: ${FUNCTIONAL_TESTS_DOCKER_UID}
|
|
||||||
build:
|
|
||||||
context: ../
|
|
||||||
dockerfile: _assets/build/Dockerfile
|
|
||||||
args:
|
|
||||||
build_tags: gowaku_no_rln,enable_private_api
|
|
||||||
build_target: statusd
|
|
||||||
build_flags: -cover
|
|
||||||
entrypoint: [
|
|
||||||
"statusd",
|
|
||||||
"-c", "/static/configs/config.json",
|
|
||||||
"--server", "0.0.0.0:8354",
|
|
||||||
"--seed-phrase", "test test test test test test test test test test test junk",
|
|
||||||
"--password", "Strong12345",
|
|
||||||
"--dir", "/tmp/status-go-data", # Keep in sync with `config.json/DataDir` value. Later this arg will not be needed.
|
|
||||||
]
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "curl -X POST --data '{\"jsonrpc\":\"2.0\",\"method\":\"net_version\",\"params\":[],\"id\":1}' -H 'Content-Type: application/json' http://0.0.0.0:3333 || exit 1"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 2s
|
|
||||||
retries: 120
|
|
||||||
environment:
|
|
||||||
GOCOVERDIR: "/coverage/binary"
|
|
||||||
volumes:
|
|
||||||
- ./coverage/binary:/coverage/binary
|
|
||||||
stop_signal: SIGINT
|
|
||||||
|
|
||||||
status-backend:
|
status-backend:
|
||||||
user: ${INTEGRATION_TESTS_DOCKER_UID}
|
user: ${INTEGRATION_TESTS_DOCKER_UID}
|
||||||
build:
|
build:
|
||||||
|
@ -50,27 +22,3 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- ./coverage/binary:/coverage/binary
|
- ./coverage/binary:/coverage/binary
|
||||||
stop_signal: SIGINT
|
stop_signal: SIGINT
|
||||||
|
|
||||||
tests-rpc:
|
|
||||||
user: ${FUNCTIONAL_TESTS_DOCKER_UID}
|
|
||||||
depends_on:
|
|
||||||
status-go:
|
|
||||||
condition: service_healthy
|
|
||||||
status-backend:
|
|
||||||
condition: service_healthy
|
|
||||||
deploy-communities-contracts:
|
|
||||||
condition: service_completed_successfully
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: Dockerfile.tests-rpc
|
|
||||||
entrypoint: [
|
|
||||||
"pytest",
|
|
||||||
"-m", "rpc",
|
|
||||||
"--anvil_url=http://anvil:8545",
|
|
||||||
"--rpc_url_statusd=http://status-go:3333",
|
|
||||||
"--status_backend_urls=${STATUS_BACKEND_URLS}",
|
|
||||||
"--ws_url_statusd=ws://status-go:8354",
|
|
||||||
"--junitxml=/tests-rpc/reports/report.xml"
|
|
||||||
]
|
|
||||||
volumes:
|
|
||||||
- .:/tests-rpc
|
|
||||||
|
|
|
@ -6,3 +6,4 @@ genson~=1.2.2
|
||||||
websocket-client~=1.4.2
|
websocket-client~=1.4.2
|
||||||
tenacity~=9.0.0
|
tenacity~=9.0.0
|
||||||
pytest-dependency~=0.6.0
|
pytest-dependency~=0.6.0
|
||||||
|
docker==7.1.0
|
||||||
|
|
|
@ -179,8 +179,8 @@ class NetworkConditionTestCase:
|
||||||
|
|
||||||
class OneToOneMessageTestCase(NetworkConditionTestCase):
|
class OneToOneMessageTestCase(NetworkConditionTestCase):
|
||||||
|
|
||||||
def initialize_backend(self, await_signals, display_name=DEFAULT_DISPLAY_NAME, url=None):
|
def initialize_backend(self, await_signals, display_name=DEFAULT_DISPLAY_NAME):
|
||||||
backend = StatusBackend(await_signals=await_signals, url=url)
|
backend = StatusBackend(await_signals=await_signals)
|
||||||
backend.init_status_backend()
|
backend.init_status_backend()
|
||||||
backend.create_account_and_login(display_name=display_name)
|
backend.create_account_and_login(display_name=display_name)
|
||||||
backend.start_messenger()
|
backend.start_messenger()
|
||||||
|
|
|
@ -4,21 +4,21 @@ import pytest
|
||||||
|
|
||||||
from conftest import option
|
from conftest import option
|
||||||
from constants import user_1, user_2
|
from constants import user_1, user_2
|
||||||
from test_cases import SignalTestCase
|
from test_cases import StatusBackendTestCase
|
||||||
from clients.signals import SignalType
|
from clients.signals import SignalType
|
||||||
|
|
||||||
@pytest.mark.rpc
|
@pytest.mark.rpc
|
||||||
@pytest.mark.transaction
|
@pytest.mark.transaction
|
||||||
@pytest.mark.wallet
|
@pytest.mark.wallet
|
||||||
class TestTransactionFromRoute(SignalTestCase):
|
class TestTransactionFromRoute(StatusBackendTestCase):
|
||||||
await_signals = [
|
await_signals = [
|
||||||
|
SignalType.NODE_LOGIN.value,
|
||||||
SignalType.WALLET_SUGGESTED_ROUTES.value,
|
SignalType.WALLET_SUGGESTED_ROUTES.value,
|
||||||
SignalType.WALLET_ROUTER_SIGN_TRANSACTIONS.value,
|
SignalType.WALLET_ROUTER_SIGN_TRANSACTIONS.value,
|
||||||
SignalType.WALLET_ROUTER_SENDING_TRANSACTIONS_STARTED.value,
|
SignalType.WALLET_ROUTER_SENDING_TRANSACTIONS_STARTED.value,
|
||||||
SignalType.WALLET_TRANSACTION_STATUS_CHANGED.value,
|
SignalType.WALLET_TRANSACTION_STATUS_CHANGED.value,
|
||||||
SignalType.WALLET_ROUTER_TRANSACTIONS_SENT.value,
|
SignalType.WALLET_ROUTER_TRANSACTIONS_SENT.value,
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_tx_from_route(self):
|
def test_tx_from_route(self):
|
||||||
|
|
||||||
_uuid = str(uuid.uuid4())
|
_uuid = str(uuid.uuid4())
|
||||||
|
@ -44,7 +44,7 @@ class TestTransactionFromRoute(SignalTestCase):
|
||||||
]
|
]
|
||||||
response = self.rpc_client.rpc_valid_request(method, params)
|
response = self.rpc_client.rpc_valid_request(method, params)
|
||||||
|
|
||||||
routes = self.signal_client.wait_for_signal(SignalType.WALLET_SUGGESTED_ROUTES.value)
|
routes = self.rpc_client.wait_for_signal(SignalType.WALLET_SUGGESTED_ROUTES.value)
|
||||||
assert routes['event']['Uuid'] == _uuid
|
assert routes['event']['Uuid'] == _uuid
|
||||||
|
|
||||||
method = "wallet_buildTransactionsFromRoute"
|
method = "wallet_buildTransactionsFromRoute"
|
||||||
|
@ -56,7 +56,7 @@ class TestTransactionFromRoute(SignalTestCase):
|
||||||
]
|
]
|
||||||
response = self.rpc_client.rpc_valid_request(method, params)
|
response = self.rpc_client.rpc_valid_request(method, params)
|
||||||
|
|
||||||
wallet_router_sign_transactions = self.signal_client.wait_for_signal(
|
wallet_router_sign_transactions = self.rpc_client.wait_for_signal(
|
||||||
SignalType.WALLET_ROUTER_SIGN_TRANSACTIONS.value)
|
SignalType.WALLET_ROUTER_SIGN_TRANSACTIONS.value)
|
||||||
|
|
||||||
assert wallet_router_sign_transactions['event']['signingDetails']['signOnKeycard'] == False
|
assert wallet_router_sign_transactions['event']['signingDetails']['signOnKeycard'] == False
|
||||||
|
@ -97,19 +97,18 @@ class TestTransactionFromRoute(SignalTestCase):
|
||||||
]
|
]
|
||||||
response = self.rpc_client.rpc_valid_request(method, params)
|
response = self.rpc_client.rpc_valid_request(method, params)
|
||||||
|
|
||||||
tx_status = self.signal_client.wait_for_signal(
|
tx_status = self.rpc_client.wait_for_signal(
|
||||||
SignalType.WALLET_TRANSACTION_STATUS_CHANGED.value)
|
SignalType.WALLET_TRANSACTION_STATUS_CHANGED.value)
|
||||||
|
|
||||||
assert tx_status["event"]["chainId"] == 31337
|
assert tx_status["event"]["chainId"] == 31337
|
||||||
assert tx_status["event"]["status"] == "Success"
|
assert tx_status["event"]["status"] == "Success"
|
||||||
tx_hash = tx_status["event"]["hash"]
|
tx_hash = tx_status["event"]["hash"]
|
||||||
|
|
||||||
method = "eth_getTransactionByHash"
|
method = "ethclient_transactionByHash"
|
||||||
params = [tx_hash]
|
params = [self.network_id, tx_hash]
|
||||||
|
|
||||||
response = self.rpc_client.rpc_valid_request(method, params, url=option.anvil_url)
|
response = self.rpc_client.rpc_valid_request(method, params)
|
||||||
tx_details = response.json()["result"]
|
tx_details = response.json()["result"]["tx"]
|
||||||
|
|
||||||
assert tx_details["value"] == amount_in
|
assert tx_details["value"] == amount_in
|
||||||
assert tx_details["to"].upper() == user_2.address.upper()
|
assert tx_details["to"].upper() == user_2.address.upper()
|
||||||
assert tx_details["from"].upper() == user_1.address.upper()
|
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
import json
|
import json
|
||||||
import random
|
import random
|
||||||
|
import wallet_utils
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from constants import user_1
|
from constants import user_1
|
||||||
from test_cases import SignalTestCase
|
from test_cases import StatusBackendTestCase
|
||||||
|
from clients.signals import SignalType
|
||||||
import wallet_utils
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
EventActivityFilteringDone = "wallet-activity-filtering-done"
|
EventActivityFilteringDone = "wallet-activity-filtering-done"
|
||||||
EventActivityFilteringUpdate = "wallet-activity-filtering-entries-updated"
|
EventActivityFilteringUpdate = "wallet-activity-filtering-entries-updated"
|
||||||
|
@ -20,8 +17,10 @@ def validate_entry(entry, tx_data):
|
||||||
|
|
||||||
@pytest.mark.wallet
|
@pytest.mark.wallet
|
||||||
@pytest.mark.rpc
|
@pytest.mark.rpc
|
||||||
class TestWalletActivitySession(SignalTestCase):
|
class TestWalletActivitySession(StatusBackendTestCase):
|
||||||
await_signals = ["wallet",
|
await_signals = [
|
||||||
|
SignalType.NODE_LOGIN.value,
|
||||||
|
"wallet",
|
||||||
"wallet.suggested.routes",
|
"wallet.suggested.routes",
|
||||||
"wallet.router.sign-transactions",
|
"wallet.router.sign-transactions",
|
||||||
"wallet.router.sending-transactions-started",
|
"wallet.router.sending-transactions-started",
|
||||||
|
@ -29,13 +28,12 @@ class TestWalletActivitySession(SignalTestCase):
|
||||||
"wallet.router.transactions-sent"]
|
"wallet.router.transactions-sent"]
|
||||||
|
|
||||||
def setup_method(self):
|
def setup_method(self):
|
||||||
super().setup_method()
|
|
||||||
self.request_id = str(random.randint(1, 8888))
|
self.request_id = str(random.randint(1, 8888))
|
||||||
|
|
||||||
def test_wallet_start_activity_filter_session(self):
|
def test_wallet_start_activity_filter_session(self):
|
||||||
tx_data = [] # (routes, build_tx, tx_signatures, tx_status)
|
tx_data = [] # (routes, build_tx, tx_signatures, tx_status)
|
||||||
# Set up a transactions for account before starting session
|
# Set up a transactions for account before starting session
|
||||||
tx_data.append(wallet_utils.send_router_transaction(self.rpc_client, self.signal_client))
|
tx_data.append(wallet_utils.send_router_transaction(self.rpc_client))
|
||||||
|
|
||||||
# Start activity session
|
# Start activity session
|
||||||
method = "wallet_startActivityFilterSessionV2"
|
method = "wallet_startActivityFilterSessionV2"
|
||||||
|
@ -43,9 +41,9 @@ class TestWalletActivitySession(SignalTestCase):
|
||||||
{"period": {"startTimestamp": 0, "endTimestamp": 0}, "types": [], "statuses": [],
|
{"period": {"startTimestamp": 0, "endTimestamp": 0}, "types": [], "statuses": [],
|
||||||
"counterpartyAddresses": [], "assets": [], "collectibles": [], "filterOutAssets": False,
|
"counterpartyAddresses": [], "assets": [], "collectibles": [], "filterOutAssets": False,
|
||||||
"filterOutCollectibles": False}, 10]
|
"filterOutCollectibles": False}, 10]
|
||||||
self.signal_client.prepare_wait_for_signal("wallet", 1, lambda signal : signal["event"]["type"] == EventActivityFilteringDone)
|
self.rpc_client.prepare_wait_for_signal("wallet", 1, lambda signal : signal["event"]["type"] == EventActivityFilteringDone)
|
||||||
response = self.rpc_client.rpc_valid_request(method, params, self.request_id)
|
response = self.rpc_client.rpc_valid_request(method, params, self.request_id)
|
||||||
event_response = self.signal_client.wait_for_signal("wallet", timeout=10)['event']
|
event_response = self.rpc_client.wait_for_signal("wallet", timeout=10)['event']
|
||||||
|
|
||||||
# Check response
|
# Check response
|
||||||
sessionID = int(response.json()["result"])
|
sessionID = int(response.json()["result"])
|
||||||
|
@ -60,10 +58,10 @@ class TestWalletActivitySession(SignalTestCase):
|
||||||
validate_entry(message['activities'][0], tx_data[-1])
|
validate_entry(message['activities'][0], tx_data[-1])
|
||||||
|
|
||||||
# Trigger new transaction
|
# Trigger new transaction
|
||||||
self.signal_client.prepare_wait_for_signal("wallet", 1, lambda signal : signal["event"]["type"] == EventActivitySessionUpdated and signal['event']['requestId'] == sessionID)
|
self.rpc_client.prepare_wait_for_signal("wallet", 1, lambda signal : signal["event"]["type"] == EventActivitySessionUpdated and signal['event']['requestId'] == sessionID)
|
||||||
tx_data.append(wallet_utils.send_router_transaction(self.rpc_client, self.signal_client))
|
tx_data.append(wallet_utils.send_router_transaction(self.rpc_client))
|
||||||
print(tx_data[-1])
|
print(tx_data[-1])
|
||||||
event_response = self.signal_client.wait_for_signal("wallet", timeout=10)['event']
|
event_response = self.rpc_client.wait_for_signal("wallet", timeout=10)['event']
|
||||||
|
|
||||||
# Check response event
|
# Check response event
|
||||||
assert int(event_response['requestId']) == sessionID
|
assert int(event_response['requestId']) == sessionID
|
||||||
|
@ -73,9 +71,9 @@ class TestWalletActivitySession(SignalTestCase):
|
||||||
# Reset activity session
|
# Reset activity session
|
||||||
method = "wallet_resetActivityFilterSession"
|
method = "wallet_resetActivityFilterSession"
|
||||||
params = [sessionID, 10]
|
params = [sessionID, 10]
|
||||||
self.signal_client.prepare_wait_for_signal("wallet", 1, lambda signal : signal["event"]["type"] == EventActivityFilteringDone and signal['event']['requestId'] == sessionID)
|
self.rpc_client.prepare_wait_for_signal("wallet", 1, lambda signal : signal["event"]["type"] == EventActivityFilteringDone and signal['event']['requestId'] == sessionID)
|
||||||
response = self.rpc_client.rpc_valid_request(method, params, self.request_id)
|
response = self.rpc_client.rpc_valid_request(method, params, self.request_id)
|
||||||
event_response = self.signal_client.wait_for_signal("wallet", timeout=10)['event']
|
event_response = self.rpc_client.wait_for_signal("wallet", timeout=10)['event']
|
||||||
|
|
||||||
# Check response event
|
# Check response event
|
||||||
assert int(event_response['requestId']) == sessionID
|
assert int(event_response['requestId']) == sessionID
|
||||||
|
|
|
@ -15,7 +15,7 @@ def verify_json_schema(response, method):
|
||||||
jsonschema.validate(instance=response,
|
jsonschema.validate(instance=response,
|
||||||
schema=json.load(schema))
|
schema=json.load(schema))
|
||||||
|
|
||||||
def get_suggested_routes(rpc_client, signal_client, **kwargs):
|
def get_suggested_routes(rpc_client, **kwargs):
|
||||||
_uuid = str(uuid.uuid4())
|
_uuid = str(uuid.uuid4())
|
||||||
amount_in = "0xde0b6b3a7640000"
|
amount_in = "0xde0b6b3a7640000"
|
||||||
|
|
||||||
|
@ -43,15 +43,15 @@ def get_suggested_routes(rpc_client, signal_client, **kwargs):
|
||||||
f"Warning: The key '{key}' does not exist in the input_params parameters and will be ignored.")
|
f"Warning: The key '{key}' does not exist in the input_params parameters and will be ignored.")
|
||||||
params = [input_params]
|
params = [input_params]
|
||||||
|
|
||||||
signal_client.prepare_wait_for_signal("wallet.suggested.routes", 1)
|
rpc_client.prepare_wait_for_signal("wallet.suggested.routes", 1)
|
||||||
_ = rpc_client.rpc_valid_request(method, params)
|
_ = rpc_client.rpc_valid_request(method, params)
|
||||||
|
|
||||||
routes = signal_client.wait_for_signal("wallet.suggested.routes")
|
routes = rpc_client.wait_for_signal("wallet.suggested.routes")
|
||||||
assert routes['event']['Uuid'] == _uuid
|
assert routes['event']['Uuid'] == _uuid
|
||||||
|
|
||||||
return routes['event']
|
return routes['event']
|
||||||
|
|
||||||
def build_transactions_from_route(rpc_client, signal_client, uuid, **kwargs):
|
def build_transactions_from_route(rpc_client, uuid, **kwargs):
|
||||||
method = "wallet_buildTransactionsFromRoute"
|
method = "wallet_buildTransactionsFromRoute"
|
||||||
build_tx_params = {
|
build_tx_params = {
|
||||||
"uuid": uuid,
|
"uuid": uuid,
|
||||||
|
@ -66,7 +66,7 @@ def build_transactions_from_route(rpc_client, signal_client, uuid, **kwargs):
|
||||||
params = [build_tx_params]
|
params = [build_tx_params]
|
||||||
_ = rpc_client.rpc_valid_request(method, params)
|
_ = rpc_client.rpc_valid_request(method, params)
|
||||||
|
|
||||||
wallet_router_sign_transactions = signal_client.wait_for_signal("wallet.router.sign-transactions")
|
wallet_router_sign_transactions = rpc_client.wait_for_signal("wallet.router.sign-transactions")
|
||||||
|
|
||||||
assert wallet_router_sign_transactions['event']['signingDetails']['signOnKeycard'] == False
|
assert wallet_router_sign_transactions['event']['signingDetails']['signOnKeycard'] == False
|
||||||
transaction_hashes = wallet_router_sign_transactions['event']['signingDetails']['hashes']
|
transaction_hashes = wallet_router_sign_transactions['event']['signingDetails']['hashes']
|
||||||
|
@ -101,7 +101,7 @@ def sign_messages(rpc_client, hashes):
|
||||||
tx_signatures[hash] = signature
|
tx_signatures[hash] = signature
|
||||||
return tx_signatures
|
return tx_signatures
|
||||||
|
|
||||||
def send_router_transactions_with_signatures(rpc_client, signal_client, uuid, tx_signatures):
|
def send_router_transactions_with_signatures(rpc_client, uuid, tx_signatures):
|
||||||
method = "wallet_sendRouterTransactionsWithSignatures"
|
method = "wallet_sendRouterTransactionsWithSignatures"
|
||||||
params = [
|
params = [
|
||||||
{
|
{
|
||||||
|
@ -111,18 +111,18 @@ def send_router_transactions_with_signatures(rpc_client, signal_client, uuid, tx
|
||||||
]
|
]
|
||||||
_ = rpc_client.rpc_valid_request(method, params)
|
_ = rpc_client.rpc_valid_request(method, params)
|
||||||
|
|
||||||
tx_status = signal_client.wait_for_signal(
|
tx_status = rpc_client.wait_for_signal(
|
||||||
"wallet.transaction.status-changed")
|
"wallet.transaction.status-changed")
|
||||||
|
|
||||||
assert tx_status["event"]["status"] == "Success"
|
assert tx_status["event"]["status"] == "Success"
|
||||||
|
|
||||||
return tx_status["event"]
|
return tx_status["event"]
|
||||||
|
|
||||||
def send_router_transaction(rpc_client, signal_client, **kwargs):
|
def send_router_transaction(rpc_client, **kwargs):
|
||||||
routes = get_suggested_routes(rpc_client, signal_client, **kwargs)
|
routes = get_suggested_routes(rpc_client, **kwargs)
|
||||||
build_tx = build_transactions_from_route(rpc_client, signal_client, routes['Uuid'])
|
build_tx = build_transactions_from_route(rpc_client, routes['Uuid'])
|
||||||
tx_signatures = sign_messages(rpc_client, build_tx['signingDetails']['hashes'])
|
tx_signatures = sign_messages(rpc_client, build_tx['signingDetails']['hashes'])
|
||||||
tx_status = send_router_transactions_with_signatures(rpc_client, signal_client, routes['Uuid'], tx_signatures)
|
tx_status = send_router_transactions_with_signatures(rpc_client, routes['Uuid'], tx_signatures)
|
||||||
return {
|
return {
|
||||||
"routes": routes,
|
"routes": routes,
|
||||||
"build_tx": build_tx,
|
"build_tx": build_tx,
|
||||||
|
|
Loading…
Reference in New Issue