Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 34 additions & 10 deletions tests-functional/README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -184,17 +184,43 @@ For more specific use cases, you can use these specialized fixtures:
#### 3. Manual/Custom Backend Setup
For advanced scenarios, you may set up backends manually in your test or test class (for example, to use custom logic or direct instantiation).
**In this case, you must ensure containers are cleaned up by using the `close_status_backend_containers` fixture.**
For advanced scenarios where you need full control, use `backend_factory`.
Example (from `test_local_pairing.py`):
Key points:
- `backend_factory` returns a fresh `StatusBackend` with no automatic login. You call `init_status_backend()` and any RPCs manually.
- All backends created by the factory within a test are automatically shutdown on teardown.
- You can parametrize the factory (e.g., `ipv6`, `privileged`) via `pytest.mark.parametrize(..., indirect=True)`.
Examples:
1) Negative validation on restore (pre-login error expected):
```python
import pytest
from clients.api import ApiResponseError
from resources.constants import user_mnemonic_12
import copy
def test_restore_with_empty_mnemonic(backend_factory):
user = copy.deepcopy(user_mnemonic_12)
backend = backend_factory("invalid_mnemonic")
backend.init_status_backend()
backend._set_display_name()
data = backend._create_account_request(password=user.password)
data["mnemonic"] = ""
with pytest.raises(ApiResponseError, match=r"restore-account: mnemonic is not set"):
backend.api_request_json("RestoreAccountAndLogin", data)
```
2) Parametrizing the factory (e.g., enabling IPv6 and privileged mode):
```python
@pytest.fixture(autouse=True)
def setup_cleanup(self, close_status_backend_containers):
"""Automatically cleanup containers after each test"""
yield
import pytest
@pytest.mark.parametrize("backend_factory", [{"privileged": True, "ipv6": "Yes"}], indirect=True)
def test_with_ipv6_and_privileged(backend_factory):
be = backend_factory("node")
be.init_status_backend()
# ... continue with manual RPCs
```
This fixture will stop, save logs, and remove all containers created during the test, preventing resource leaks.
#### 4. Summary Table
Expand All @@ -206,13 +232,11 @@ This fixture will stop, save logs, and remove all containers created during the
| `backend_new_profile` | function | New user profile creation | Automatic | Created from scratch |
| `backend_recovered_profile` | function | Existing profile recovery | Automatic | Recovered from seed phrase |
| `waku_light_client` | function | Light client parametrization | N/A | N/A (configuration only) |
| `close_status_backend_containers` | function | For manual/custom setup | Must be used manually | N/A (depends on your setup) |
#### 5. Best Practices
- **Prefer the factory fixtures** for most tests—they are robust, reusable, and handle cleanup for you.
- **Use specialized fixtures** (`backend_new_profile`, `backend_recovered_profile`) when you need specific profile creation behavior.
- **Use `close_status_backend_containers`** if you create backends manually or outside the provided factories.
- **Never leave containers running** after a test—always ensure cleanup is in place.
For more details, see the docstrings in `tests/conftest.py` and the example usages in the test files.
Expand Down
59 changes: 7 additions & 52 deletions tests-functional/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from requests import ReadTimeout

from clients.status_backend import StatusBackend
from clients.statusgo_container import StatusGoContainer
from clients.anvil import Anvil
from clients.foundry import Foundry
from clients.contract_deployers.multicall3 import Multicall3Deployer
Expand Down Expand Up @@ -42,7 +41,11 @@ def test_with_params(self, backend_factory):
# Store created backends for cleanup
created_backends: list[StatusBackend] = []

test_name = request.node.name if hasattr(request, "cls") else str(uuid4)
# Safe generation of names for logs/teardown for both class and function tests
_cls_obj = getattr(request, "cls", None)
cls_name = _cls_obj.__name__ if _cls_obj is not None else None
node = getattr(request, "node", None)
test_name = getattr(node, "name", f"test-{uuid4()}")

def factory(name, **kwargs) -> StatusBackend:
"""
Expand All @@ -55,8 +58,7 @@ def factory(name, **kwargs) -> StatusBackend:
Returns:
StatusBackend: Created backend instance
"""
logging.debug(f"🔧 [SETUP] Creating {name} backend for {request.cls.__name__}")
logging.debug(f"🔧 [SETUP] Creating {name} backend for {test_name}")
logging.debug(f"🔧 [SETUP] Creating {name} backend for {cls_name or test_name}")
logging.debug(f"📋 [SETUP] Parameters: privileged={privileged}, ipv6={ipv6}")

# Create backend
Expand All @@ -69,7 +71,7 @@ def factory(name, **kwargs) -> StatusBackend:
yield factory

# Cleanup all created backends
logging.debug(f"🧹 [TEARDOWN] Cleaning up {len(created_backends)} backends for {request.cls.__name__ if hasattr(request, 'cls') else 'test'}")
logging.debug(f"🧹 [TEARDOWN] Cleaning up {len(created_backends)} backends for {cls_name or 'test'}")

for i, backend in enumerate(reversed(created_backends)):
logging.debug(f"🧹 [TEARDOWN] Cleaning up backend {len(created_backends) - i}...")
Expand Down Expand Up @@ -128,53 +130,6 @@ def _backend_recovered_profile(name: str, user: object, waku_light_client: bool
backend.logout()


@pytest.fixture(scope="function", autouse=False)
def close_status_backend_containers(request):
"""
Fixture to automatically cleanup Status backend containers after each test.
Should be used ONLY for tests that do not use backend_factory or class_backend fixtures.
This fixture ensures that all Status backend containers are properly stopped,
logs are saved, and containers are removed to prevent resource leaks and
conflicts between tests.
How it works:
1. Yields immediately (runs before test execution)
2. After test completes, stops all containers in StatusGoContainer.all_containers
3. Saves logs from each container for debugging
4. Removes containers to free up system resources
5. Clears the containers list
Usage:
# Automatic cleanup for all tests in a class
@pytest.fixture(autouse=True)
def setup_cleanup(self, close_status_backend_containers):
yield
# Manual cleanup for specific test
def test_something(self, close_status_backend_containers):
# test code here
pass
Parameters:
request: pytest request object containing test metadata
Dependencies:
- StatusGoContainer.all_containers: Global list of active containers
- Container objects with stop(), save_logs(), remove() methods
Scope: function (runs once per test function)
Autouse: False (must be explicitly requested)
"""
yield
for container in StatusGoContainer.all_containers:
try:
container.shutdown(log_sufix=request.node.name) # pyright: ignore[reportAttributeAccessIssue]
except Exception as e:
logging.error(f"Error cleaning up container: {e}")
StatusGoContainer.all_containers = []


@pytest.fixture(scope="session")
def anvil_client():
return Anvil()
Expand Down
63 changes: 17 additions & 46 deletions tests-functional/tests/test_backup_mnemonic_and_restore.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,17 @@

from clients.api import ApiResponseError
from clients.signals import SignalType
from clients.status_backend import StatusBackend
from resources.constants import user_mnemonic_12, user_mnemonic_15, user_mnemonic_24, user_keycard_1
from resources.utils import assert_response_attributes
from utils import fake


@pytest.mark.create_account
@pytest.mark.rpc
class TestBackupMnemonicAndRestore:

@pytest.fixture(autouse=True)
def setup_cleanup(self, close_status_backend_containers):
"""Automatically cleanup containers after each test"""
yield

def test_profile_creation_and_mnemonics_backup(self):
def test_profile_creation_and_mnemonics_backup(self, backend_new_profile):
# Create a new account container and initialize
account = StatusBackend()
account.init_status_backend()
account.create_account_and_login(password=fake.profile_password())
account.wait_for_login()
account = backend_new_profile("account")

# Retrieve and verify the mnemonic
settings = account.settings_service.get_settings()
Expand All @@ -35,12 +25,9 @@ def test_profile_creation_and_mnemonics_backup(self):
assert isinstance(mnemonic, str)
assert len(mnemonic.split()) == 12 # Basic check for mnemonic length

def test_backup_account_and_restore_it_via_mnemonics(self):
def test_backup_account_and_restore_it_via_mnemonics(self, backend_new_profile, backend_recovered_profile):
# Create original account and backup mnemonic
original_account = StatusBackend()
original_account.init_status_backend()
original_account.create_account_and_login(password=fake.profile_password())
original_account.wait_for_login()
original_account = backend_new_profile("original")
original_get_settings_response = original_account.settings_service.get_settings()
original_settings = original_get_settings_response
mnemonic = original_settings.get("mnemonic", None)
Expand All @@ -50,10 +37,7 @@ def test_backup_account_and_restore_it_via_mnemonics(self):
user.passphrase = mnemonic

# Restore account in a new container
restored_account = StatusBackend()
restored_account.init_status_backend()
restored_account.restore_account_and_login(user=user)
restored_account.wait_for_login()
restored_account = backend_recovered_profile("restored", user=user)

# Verify mnemonic is not exposed after restore
restored_get_settings_response = restored_account.settings_service.get_settings()
Expand Down Expand Up @@ -82,12 +66,9 @@ def test_backup_account_and_restore_it_via_mnemonics(self):
[user_mnemonic_24, user_mnemonic_15, user_mnemonic_12],
ids=["mnemonic_24", "mnemonic_15", "mnemonic_12"],
)
def test_restore_app_different_valid_size_mnemonics(self, user_mnemonic):
def test_restore_app_different_valid_size_mnemonics(self, user_mnemonic, backend_recovered_profile):
# Initialize backend client and restore account using user_mnemonic.
restored_account = StatusBackend()
restored_account.init_status_backend()
restored_account.restore_account_and_login(user=user_mnemonic)
restored_account.wait_for_login()
restored_account = backend_recovered_profile("restored", user=user_mnemonic)

# Request getAccounts and check restoreed accounts attributes.
get_accounts_response = restored_account.accounts_service.get_accounts()
Expand All @@ -105,51 +86,44 @@ def test_restore_app_different_valid_size_mnemonics(self, user_mnemonic):
"mnemonic_size",
[1, 3, 7, 13, 29],
)
def test_restore_with_arbitrary_size_mnemonics(self, mnemonic_size):
def test_restore_with_arbitrary_size_mnemonics(self, mnemonic_size, backend_recovered_profile):
# Restore with an arbitrary length mnemonic
user = copy.deepcopy(user_mnemonic_12)
user.passphrase = " ".join("".join(random.choice(string.ascii_lowercase) for _ in range(random.randint(2, 10))) for _ in range(mnemonic_size))

restored_account = StatusBackend()
restored_account.init_status_backend()
restored_account.restore_account_and_login(user=user)
restored_account.wait_for_login()
restored_account = backend_recovered_profile("restored", user=user)

get_settings_response = restored_account.settings_service.get_settings()
restored_settings = get_settings_response
assert restored_settings.get("mnemonic", None) is None
assert restored_settings.get("address", None)

def test_restore_with_mnemonic_with_special_chars(self):
def test_restore_with_mnemonic_with_special_chars(self, backend_recovered_profile):
# Restore with an mnemonic with special chars
user = copy.deepcopy(user_mnemonic_12)
user.passphrase = "<>?`~!@#$%^&*()_+1 $fgdg ^&*()"

restored_account = StatusBackend()
restored_account.init_status_backend()
restored_account.restore_account_and_login(user=user)
restored_account.wait_for_login()
restored_account = backend_recovered_profile("restored", user=user)
get_settings_response = restored_account.settings_service.get_settings()
restored_settings = get_settings_response
assert restored_settings.get("mnemonic", None) is None
assert restored_settings.get("address", None)

def test_restore_with_empty_mnemonic(self):
def test_restore_with_empty_mnemonic(self, backend_factory):
# Restore with empty mnemonic isn't allowed
user = copy.deepcopy(user_mnemonic_12)

restored_account = StatusBackend()
restored_account = backend_factory("invalid_mnemonic")
restored_account.init_status_backend()
restored_account._set_display_name()
data = restored_account._create_account_request(password=user.password)
data["mnemonic"] = ""
with pytest.raises(ApiResponseError, match=r"restore-account: mnemonic is not set"):
restored_account.api_request_json("RestoreAccountAndLogin", data)

def test_restore_with_both_mnemonic_and_keycard(self):
def test_restore_with_both_mnemonic_and_keycard(self, backend_factory):
# Restore with both keycard and mnemonic isn't allowed
user = copy.deepcopy(user_mnemonic_12)
restored_account = StatusBackend()
restored_account = backend_factory("both_credentials")
restored_account.init_status_backend()
restored_account._set_display_name()
data = restored_account._create_account_request(password=user.password)
Expand All @@ -158,12 +132,9 @@ def test_restore_with_both_mnemonic_and_keycard(self):
with pytest.raises(ApiResponseError, match=r"restore-account: mnemonic is set for keycard account"):
restored_account.api_request_json("RestoreAccountAndLogin", data)

def test_restored_on_existing_restored_account_fails(self):
def test_restored_on_existing_restored_account_fails(self, backend_recovered_profile):
user = copy.deepcopy(user_mnemonic_12)
restored_account = StatusBackend()
restored_account.init_status_backend()
restored_account.restore_account_and_login(user=user)
restored_account.wait_for_login()
restored_account = backend_recovered_profile("restored", user=user)
restored_account.restore_account_and_login(user=user)
signal = restored_account.wait_for_signal(SignalType.NODE_LOGIN.value)

Expand Down
18 changes: 5 additions & 13 deletions tests-functional/tests/test_init_status_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,16 @@

import pytest

from clients.status_backend import StatusBackend
from resources.constants import user_1


@pytest.mark.create_account
@pytest.mark.rpc
class TestInitialiseApp:

@pytest.fixture(autouse=True)
def setup_cleanup(self, close_status_backend_containers):
"""Automatically cleanup containers after each test"""
yield

@pytest.mark.init
def test_init_app(self):

backend_client = StatusBackend()
backend_client.init_status_backend()
backend_client.restore_account_and_login()
def test_init_app(self, backend_recovered_profile):
backend_client = backend_recovered_profile("init_app", user=user_1)

assert backend_client is not None

Expand All @@ -40,8 +32,8 @@ def assert_file_first_line(path, pattern: str, expected: bool):
@pytest.mark.rpc
@pytest.mark.init
@pytest.mark.parametrize("log_enabled,api_logging_enabled", [(False, False), (True, True)])
def test_check_logs(log_enabled: bool, api_logging_enabled: bool, close_status_backend_containers):
backend = StatusBackend()
def test_check_logs(log_enabled: bool, api_logging_enabled: bool, backend_factory):
backend = backend_factory("prelogin")

data_dir = os.path.join(backend.data_dir, "data")
logs_dir = os.path.join(backend.data_dir, "logs")
Expand Down
17 changes: 2 additions & 15 deletions tests-functional/tests/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,15 @@

import pytest

from clients.status_backend import StatusBackend
from utils import fake


@pytest.mark.rpc
class TestLogging:

@pytest.fixture(autouse=True)
def setup_cleanup(self, close_status_backend_containers):
"""Automatically cleanup containers after each test"""
yield

@pytest.mark.init
def test_logging(self, tmp_path):
backend_client = StatusBackend()
def test_logging(self, tmp_path, backend_new_profile):
backend_client = backend_new_profile("logger")
assert backend_client is not None

# Init and login
backend_client.init_status_backend()
backend_client.create_account_and_login(password=fake.profile_password())
backend_client.wait_for_login()

# Configure logging
backend_client.api_request_json("SetLogLevel", {"logLevel": "ERROR"})
backend_client.api_request_json("SetLogNamespaces", {"logNamespaces": "test1.test2:debug,test1.test2.test3:info"})
Expand Down
Loading