Skip to content

Commit eb07941

Browse files
chore: tests
1 parent 8eccb23 commit eb07941

File tree

5 files changed

+108
-33
lines changed

5 files changed

+108
-33
lines changed

operate/cli.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
from operate.account.user import UserAccount
4545
from operate.constants import KEY, KEYS, OPERATE_HOME, SERVICES
4646
from operate.ledger.profiles import DEFAULT_NEW_SAFE_FUNDS_AMOUNT
47-
from operate.migration import FormatMigrator
47+
from operate.migration import MigrationManager
4848
from operate.operate_types import Chain, DeploymentStatus, LedgerType
4949
from operate.quickstart.analyse_logs import analyse_logs
5050
from operate.quickstart.claim_staking_rewards import claim_staking_rewards
@@ -96,8 +96,8 @@ def __init__(
9696
)
9797
self.password: t.Optional[str] = os.environ.get("OPERATE_USER_PASSWORD")
9898

99-
fm = FormatMigrator(self._path, self.logger)
100-
fm.migrate_user_json()
99+
mm = MigrationManager(self._path, self.logger)
100+
mm.migrate_account_user()
101101

102102
def create_user_account(self, password: str) -> UserAccount:
103103
"""Create a user account."""

operate/migration.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from pathlib import Path
2626

2727

28-
class FormatMigrator:
28+
class MigrationManager:
2929
"""FormatMigrator"""
3030

3131
# TODO Backport here migration for services/config.json, etc.
@@ -40,11 +40,10 @@ def __init__(
4040
self._path = home
4141
self.logger = logger
4242

43-
def migrate_user_json(self) -> None:
43+
def migrate_account_user(self) -> None:
4444
"""Migrates user.json"""
45-
self.logger.info("[FORMAT MIGRATOR] Migrating user.json")
46-
path = self._path / "user.json"
4745

46+
path = self._path / "user.json"
4847
if not path.exists():
4948
return
5049

@@ -57,3 +56,5 @@ def migrate_user_json(self) -> None:
5756
new_data = {"password_hash": data["password_sha"]}
5857
with open(path, "w", encoding="utf-8") as f:
5958
json.dump(new_data, f, indent=4)
59+
60+
self.logger.info("[MIGRATION MANAGER] Migrated user.json.")

tests/conftest.py

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# -*- coding: utf-8 -*-
2+
# ------------------------------------------------------------------------------
3+
#
4+
# Copyright 2025 Valory AG
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
# ------------------------------------------------------------------------------
19+
20+
"""
21+
Fixtures for pytest
22+
23+
The conftest.py file serves as a means of providing fixtures for an entire
24+
directory. Fixtures defined in a conftest.py can be used by any test in that
25+
package without needing to import them (pytest will automatically discover them).
26+
27+
See https://docs.pytest.org/en/stable/reference/fixtures.html
28+
"""
29+
30+
import random
31+
import string
32+
33+
import pytest
34+
35+
36+
def random_string(length: int = 8) -> str:
37+
"""random_string"""
38+
chars = string.ascii_letters + string.digits
39+
return "".join(random.choices(chars, k=length)) # nosec B311
40+
41+
42+
@pytest.fixture
43+
def password() -> str:
44+
"""password fixture"""
45+
return random_string(16)

tests/test_operate_cli.py

+53-12
Original file line numberDiff line numberDiff line change
@@ -19,31 +19,29 @@
1919

2020
"""Tests for operate.cli module."""
2121

22+
import hashlib
23+
import json
2224
import random
2325
import string
2426
from pathlib import Path
2527

2628
import pytest
29+
from argon2 import PasswordHasher, Type
30+
from argon2.exceptions import InvalidHashError
2731
from web3 import Web3
2832

2933
from operate.cli import OperateApp
34+
from operate.constants import OPERATE_HOME
3035
from operate.operate_types import LedgerType
3136

3237

3338
ROOT_PATH = Path(__file__).resolve().parent
34-
OPERATE = ".operate_test"
3539

3640
MSG_NEW_PASSWORD_MISSING = "You must provide a new password" # nosec
3741
MSG_INVALID_PASSWORD = "Password is not valid" # nosec
3842
MSG_INVALID_MNEMONIC = "Seed phrase is not valid" # nosec
3943

4044

41-
def random_string(length: int = 8) -> str:
42-
"""random_string"""
43-
chars = string.ascii_letters + string.digits
44-
return "".join(random.choices(chars, k=length)) # nosec B311
45-
46-
4745
def random_mnemonic(num_words: int = 12) -> str:
4846
"""Generate a random BIP-39 mnemonic"""
4947
w3 = Web3()
@@ -52,7 +50,7 @@ def random_mnemonic(num_words: int = 12) -> str:
5250
return mnemonic
5351

5452

55-
class TestOperate:
53+
class TestOperateApp:
5654
"""Tests for operate.cli.OperateApp class."""
5755

5856
def test_update_password(
@@ -62,16 +60,16 @@ def test_update_password(
6260
"""Test operate.update_password() and operate.update_password_with_mnemonic()"""
6361

6462
operate = OperateApp(
65-
home=tmp_path / OPERATE,
63+
home=tmp_path / OPERATE_HOME,
6664
)
6765
operate.setup()
6866
password1 = random_string()
6967
operate.create_user_account(password=password1)
7068
operate.password = password1
7169
wallet_manager = operate.wallet_manager
72-
_, mnemonic = wallet_manager.create(LedgerType.ETHEREUM)
73-
num_words = len(mnemonic)
74-
mnemonic = " ".join(mnemonic)
70+
_, mnemonic_list = wallet_manager.create(LedgerType.ETHEREUM)
71+
num_words = len(mnemonic_list)
72+
mnemonic = " ".join(mnemonic_list)
7573

7674
password2 = random_string()
7775
password3 = "!@#Test$%^"
@@ -139,3 +137,46 @@ def test_update_password(
139137
invalid_mnemonic = random_mnemonic(num_words=15)
140138
with pytest.raises(ValueError, match=rf"^{MSG_INVALID_MNEMONIC}"):
141139
operate.update_password_with_mnemonic(invalid_mnemonic, password2)
140+
141+
def test_migrate_account(
142+
self,
143+
tmp_path: Path,
144+
password: str,
145+
) -> None:
146+
operate_home_path = tmp_path / OPERATE_HOME
147+
operate = OperateApp(
148+
home=operate_home_path,
149+
)
150+
151+
# Artificially create an old-format user.json
152+
sha256 = hashlib.sha256()
153+
sha256.update(password.encode())
154+
password_sha = sha256.hexdigest()
155+
data = {"password_sha": password_sha}
156+
user_json_path = operate_home_path / "user.json"
157+
user_json_path.write_text(json.dumps(data, indent=2), encoding="utf-8")
158+
159+
operate = OperateApp(
160+
home=operate_home_path,
161+
)
162+
163+
data = json.loads(user_json_path.read_text(encoding="utf-8"))
164+
165+
assert operate.user_account
166+
assert "password_hash" in data
167+
assert "password_sha" not in data
168+
assert password_sha == data["password_hash"]
169+
ph = PasswordHasher(type=Type.ID)
170+
with pytest.raises(InvalidHashError):
171+
ph.verify(data["password_hash"], password)
172+
173+
operate.user_account.is_valid(password)
174+
data = json.loads(user_json_path.read_text(encoding="utf-8"))
175+
176+
assert operate.user_account
177+
assert operate.user_account.is_valid(password)
178+
assert "password_hash" in data
179+
assert "password_sha" not in data
180+
assert password_sha != data["password_hash"]
181+
ph = PasswordHasher(type=Type.ID)
182+
assert ph.verify(data["password_hash"], password)

tests/test_services_manage.py

+2-14
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919

2020
"""Tests for services.service module."""
2121

22-
import random
23-
import string
2422
import typing as t
2523
from pathlib import Path
2624

@@ -38,14 +36,6 @@
3836
OPERATE = ".operate_test"
3937

4038

41-
@pytest.fixture
42-
def random_string() -> str:
43-
"""random_string"""
44-
length = 8
45-
chars = string.ascii_letters + string.digits
46-
return "".join(random.choices(chars, k=length)) # nosec B311
47-
48-
4939
def get_template(**kwargs: t.Any) -> ServiceTemplate:
5040
"""get_template"""
5141

@@ -108,15 +98,14 @@ def test_service_manager_partial_update(
10898
update_description: bool,
10999
update_hash: bool,
110100
tmp_path: Path,
111-
random_string: str,
101+
password: str,
112102
) -> None:
113103
"""Test operate.service_manager().update()"""
114104

115105
operate = OperateApp(
116106
home=tmp_path / OPERATE,
117107
)
118108
operate.setup()
119-
password = random_string
120109
operate.create_user_account(password=password)
121110
operate.password = password
122111
service_manager = operate.service_manager()
@@ -200,15 +189,14 @@ def test_service_manager_update(
200189
update_description: bool,
201190
update_hash: bool,
202191
tmp_path: Path,
203-
random_string: str,
192+
password: str,
204193
) -> None:
205194
"""Test operate.service_manager().update()"""
206195

207196
operate = OperateApp(
208197
home=tmp_path / OPERATE,
209198
)
210199
operate.setup()
211-
password = random_string
212200
operate.create_user_account(password=password)
213201
operate.password = password
214202
service_manager = operate.service_manager()

0 commit comments

Comments
 (0)