genlayer-test 1.0.0__py3-none-any.whl → 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {genlayer_test-1.0.0.dist-info → genlayer_test-2.0.0.dist-info}/METADATA +23 -8
- {genlayer_test-1.0.0.dist-info → genlayer_test-2.0.0.dist-info}/RECORD +17 -16
- gltest/contracts/contract.py +5 -1
- gltest/contracts/contract_factory.py +5 -2
- gltest/contracts/contract_functions.py +0 -2
- gltest_cli/config/plugin.py +16 -0
- gltest_cli/config/types.py +25 -0
- gltest_cli/config/user.py +12 -2
- tests/examples/contracts/football_prediction_market.py +1 -1
- tests/gltest_cli/config/test_config_integration.py +432 -0
- tests/gltest_cli/config/test_general_config.py +257 -0
- tests/gltest_cli/config/test_plugin.py +89 -0
- tests/gltest_cli/config/test_user.py +10 -0
- {genlayer_test-1.0.0.dist-info → genlayer_test-2.0.0.dist-info}/WHEEL +0 -0
- {genlayer_test-1.0.0.dist-info → genlayer_test-2.0.0.dist-info}/entry_points.txt +0 -0
- {genlayer_test-1.0.0.dist-info → genlayer_test-2.0.0.dist-info}/licenses/LICENSE +0 -0
- {genlayer_test-1.0.0.dist-info → genlayer_test-2.0.0.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: genlayer-test
|
3
|
-
Version:
|
3
|
+
Version: 2.0.0
|
4
4
|
Summary: GenLayer Testing Suite
|
5
5
|
Author: GenLayer
|
6
6
|
License-Expression: MIT
|
@@ -15,7 +15,7 @@ Description-Content-Type: text/markdown
|
|
15
15
|
License-File: LICENSE
|
16
16
|
Requires-Dist: pytest
|
17
17
|
Requires-Dist: setuptools>=77.0
|
18
|
-
Requires-Dist: genlayer-py==0.7.
|
18
|
+
Requires-Dist: genlayer-py==0.7.2
|
19
19
|
Requires-Dist: colorama>=0.4.6
|
20
20
|
Requires-Dist: pyyaml
|
21
21
|
Requires-Dist: python-dotenv
|
@@ -114,6 +114,7 @@ networks:
|
|
114
114
|
|
115
115
|
localnet: # Local development network configuration
|
116
116
|
url: "http://127.0.0.1:4000/api"
|
117
|
+
leader_only: false # Set to true to run all contracts in leader-only mode by default
|
117
118
|
|
118
119
|
testnet_asimov: # Test network configuration
|
119
120
|
id: 4221
|
@@ -125,6 +126,7 @@ networks:
|
|
125
126
|
|
126
127
|
paths:
|
127
128
|
contracts: "contracts" # Path to your contracts directory
|
129
|
+
artifacts: "artifacts" # Path to your artifacts directory
|
128
130
|
|
129
131
|
environment: .env # Path to your environment file containing private keys and other secrets
|
130
132
|
```
|
@@ -137,12 +139,14 @@ Key configuration sections:
|
|
137
139
|
- `url`: The RPC endpoint for the network
|
138
140
|
- `id`: Chain ID
|
139
141
|
- `accounts`: List of account private keys (using environment variables)
|
142
|
+
- `leader_only`: Leader only mode
|
140
143
|
- Special case for `localnet`:
|
141
144
|
- If a network is named `localnet`, missing fields will be filled with default values
|
142
145
|
- For all other network names, `id`, `url`, and `accounts` are required fields
|
143
146
|
|
144
147
|
2. **Paths**: Define important directory paths
|
145
148
|
- `contracts`: Location of your contract files
|
149
|
+
- `artifacts`: Location of your artifacts files (analysis results will be stored here)
|
146
150
|
|
147
151
|
3. **Environment**: Path to your `.env` file containing sensitive information like private keys
|
148
152
|
|
@@ -231,6 +235,20 @@ def test_with_mocked_llm(setup_validators):
|
|
231
235
|
|
232
236
|
Note: This feature is only available when running tests on localnet.
|
233
237
|
|
238
|
+
11. Run tests with leader-only mode enabled
|
239
|
+
```bash
|
240
|
+
$ gltest --leader-only
|
241
|
+
```
|
242
|
+
The `--leader-only` flag configures all contract deployments and write operations to run only on the leader node. This is useful for:
|
243
|
+
- Faster test execution by avoiding consensus
|
244
|
+
- Testing specific leader-only scenarios
|
245
|
+
- Development and debugging purposes
|
246
|
+
- Reducing computational overhead in test environments
|
247
|
+
|
248
|
+
When this flag is enabled, all contracts deployed and all write transactions will automatically use leader-only mode, regardless of individual method parameters.
|
249
|
+
|
250
|
+
**Note:** Leader-only mode is only available for studio-based networks (localhost, 127.0.0.1, *.genlayer.com, *.genlayerlabs.com). When enabled on other networks, it will have no effect and a warning will be logged.
|
251
|
+
|
234
252
|
## 🚀 Key Features
|
235
253
|
|
236
254
|
- **Pytest Integration** – Extends pytest to support intelligent contract testing, making it familiar and easy to adopt.
|
@@ -251,8 +269,9 @@ Before diving into the examples, let's understand the basic project structure:
|
|
251
269
|
genlayer-example/
|
252
270
|
├── contracts/ # Contract definitions
|
253
271
|
│ └── storage.py # Example storage contract
|
254
|
-
|
255
|
-
|
272
|
+
├── test/ # Test files
|
273
|
+
│ └── test_contract.py # Contract test cases
|
274
|
+
└── gltest.config.yaml # Configuration file
|
256
275
|
```
|
257
276
|
|
258
277
|
### Storage Contract Example
|
@@ -311,7 +330,6 @@ def test_deployment():
|
|
311
330
|
args=["initial_value"], # Constructor arguments
|
312
331
|
account=get_default_account(), # Account to deploy from
|
313
332
|
consensus_max_rotations=3, # Optional: max consensus rotations
|
314
|
-
leader_only=False, # Optional: whether to run only on leader
|
315
333
|
)
|
316
334
|
|
317
335
|
# Contract is now deployed and ready to use
|
@@ -357,7 +375,6 @@ def test_write_methods():
|
|
357
375
|
).transact(
|
358
376
|
value=0, # Optional: amount of native currency to send
|
359
377
|
consensus_max_rotations=3, # Optional: max consensus rotations
|
360
|
-
leader_only=False, # Optional: whether to run only on leader
|
361
378
|
wait_interval=1, # Optional: seconds between status checks
|
362
379
|
wait_retries=10, # Optional: max number of retries
|
363
380
|
)
|
@@ -604,13 +621,11 @@ The `.analyze()` method helps you:
|
|
604
621
|
# Try with increased consensus parameters
|
605
622
|
contract = factory.deploy(
|
606
623
|
consensus_max_rotations=5, # Increase number of consensus rotations
|
607
|
-
leader_only=True, # Try leader-only mode for faster execution
|
608
624
|
)
|
609
625
|
|
610
626
|
# For critical operations, use more conservative settings
|
611
627
|
contract = factory.deploy(
|
612
628
|
consensus_max_rotations=10, # More rotations for better reliability
|
613
|
-
leader_only=False, # Full consensus for better security
|
614
629
|
wait_interval=3, # Longer wait between checks
|
615
630
|
wait_retries=30 # More retries for consensus
|
616
631
|
)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
genlayer_test-
|
1
|
+
genlayer_test-2.0.0.dist-info/licenses/LICENSE,sha256=che_H4vE0QUx3HvWrAa1_jDEVInift0U6VO15-QqEls,1064
|
2
2
|
gltest/__init__.py,sha256=qoBV3IgDJr8uh9262XsWNMhi-ilkSgMqKVC9FVMk7cw,372
|
3
3
|
gltest/accounts.py,sha256=HUmWguJMolggQaZNRPw-LGlRlQCjLLdUanKRowMv6pI,812
|
4
4
|
gltest/assertions.py,sha256=0dEk0VxcHK4I7GZPHxJmz-2jaA60V499gOSR74rZbfM,1748
|
@@ -10,9 +10,9 @@ gltest/types.py,sha256=BODmwTr2gAUEiO9FjiuTiWwuKvXgo4xZWstQWNUfnlw,156
|
|
10
10
|
gltest/artifacts/__init__.py,sha256=qTt3TE19gVNWnQLUlt5aDe4nNvJ2YJ1jzDkMmYIsCG0,194
|
11
11
|
gltest/artifacts/contract.py,sha256=KChpmfjZod_0dVB8y-dvWz6IVm7QlIJsgG2ArtvVDaU,6457
|
12
12
|
gltest/contracts/__init__.py,sha256=A9bvEtYOoqoHS8TLlFBfmNOnfwdsJPEf-AZuikagCHM,166
|
13
|
-
gltest/contracts/contract.py,sha256=
|
14
|
-
gltest/contracts/contract_factory.py,sha256=
|
15
|
-
gltest/contracts/contract_functions.py,sha256=
|
13
|
+
gltest/contracts/contract.py,sha256=cYr5B5Sy-YRtEk6PvlLGkRy1X0cwf1EmsCHb6xPL-Rw,6524
|
14
|
+
gltest/contracts/contract_factory.py,sha256=4xoODlCu2jHh3Z2e6OmWWnAiXT99yFu_7DhVvCUbKZE,6812
|
15
|
+
gltest/contracts/contract_functions.py,sha256=Dpgu9rUSMrczBgNSlEaZ47RUpKzV8vucnc6OyxExMnM,2017
|
16
16
|
gltest/contracts/method_stats.py,sha256=zWWjvf7K5VC4yrHpDIR717VF7LYp1RaZ1Hr_RZvWxJA,5150
|
17
17
|
gltest/contracts/stats_collector.py,sha256=fuCc8L8hd0tsVGzH4adtZWwPa7ORf0A0zR5Dt1w92Qk,9033
|
18
18
|
gltest/contracts/utils.py,sha256=TTXgcXn9BuRIlKJrjwmU7R3l1IgXsXk2luM-r3lfbbg,296
|
@@ -24,13 +24,13 @@ gltest_cli/main.py,sha256=Ti2-0Ev1x5_cM0D1UKqdgaDt80CDHEQGtdRne2qLm4M,53
|
|
24
24
|
gltest_cli/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
25
25
|
gltest_cli/config/constants.py,sha256=3iSK337AeupyYl_6Sf8MR_o91XfLmul8m1rVmi3Fvmo,342
|
26
26
|
gltest_cli/config/general.py,sha256=ezpoGsT8grO9zQH6RugV14b1GzeFt-htYToHQBJhNvY,186
|
27
|
-
gltest_cli/config/plugin.py,sha256=
|
27
|
+
gltest_cli/config/plugin.py,sha256=zWNEJWjywI-T5B5Bc-8OyrGt7rUVlpNP8zrlsORhVgw,5412
|
28
28
|
gltest_cli/config/pytest_context.py,sha256=Ze8JSkrwMTCE8jIhpzU_71CEXg92SiEPvSgNTp-gbS4,243
|
29
|
-
gltest_cli/config/types.py,sha256=
|
30
|
-
gltest_cli/config/user.py,sha256=
|
29
|
+
gltest_cli/config/types.py,sha256=KDAA_vNqI7o_sYMj21ks3WaIXGrbLVb76E17TSlWUwI,7241
|
30
|
+
gltest_cli/config/user.py,sha256=WNX_Wi2eJpjW0UstQuQiDjzRpOIOwx9iiXOx4ntt93U,8759
|
31
31
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
32
32
|
tests/conftest.py,sha256=RKdoE5_zcMimeojAoA_GSFI9du4pMzMi1vZ1njtfoAs,28
|
33
|
-
tests/examples/contracts/football_prediction_market.py,sha256=
|
33
|
+
tests/examples/contracts/football_prediction_market.py,sha256=9xwU8f3q73Hae-ByHy_wauhMPLRnLZd4XKNrClnjOJM,3248
|
34
34
|
tests/examples/contracts/intelligent_oracle.py,sha256=cZNGbjKMXY-pimVmPIKIlS963Gd3L1JAipq0VBR1J5Q,12360
|
35
35
|
tests/examples/contracts/intelligent_oracle_factory.py,sha256=8lBEn3Atb0yUpXwlvnShlcRxCBTXCrrkoITDHWoWuHU,1499
|
36
36
|
tests/examples/contracts/llm_erc20.py,sha256=pOvSUszCtC_f5yDX0tZnj494Ce10uESZ09JLLE8V67o,2534
|
@@ -65,11 +65,12 @@ tests/gltest/artifact/contracts/duplicate_ic_contract_2.py,sha256=bSWsUVjBy5cGtI
|
|
65
65
|
tests/gltest/artifact/contracts/not_ic_contract.py,sha256=hQyGnYiiVceYdLI2WrvcFgPqzy1S4-YMb9FPhiHEGSA,510
|
66
66
|
tests/gltest/assertions/test_assertions.py,sha256=qzVrOdOM4xYtIy1sFHVAD_-naDHOequ23tEN0MELh0k,10781
|
67
67
|
tests/gltest_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
68
|
-
tests/gltest_cli/config/
|
69
|
-
tests/gltest_cli/config/
|
70
|
-
tests/gltest_cli/config/
|
71
|
-
|
72
|
-
genlayer_test-
|
73
|
-
genlayer_test-
|
74
|
-
genlayer_test-
|
75
|
-
genlayer_test-
|
68
|
+
tests/gltest_cli/config/test_config_integration.py,sha256=vPTzr3_h9UMw7m72HogBJE2ZPhRduXoLSq18Z7FoCWQ,10105
|
69
|
+
tests/gltest_cli/config/test_general_config.py,sha256=UHtSwVnso-ZwNtYM0Z4v2sCLKwyrVbHlk6b1leVfV84,14703
|
70
|
+
tests/gltest_cli/config/test_plugin.py,sha256=87kJpSYcWbNuKDqfc_jiN7hoRnFkMEnOOTINwuXKBY0,7981
|
71
|
+
tests/gltest_cli/config/test_user.py,sha256=JxR655oUFoM9quWQO68CVPKRpT0TMpzS3bF6j6NWyT4,14401
|
72
|
+
genlayer_test-2.0.0.dist-info/METADATA,sha256=ykgDxxVQ0p5XRRShD6ke9amrhH4o7MacAQsUya0r5Ts,24978
|
73
|
+
genlayer_test-2.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
74
|
+
genlayer_test-2.0.0.dist-info/entry_points.txt,sha256=RWPcSArBpz_G4BYioh5L8Q8hyClRbSgzLimjcWMp-BQ,94
|
75
|
+
genlayer_test-2.0.0.dist-info/top_level.txt,sha256=-qiGZxTRBytujzgVcKpxjvQ-WNeUDjXa59ceGMwMpko,24
|
76
|
+
genlayer_test-2.0.0.dist-info/RECORD,,
|
gltest/contracts/contract.py
CHANGED
@@ -46,7 +46,6 @@ def write_contract_wrapper(
|
|
46
46
|
def transact_method(
|
47
47
|
value: int = 0,
|
48
48
|
consensus_max_rotations: Optional[int] = None,
|
49
|
-
leader_only: bool = False,
|
50
49
|
wait_transaction_status: TransactionStatus = TransactionStatus.FINALIZED,
|
51
50
|
wait_interval: Optional[int] = None,
|
52
51
|
wait_retries: Optional[int] = None,
|
@@ -67,6 +66,11 @@ def write_contract_wrapper(
|
|
67
66
|
if wait_retries is not None
|
68
67
|
else general_config.get_default_wait_retries()
|
69
68
|
)
|
69
|
+
leader_only = (
|
70
|
+
general_config.get_leader_only()
|
71
|
+
if general_config.check_studio_based_rpc()
|
72
|
+
else False
|
73
|
+
)
|
70
74
|
client = get_gl_client()
|
71
75
|
tx_hash = client.write_contract(
|
72
76
|
address=self.address,
|
@@ -23,7 +23,6 @@ from gltest.exceptions import DeploymentError
|
|
23
23
|
from gltest_cli.config.general import get_general_config
|
24
24
|
|
25
25
|
|
26
|
-
|
27
26
|
@dataclass
|
28
27
|
class ContractFactory:
|
29
28
|
"""
|
@@ -108,7 +107,6 @@ class ContractFactory:
|
|
108
107
|
args: List[Any] = [],
|
109
108
|
account: Optional[LocalAccount] = None,
|
110
109
|
consensus_max_rotations: Optional[int] = None,
|
111
|
-
leader_only: bool = False,
|
112
110
|
wait_interval: Optional[int] = None,
|
113
111
|
wait_retries: Optional[int] = None,
|
114
112
|
wait_transaction_status: TransactionStatus = TransactionStatus.FINALIZED,
|
@@ -121,6 +119,11 @@ class ContractFactory:
|
|
121
119
|
wait_interval = general_config.get_default_wait_interval()
|
122
120
|
if wait_retries is None:
|
123
121
|
wait_retries = general_config.get_default_wait_retries()
|
122
|
+
leader_only = (
|
123
|
+
general_config.get_leader_only()
|
124
|
+
if general_config.check_studio_based_rpc()
|
125
|
+
else False
|
126
|
+
)
|
124
127
|
|
125
128
|
client = get_gl_client()
|
126
129
|
try:
|
@@ -20,7 +20,6 @@ class ContractFunction:
|
|
20
20
|
self,
|
21
21
|
value: int = 0,
|
22
22
|
consensus_max_rotations: Optional[int] = None,
|
23
|
-
leader_only: bool = False,
|
24
23
|
wait_transaction_status: TransactionStatus = TransactionStatus.FINALIZED,
|
25
24
|
wait_interval: Optional[int] = None,
|
26
25
|
wait_retries: Optional[int] = None,
|
@@ -32,7 +31,6 @@ class ContractFunction:
|
|
32
31
|
return self.transact_method(
|
33
32
|
value=value,
|
34
33
|
consensus_max_rotations=consensus_max_rotations,
|
35
|
-
leader_only=leader_only,
|
36
34
|
wait_transaction_status=wait_transaction_status,
|
37
35
|
wait_interval=wait_interval,
|
38
36
|
wait_retries=wait_retries,
|
gltest_cli/config/plugin.py
CHANGED
@@ -64,6 +64,13 @@ def pytest_addoption(parser):
|
|
64
64
|
help="Test with mocks",
|
65
65
|
)
|
66
66
|
|
67
|
+
group.addoption(
|
68
|
+
"--leader-only",
|
69
|
+
action="store_true",
|
70
|
+
default=False,
|
71
|
+
help="Run contracts in leader-only mode",
|
72
|
+
)
|
73
|
+
|
67
74
|
|
68
75
|
def pytest_configure(config):
|
69
76
|
general_config = get_general_config()
|
@@ -91,6 +98,7 @@ def pytest_configure(config):
|
|
91
98
|
rpc_url = config.getoption("--rpc-url")
|
92
99
|
network = config.getoption("--network")
|
93
100
|
test_with_mocks = config.getoption("--test-with-mocks")
|
101
|
+
leader_only = config.getoption("--leader-only")
|
94
102
|
|
95
103
|
plugin_config = PluginConfig()
|
96
104
|
plugin_config.contracts_dir = (
|
@@ -104,6 +112,7 @@ def pytest_configure(config):
|
|
104
112
|
plugin_config.rpc_url = rpc_url
|
105
113
|
plugin_config.network_name = network
|
106
114
|
plugin_config.test_with_mocks = test_with_mocks
|
115
|
+
plugin_config.leader_only = leader_only
|
107
116
|
|
108
117
|
general_config.plugin_config = plugin_config
|
109
118
|
|
@@ -137,6 +146,13 @@ def pytest_sessionstart(session):
|
|
137
146
|
logger.info(f" Default wait retries: {general_config.get_default_wait_retries()}")
|
138
147
|
logger.info(f" Test with mocks: {general_config.get_test_with_mocks()}")
|
139
148
|
|
149
|
+
if general_config.get_leader_only() and not general_config.check_studio_based_rpc():
|
150
|
+
logger.warning(
|
151
|
+
"Leader only mode: True (enabled on non-studio network - will have no effect)"
|
152
|
+
)
|
153
|
+
else:
|
154
|
+
logger.info(f" Leader only mode: {general_config.get_leader_only()}")
|
155
|
+
|
140
156
|
|
141
157
|
def pytest_runtest_setup(item):
|
142
158
|
_pytest_context.current_item = item
|
gltest_cli/config/types.py
CHANGED
@@ -21,6 +21,7 @@ class PluginConfig:
|
|
21
21
|
default_wait_retries: Optional[int] = None
|
22
22
|
network_name: Optional[str] = None
|
23
23
|
test_with_mocks: bool = False
|
24
|
+
leader_only: bool = False
|
24
25
|
|
25
26
|
|
26
27
|
@dataclass
|
@@ -29,6 +30,7 @@ class NetworkConfigData:
|
|
29
30
|
url: Optional[str] = None
|
30
31
|
accounts: Optional[List[str]] = None
|
31
32
|
from_account: Optional[str] = None
|
33
|
+
leader_only: bool = False
|
32
34
|
|
33
35
|
def __post_init__(self):
|
34
36
|
if self.id is not None and not isinstance(self.id, int):
|
@@ -157,8 +159,31 @@ class GeneralConfig:
|
|
157
159
|
def get_test_with_mocks(self) -> bool:
|
158
160
|
return self.plugin_config.test_with_mocks
|
159
161
|
|
162
|
+
def get_leader_only(self) -> bool:
|
163
|
+
if self.plugin_config.leader_only:
|
164
|
+
return True
|
165
|
+
network_name = self.get_network_name()
|
166
|
+
if network_name in self.user_config.networks:
|
167
|
+
network_config = self.user_config.networks[network_name]
|
168
|
+
return network_config.leader_only
|
169
|
+
return False
|
170
|
+
|
160
171
|
def check_local_rpc(self) -> bool:
|
161
172
|
SUPPORTED_RPC_DOMAINS = ["localhost", "127.0.0.1"]
|
162
173
|
rpc_url = self.get_rpc_url()
|
163
174
|
domain = urlparse(rpc_url).netloc.split(":")[0] # Extract domain without port
|
164
175
|
return domain in SUPPORTED_RPC_DOMAINS
|
176
|
+
|
177
|
+
def check_studio_based_rpc(self) -> bool:
|
178
|
+
SUPPORTED_RPC_DOMAINS = ["localhost", "127.0.0.1"]
|
179
|
+
rpc_url = self.get_rpc_url()
|
180
|
+
domain = urlparse(rpc_url).netloc.split(":")[0] # Extract domain without port
|
181
|
+
|
182
|
+
if domain in SUPPORTED_RPC_DOMAINS:
|
183
|
+
return True
|
184
|
+
|
185
|
+
# Check .genlayer.com or .genlayerlabs.com subdomains
|
186
|
+
if domain.endswith(".genlayer.com") or domain.endswith(".genlayerlabs.com"):
|
187
|
+
return True
|
188
|
+
|
189
|
+
return False
|
gltest_cli/config/user.py
CHANGED
@@ -17,7 +17,7 @@ from gltest_cli.config.constants import (
|
|
17
17
|
from gltest_cli.config.types import UserConfig, NetworkConfigData, PathConfig
|
18
18
|
|
19
19
|
VALID_ROOT_KEYS = ["networks", "paths", "environment"]
|
20
|
-
VALID_NETWORK_KEYS = ["id", "url", "accounts", "from"]
|
20
|
+
VALID_NETWORK_KEYS = ["id", "url", "accounts", "from", "leader_only"]
|
21
21
|
VALID_PATHS_KEYS = ["contracts", "artifacts"]
|
22
22
|
|
23
23
|
|
@@ -33,6 +33,7 @@ def get_default_user_config() -> UserConfig:
|
|
33
33
|
url=DEFAULT_RPC_URL,
|
34
34
|
accounts=accounts_private_keys,
|
35
35
|
from_account=accounts_private_keys[0],
|
36
|
+
leader_only=False,
|
36
37
|
),
|
37
38
|
},
|
38
39
|
paths=PathConfig(
|
@@ -84,6 +85,10 @@ def validate_network_config(network_name: str, network_config: dict):
|
|
84
85
|
|
85
86
|
if "from" in network_config and not isinstance(network_config["from"], str):
|
86
87
|
raise ValueError(f"network {network_name} from must be a string")
|
88
|
+
if "leader_only" in network_config and not isinstance(
|
89
|
+
network_config["leader_only"], bool
|
90
|
+
):
|
91
|
+
raise ValueError(f"network {network_name} leader_only must be a boolean")
|
87
92
|
|
88
93
|
# For non-default networks, url and accounts are required
|
89
94
|
if network_name != DEFAULT_NETWORK:
|
@@ -185,18 +190,23 @@ def _get_overridden_networks(raw_config: dict) -> tuple[dict, str]:
|
|
185
190
|
]
|
186
191
|
if "from" in network_config:
|
187
192
|
networks_config[network_name].from_account = network_config["from"]
|
193
|
+
if "leader_only" in network_config:
|
194
|
+
networks_config[network_name].leader_only = network_config[
|
195
|
+
"leader_only"
|
196
|
+
]
|
188
197
|
continue
|
189
198
|
|
190
199
|
url = network_config["url"]
|
191
200
|
accounts = network_config["accounts"]
|
192
201
|
from_account = network_config.get("from", accounts[0])
|
193
202
|
network_id = network_config.get("id")
|
194
|
-
|
203
|
+
leader_only = network_config.get("leader_only", False)
|
195
204
|
networks_config[network_name] = NetworkConfigData(
|
196
205
|
id=network_id,
|
197
206
|
url=url,
|
198
207
|
accounts=accounts,
|
199
208
|
from_account=from_account,
|
209
|
+
leader_only=leader_only,
|
200
210
|
)
|
201
211
|
return networks_config, user_default_network
|
202
212
|
|
@@ -0,0 +1,432 @@
|
|
1
|
+
def test_leader_only_network_config_true(pytester):
|
2
|
+
pytester.makepyfile(
|
3
|
+
"""
|
4
|
+
from gltest_cli.config.general import get_general_config
|
5
|
+
|
6
|
+
def test_leader_only_network_config():
|
7
|
+
general_config = get_general_config()
|
8
|
+
assert general_config.get_leader_only() == True
|
9
|
+
"""
|
10
|
+
)
|
11
|
+
|
12
|
+
config_content = """
|
13
|
+
networks:
|
14
|
+
default: localnet
|
15
|
+
localnet:
|
16
|
+
url: "http://127.0.0.1:4000/api"
|
17
|
+
leader_only: true
|
18
|
+
|
19
|
+
paths:
|
20
|
+
contracts: "contracts"
|
21
|
+
|
22
|
+
environment: .env
|
23
|
+
"""
|
24
|
+
|
25
|
+
pytester.makefile(".config.yaml", **{"gltest": config_content})
|
26
|
+
|
27
|
+
result = pytester.runpytest("-v")
|
28
|
+
|
29
|
+
result.stdout.fnmatch_lines(
|
30
|
+
[
|
31
|
+
"*::test_leader_only_network_config PASSED*",
|
32
|
+
]
|
33
|
+
)
|
34
|
+
assert result.ret == 0
|
35
|
+
|
36
|
+
|
37
|
+
def test_leader_only_network_config_false(pytester):
|
38
|
+
pytester.makepyfile(
|
39
|
+
"""
|
40
|
+
from gltest_cli.config.general import get_general_config
|
41
|
+
|
42
|
+
def test_leader_only_network_config():
|
43
|
+
general_config = get_general_config()
|
44
|
+
assert general_config.get_leader_only() == False
|
45
|
+
"""
|
46
|
+
)
|
47
|
+
|
48
|
+
config_content = """
|
49
|
+
networks:
|
50
|
+
default: localnet
|
51
|
+
localnet:
|
52
|
+
url: "http://127.0.0.1:4000/api"
|
53
|
+
leader_only: false
|
54
|
+
|
55
|
+
paths:
|
56
|
+
contracts: "contracts"
|
57
|
+
|
58
|
+
environment: .env
|
59
|
+
"""
|
60
|
+
|
61
|
+
pytester.makefile(".config.yaml", **{"gltest": config_content})
|
62
|
+
|
63
|
+
result = pytester.runpytest("-v")
|
64
|
+
|
65
|
+
result.stdout.fnmatch_lines(
|
66
|
+
[
|
67
|
+
"*::test_leader_only_network_config PASSED*",
|
68
|
+
]
|
69
|
+
)
|
70
|
+
assert result.ret == 0
|
71
|
+
|
72
|
+
|
73
|
+
def test_leader_only_cli_overrides_network_config(pytester):
|
74
|
+
pytester.makepyfile(
|
75
|
+
"""
|
76
|
+
from gltest_cli.config.general import get_general_config
|
77
|
+
|
78
|
+
def test_leader_only_cli_overrides():
|
79
|
+
general_config = get_general_config()
|
80
|
+
assert general_config.get_leader_only() == True
|
81
|
+
"""
|
82
|
+
)
|
83
|
+
|
84
|
+
config_content = """
|
85
|
+
networks:
|
86
|
+
default: localnet
|
87
|
+
localnet:
|
88
|
+
url: "http://127.0.0.1:4000/api"
|
89
|
+
leader_only: false
|
90
|
+
|
91
|
+
paths:
|
92
|
+
contracts: "contracts"
|
93
|
+
|
94
|
+
environment: .env
|
95
|
+
"""
|
96
|
+
|
97
|
+
pytester.makefile(".config.yaml", **{"gltest": config_content})
|
98
|
+
|
99
|
+
# CLI flag should override network config
|
100
|
+
result = pytester.runpytest("--leader-only", "-v")
|
101
|
+
|
102
|
+
result.stdout.fnmatch_lines(
|
103
|
+
[
|
104
|
+
"*::test_leader_only_cli_overrides PASSED*",
|
105
|
+
]
|
106
|
+
)
|
107
|
+
assert result.ret == 0
|
108
|
+
|
109
|
+
|
110
|
+
def test_leader_only_network_config_default(pytester):
|
111
|
+
pytester.makepyfile(
|
112
|
+
"""
|
113
|
+
from gltest_cli.config.general import get_general_config
|
114
|
+
|
115
|
+
def test_leader_only_network_config_default():
|
116
|
+
general_config = get_general_config()
|
117
|
+
assert general_config.get_leader_only() == False
|
118
|
+
"""
|
119
|
+
)
|
120
|
+
|
121
|
+
config_content = """
|
122
|
+
networks:
|
123
|
+
default: localnet
|
124
|
+
localnet:
|
125
|
+
url: "http://127.0.0.1:4000/api"
|
126
|
+
|
127
|
+
paths:
|
128
|
+
contracts: "contracts"
|
129
|
+
|
130
|
+
environment: .env
|
131
|
+
"""
|
132
|
+
|
133
|
+
pytester.makefile(".config.yaml", **{"gltest": config_content})
|
134
|
+
|
135
|
+
result = pytester.runpytest("-v")
|
136
|
+
result.stdout.fnmatch_lines(
|
137
|
+
[
|
138
|
+
"*::test_leader_only_network_config_default PASSED*",
|
139
|
+
]
|
140
|
+
)
|
141
|
+
assert result.ret == 0
|
142
|
+
|
143
|
+
|
144
|
+
def test_custom_accounts_config(pytester):
|
145
|
+
"""Test custom accounts configuration."""
|
146
|
+
pytester.makepyfile(
|
147
|
+
"""
|
148
|
+
from gltest_cli.config.general import get_general_config
|
149
|
+
|
150
|
+
def test_custom_accounts():
|
151
|
+
general_config = get_general_config()
|
152
|
+
accounts = general_config.get_accounts_keys()
|
153
|
+
assert len(accounts) == 3
|
154
|
+
assert accounts[0] == "account1_private_key"
|
155
|
+
assert accounts[1] == "account2_private_key"
|
156
|
+
assert accounts[2] == "account3_private_key"
|
157
|
+
from_account = general_config.get_default_account_key()
|
158
|
+
assert from_account == "account1_private_key"
|
159
|
+
"""
|
160
|
+
)
|
161
|
+
|
162
|
+
config_content = """
|
163
|
+
networks:
|
164
|
+
default: localnet
|
165
|
+
localnet:
|
166
|
+
url: "http://127.0.0.1:4000/api"
|
167
|
+
accounts:
|
168
|
+
- "account1_private_key"
|
169
|
+
- "account2_private_key"
|
170
|
+
- "account3_private_key"
|
171
|
+
|
172
|
+
paths:
|
173
|
+
contracts: "contracts"
|
174
|
+
|
175
|
+
environment: .env
|
176
|
+
"""
|
177
|
+
|
178
|
+
pytester.makefile(".config.yaml", **{"gltest": config_content})
|
179
|
+
|
180
|
+
result = pytester.runpytest("-v")
|
181
|
+
result.stdout.fnmatch_lines(
|
182
|
+
[
|
183
|
+
"*::test_custom_accounts PASSED*",
|
184
|
+
]
|
185
|
+
)
|
186
|
+
assert result.ret == 0
|
187
|
+
|
188
|
+
|
189
|
+
def test_from_account_config(pytester):
|
190
|
+
"""Test 'from' account configuration."""
|
191
|
+
pytester.makepyfile(
|
192
|
+
"""
|
193
|
+
from gltest_cli.config.general import get_general_config
|
194
|
+
|
195
|
+
def test_from_account():
|
196
|
+
general_config = get_general_config()
|
197
|
+
from_account = general_config.get_default_account_key()
|
198
|
+
assert from_account == "account2_private_key"
|
199
|
+
"""
|
200
|
+
)
|
201
|
+
|
202
|
+
config_content = """
|
203
|
+
networks:
|
204
|
+
default: localnet
|
205
|
+
localnet:
|
206
|
+
url: "http://127.0.0.1:4000/api"
|
207
|
+
accounts:
|
208
|
+
- "account1_private_key"
|
209
|
+
- "account2_private_key"
|
210
|
+
- "account3_private_key"
|
211
|
+
from: "account2_private_key"
|
212
|
+
|
213
|
+
paths:
|
214
|
+
contracts: "contracts"
|
215
|
+
|
216
|
+
environment: .env
|
217
|
+
"""
|
218
|
+
|
219
|
+
pytester.makefile(".config.yaml", **{"gltest": config_content})
|
220
|
+
|
221
|
+
result = pytester.runpytest("-v")
|
222
|
+
result.stdout.fnmatch_lines(
|
223
|
+
[
|
224
|
+
"*::test_from_account PASSED*",
|
225
|
+
]
|
226
|
+
)
|
227
|
+
assert result.ret == 0
|
228
|
+
|
229
|
+
|
230
|
+
def test_multiple_networks_config(pytester):
|
231
|
+
"""Test multiple networks configuration."""
|
232
|
+
pytester.makepyfile(
|
233
|
+
"""
|
234
|
+
from gltest_cli.config.general import get_general_config
|
235
|
+
|
236
|
+
def test_multiple_networks():
|
237
|
+
general_config = get_general_config()
|
238
|
+
# Default should be testnet
|
239
|
+
assert general_config.get_network_name() == "testnet"
|
240
|
+
rpc_url = general_config.get_rpc_url()
|
241
|
+
assert rpc_url == "https://testnet.example.com"
|
242
|
+
"""
|
243
|
+
)
|
244
|
+
|
245
|
+
config_content = """
|
246
|
+
networks:
|
247
|
+
default: testnet
|
248
|
+
localnet:
|
249
|
+
id: 61999
|
250
|
+
url: "http://127.0.0.1:4000/api"
|
251
|
+
accounts:
|
252
|
+
- "local_account1"
|
253
|
+
- "local_account2"
|
254
|
+
testnet:
|
255
|
+
id: 5555
|
256
|
+
url: "https://testnet.example.com"
|
257
|
+
accounts:
|
258
|
+
- "testnet_account1"
|
259
|
+
- "testnet_account2"
|
260
|
+
leader_only: true
|
261
|
+
|
262
|
+
paths:
|
263
|
+
contracts: "contracts"
|
264
|
+
|
265
|
+
environment: .env
|
266
|
+
"""
|
267
|
+
|
268
|
+
pytester.makefile(".config.yaml", **{"gltest": config_content})
|
269
|
+
|
270
|
+
result = pytester.runpytest("-v")
|
271
|
+
result.stdout.fnmatch_lines(
|
272
|
+
[
|
273
|
+
"*::test_multiple_networks PASSED*",
|
274
|
+
]
|
275
|
+
)
|
276
|
+
assert result.ret == 0
|
277
|
+
|
278
|
+
|
279
|
+
def test_custom_paths_config(pytester):
|
280
|
+
"""Test custom paths configuration."""
|
281
|
+
pytester.makepyfile(
|
282
|
+
"""
|
283
|
+
from gltest_cli.config.general import get_general_config
|
284
|
+
from pathlib import Path
|
285
|
+
|
286
|
+
def test_custom_paths():
|
287
|
+
general_config = get_general_config()
|
288
|
+
assert general_config.get_contracts_dir() == Path("src/contracts")
|
289
|
+
assert general_config.get_artifacts_dir() == Path("build/artifacts")
|
290
|
+
"""
|
291
|
+
)
|
292
|
+
|
293
|
+
config_content = """
|
294
|
+
networks:
|
295
|
+
default: localnet
|
296
|
+
localnet:
|
297
|
+
url: "http://127.0.0.1:4000/api"
|
298
|
+
|
299
|
+
paths:
|
300
|
+
contracts: "src/contracts"
|
301
|
+
artifacts: "build/artifacts"
|
302
|
+
|
303
|
+
environment: .env
|
304
|
+
"""
|
305
|
+
|
306
|
+
pytester.makefile(".config.yaml", **{"gltest": config_content})
|
307
|
+
|
308
|
+
result = pytester.runpytest("-v")
|
309
|
+
result.stdout.fnmatch_lines(
|
310
|
+
[
|
311
|
+
"*::test_custom_paths PASSED*",
|
312
|
+
]
|
313
|
+
)
|
314
|
+
assert result.ret == 0
|
315
|
+
|
316
|
+
|
317
|
+
def test_custom_environment_file(pytester):
|
318
|
+
"""Test custom environment file configuration."""
|
319
|
+
pytester.makepyfile(
|
320
|
+
"""
|
321
|
+
from gltest_cli.config.general import get_general_config
|
322
|
+
|
323
|
+
def test_custom_environment():
|
324
|
+
general_config = get_general_config()
|
325
|
+
assert general_config.user_config.environment == ".env.test"
|
326
|
+
"""
|
327
|
+
)
|
328
|
+
|
329
|
+
config_content = """
|
330
|
+
networks:
|
331
|
+
default: localnet
|
332
|
+
localnet:
|
333
|
+
url: "http://127.0.0.1:4000/api"
|
334
|
+
|
335
|
+
paths:
|
336
|
+
contracts: "contracts"
|
337
|
+
|
338
|
+
environment: .env.test
|
339
|
+
"""
|
340
|
+
|
341
|
+
pytester.makefile(".config.yaml", **{"gltest": config_content})
|
342
|
+
|
343
|
+
result = pytester.runpytest("-v")
|
344
|
+
result.stdout.fnmatch_lines(
|
345
|
+
[
|
346
|
+
"*::test_custom_environment PASSED*",
|
347
|
+
]
|
348
|
+
)
|
349
|
+
assert result.ret == 0
|
350
|
+
|
351
|
+
|
352
|
+
def test_cli_network_override(pytester):
|
353
|
+
"""Test CLI network override of config file."""
|
354
|
+
pytester.makepyfile(
|
355
|
+
"""
|
356
|
+
from gltest_cli.config.general import get_general_config
|
357
|
+
|
358
|
+
def test_network_override():
|
359
|
+
general_config = get_general_config()
|
360
|
+
assert general_config.get_network_name() == "testnet"
|
361
|
+
rpc_url = general_config.get_rpc_url()
|
362
|
+
assert rpc_url == "https://testnet.example.com"
|
363
|
+
"""
|
364
|
+
)
|
365
|
+
|
366
|
+
config_content = """
|
367
|
+
networks:
|
368
|
+
default: localnet
|
369
|
+
localnet:
|
370
|
+
id: 61999
|
371
|
+
url: "http://127.0.0.1:4000/api"
|
372
|
+
accounts:
|
373
|
+
- "local_account1"
|
374
|
+
testnet:
|
375
|
+
id: 5555
|
376
|
+
url: "https://testnet.example.com"
|
377
|
+
accounts:
|
378
|
+
- "testnet_account1"
|
379
|
+
|
380
|
+
paths:
|
381
|
+
contracts: "contracts"
|
382
|
+
|
383
|
+
environment: .env
|
384
|
+
"""
|
385
|
+
|
386
|
+
pytester.makefile(".config.yaml", **{"gltest": config_content})
|
387
|
+
|
388
|
+
result = pytester.runpytest("--network=testnet", "-v")
|
389
|
+
result.stdout.fnmatch_lines(
|
390
|
+
[
|
391
|
+
"*::test_network_override PASSED*",
|
392
|
+
]
|
393
|
+
)
|
394
|
+
assert result.ret == 0
|
395
|
+
|
396
|
+
|
397
|
+
def test_wait_interval_and_retries_config(pytester):
|
398
|
+
"""Test default wait interval and retries from CLI."""
|
399
|
+
pytester.makepyfile(
|
400
|
+
"""
|
401
|
+
from gltest_cli.config.general import get_general_config
|
402
|
+
|
403
|
+
def test_wait_config():
|
404
|
+
general_config = get_general_config()
|
405
|
+
assert general_config.get_default_wait_interval() == 3000
|
406
|
+
assert general_config.get_default_wait_retries() == 20
|
407
|
+
"""
|
408
|
+
)
|
409
|
+
|
410
|
+
config_content = """
|
411
|
+
networks:
|
412
|
+
default: localnet
|
413
|
+
localnet:
|
414
|
+
url: "http://127.0.0.1:4000/api"
|
415
|
+
|
416
|
+
paths:
|
417
|
+
contracts: "contracts"
|
418
|
+
|
419
|
+
environment: .env
|
420
|
+
"""
|
421
|
+
|
422
|
+
pytester.makefile(".config.yaml", **{"gltest": config_content})
|
423
|
+
|
424
|
+
result = pytester.runpytest(
|
425
|
+
"--default-wait-interval=3000", "--default-wait-retries=20", "-v"
|
426
|
+
)
|
427
|
+
result.stdout.fnmatch_lines(
|
428
|
+
[
|
429
|
+
"*::test_wait_config PASSED*",
|
430
|
+
]
|
431
|
+
)
|
432
|
+
assert result.ret == 0
|
@@ -147,3 +147,260 @@ def test_general_config_contracts_default():
|
|
147
147
|
|
148
148
|
# Should return default contracts directory
|
149
149
|
assert general_config.get_contracts_dir() == DEFAULT_CONTRACTS_DIR
|
150
|
+
|
151
|
+
|
152
|
+
def test_general_config_leader_only_default():
|
153
|
+
"""Test GeneralConfig leader_only with default values."""
|
154
|
+
user_config = UserConfig(
|
155
|
+
networks={"localnet": NetworkConfigData()},
|
156
|
+
)
|
157
|
+
|
158
|
+
plugin_config = PluginConfig()
|
159
|
+
general_config = GeneralConfig(user_config=user_config, plugin_config=plugin_config)
|
160
|
+
|
161
|
+
# Should return False by default
|
162
|
+
assert general_config.get_leader_only() is False
|
163
|
+
|
164
|
+
|
165
|
+
def test_general_config_leader_only_network_config():
|
166
|
+
"""Test GeneralConfig leader_only from network configuration."""
|
167
|
+
user_config = UserConfig(
|
168
|
+
networks={"localnet": NetworkConfigData(leader_only=True)},
|
169
|
+
default_network="localnet",
|
170
|
+
)
|
171
|
+
|
172
|
+
plugin_config = PluginConfig()
|
173
|
+
general_config = GeneralConfig(user_config=user_config, plugin_config=plugin_config)
|
174
|
+
|
175
|
+
# Should return True from network config
|
176
|
+
assert general_config.get_leader_only() is True
|
177
|
+
|
178
|
+
|
179
|
+
def test_general_config_leader_only_plugin_precedence():
|
180
|
+
"""Test that plugin config takes precedence over network config for leader_only."""
|
181
|
+
user_config = UserConfig(
|
182
|
+
networks={"localnet": NetworkConfigData(leader_only=False)},
|
183
|
+
default_network="localnet",
|
184
|
+
)
|
185
|
+
|
186
|
+
plugin_config = PluginConfig(leader_only=True)
|
187
|
+
general_config = GeneralConfig(user_config=user_config, plugin_config=plugin_config)
|
188
|
+
|
189
|
+
# Plugin config should take precedence
|
190
|
+
assert general_config.get_leader_only() is True
|
191
|
+
|
192
|
+
|
193
|
+
def test_general_config_leader_only_multiple_networks():
|
194
|
+
"""Test leader_only with multiple networks."""
|
195
|
+
user_config = UserConfig(
|
196
|
+
networks={
|
197
|
+
"localnet": NetworkConfigData(leader_only=False),
|
198
|
+
"testnet": NetworkConfigData(leader_only=True),
|
199
|
+
},
|
200
|
+
default_network="testnet",
|
201
|
+
)
|
202
|
+
|
203
|
+
plugin_config = PluginConfig()
|
204
|
+
general_config = GeneralConfig(user_config=user_config, plugin_config=plugin_config)
|
205
|
+
|
206
|
+
# Should use the default network's leader_only value
|
207
|
+
assert general_config.get_leader_only() is True
|
208
|
+
|
209
|
+
# Change network via plugin config
|
210
|
+
plugin_config.network_name = "localnet"
|
211
|
+
general_config = GeneralConfig(user_config=user_config, plugin_config=plugin_config)
|
212
|
+
assert general_config.get_leader_only() is False
|
213
|
+
|
214
|
+
|
215
|
+
def test_general_config_leader_only_network_not_found():
|
216
|
+
"""Test leader_only when selected network is not found."""
|
217
|
+
user_config = UserConfig(
|
218
|
+
networks={"localnet": NetworkConfigData(leader_only=True)},
|
219
|
+
default_network="localnet",
|
220
|
+
)
|
221
|
+
|
222
|
+
plugin_config = PluginConfig(network_name="nonexistent")
|
223
|
+
general_config = GeneralConfig(user_config=user_config, plugin_config=plugin_config)
|
224
|
+
|
225
|
+
# Should return False when network is not found
|
226
|
+
assert general_config.get_leader_only() is False
|
227
|
+
|
228
|
+
|
229
|
+
def test_check_local_rpc_with_localhost():
|
230
|
+
"""Test check_local_rpc with localhost URL."""
|
231
|
+
user_config = UserConfig(
|
232
|
+
networks={"localnet": NetworkConfigData(url="http://localhost:8545")},
|
233
|
+
default_network="localnet",
|
234
|
+
)
|
235
|
+
|
236
|
+
plugin_config = PluginConfig()
|
237
|
+
general_config = GeneralConfig(user_config=user_config, plugin_config=plugin_config)
|
238
|
+
|
239
|
+
assert general_config.check_local_rpc() is True
|
240
|
+
|
241
|
+
|
242
|
+
def test_check_local_rpc_with_127_0_0_1():
|
243
|
+
"""Test check_local_rpc with 127.0.0.1 URL."""
|
244
|
+
user_config = UserConfig(
|
245
|
+
networks={"localnet": NetworkConfigData(url="http://127.0.0.1:8545")},
|
246
|
+
default_network="localnet",
|
247
|
+
)
|
248
|
+
|
249
|
+
plugin_config = PluginConfig()
|
250
|
+
general_config = GeneralConfig(user_config=user_config, plugin_config=plugin_config)
|
251
|
+
|
252
|
+
assert general_config.check_local_rpc() is True
|
253
|
+
|
254
|
+
|
255
|
+
def test_check_local_rpc_with_external_url():
|
256
|
+
"""Test check_local_rpc with external URL."""
|
257
|
+
user_config = UserConfig(
|
258
|
+
networks={"testnet": NetworkConfigData(url="https://api.genlayer.com:8545")},
|
259
|
+
default_network="testnet",
|
260
|
+
)
|
261
|
+
|
262
|
+
plugin_config = PluginConfig()
|
263
|
+
general_config = GeneralConfig(user_config=user_config, plugin_config=plugin_config)
|
264
|
+
|
265
|
+
assert general_config.check_local_rpc() is False
|
266
|
+
|
267
|
+
|
268
|
+
def test_check_local_rpc_with_plugin_override():
|
269
|
+
"""Test check_local_rpc with plugin config RPC URL override."""
|
270
|
+
user_config = UserConfig(
|
271
|
+
networks={"localnet": NetworkConfigData(url="https://external.com")},
|
272
|
+
default_network="localnet",
|
273
|
+
)
|
274
|
+
|
275
|
+
plugin_config = PluginConfig(rpc_url="http://localhost:9000")
|
276
|
+
general_config = GeneralConfig(user_config=user_config, plugin_config=plugin_config)
|
277
|
+
|
278
|
+
# Plugin config should take precedence
|
279
|
+
assert general_config.check_local_rpc() is True
|
280
|
+
|
281
|
+
|
282
|
+
def test_check_studio_based_rpc_with_localhost():
|
283
|
+
"""Test check_studio_based_rpc with localhost URL."""
|
284
|
+
user_config = UserConfig(
|
285
|
+
networks={"localnet": NetworkConfigData(url="http://localhost:8545")},
|
286
|
+
default_network="localnet",
|
287
|
+
)
|
288
|
+
|
289
|
+
plugin_config = PluginConfig()
|
290
|
+
general_config = GeneralConfig(user_config=user_config, plugin_config=plugin_config)
|
291
|
+
|
292
|
+
assert general_config.check_studio_based_rpc() is True
|
293
|
+
|
294
|
+
|
295
|
+
def test_check_studio_based_rpc_with_127_0_0_1():
|
296
|
+
"""Test check_studio_based_rpc with 127.0.0.1 URL."""
|
297
|
+
user_config = UserConfig(
|
298
|
+
networks={"localnet": NetworkConfigData(url="http://127.0.0.1:8545")},
|
299
|
+
default_network="localnet",
|
300
|
+
)
|
301
|
+
|
302
|
+
plugin_config = PluginConfig()
|
303
|
+
general_config = GeneralConfig(user_config=user_config, plugin_config=plugin_config)
|
304
|
+
|
305
|
+
assert general_config.check_studio_based_rpc() is True
|
306
|
+
|
307
|
+
|
308
|
+
def test_check_studio_based_rpc_with_genlayer_subdomain():
|
309
|
+
"""Test check_studio_based_rpc with .genlayer.com subdomains."""
|
310
|
+
test_cases = [
|
311
|
+
"https://api.genlayer.com:8545",
|
312
|
+
"https://test.genlayer.com",
|
313
|
+
"http://staging.api.genlayer.com:9000",
|
314
|
+
"https://dev.test.genlayer.com",
|
315
|
+
]
|
316
|
+
|
317
|
+
for url in test_cases:
|
318
|
+
user_config = UserConfig(
|
319
|
+
networks={"testnet": NetworkConfigData(url=url)},
|
320
|
+
default_network="testnet",
|
321
|
+
)
|
322
|
+
|
323
|
+
plugin_config = PluginConfig()
|
324
|
+
general_config = GeneralConfig(
|
325
|
+
user_config=user_config, plugin_config=plugin_config
|
326
|
+
)
|
327
|
+
|
328
|
+
assert general_config.check_studio_based_rpc() is True, f"Failed for URL: {url}"
|
329
|
+
|
330
|
+
|
331
|
+
def test_check_studio_based_rpc_with_genlayerlabs_subdomain():
|
332
|
+
"""Test check_studio_based_rpc with .genlayerlabs.com subdomains."""
|
333
|
+
test_cases = [
|
334
|
+
"https://api.genlayerlabs.com:8545",
|
335
|
+
"https://test.genlayerlabs.com",
|
336
|
+
"http://staging.api.genlayerlabs.com:9000",
|
337
|
+
"https://dev.test.genlayerlabs.com",
|
338
|
+
]
|
339
|
+
|
340
|
+
for url in test_cases:
|
341
|
+
user_config = UserConfig(
|
342
|
+
networks={"testnet": NetworkConfigData(url=url)},
|
343
|
+
default_network="testnet",
|
344
|
+
)
|
345
|
+
|
346
|
+
plugin_config = PluginConfig()
|
347
|
+
general_config = GeneralConfig(
|
348
|
+
user_config=user_config, plugin_config=plugin_config
|
349
|
+
)
|
350
|
+
|
351
|
+
assert general_config.check_studio_based_rpc() is True, f"Failed for URL: {url}"
|
352
|
+
|
353
|
+
|
354
|
+
def test_check_studio_based_rpc_with_non_genlayer_domain():
|
355
|
+
"""Test check_studio_based_rpc with non-GenLayer domains."""
|
356
|
+
test_cases = [
|
357
|
+
"https://api.example.com:8545",
|
358
|
+
"https://test.otherdomain.com",
|
359
|
+
"http://staging.api.random.org:9000",
|
360
|
+
"https://genlayer.example.com", # Not a subdomain of .genlayer.com
|
361
|
+
"https://genlayerlabs.example.com", # Not a subdomain of .genlayerlabs.com
|
362
|
+
]
|
363
|
+
|
364
|
+
for url in test_cases:
|
365
|
+
user_config = UserConfig(
|
366
|
+
networks={"testnet": NetworkConfigData(url=url)},
|
367
|
+
default_network="testnet",
|
368
|
+
)
|
369
|
+
|
370
|
+
plugin_config = PluginConfig()
|
371
|
+
general_config = GeneralConfig(
|
372
|
+
user_config=user_config, plugin_config=plugin_config
|
373
|
+
)
|
374
|
+
|
375
|
+
assert (
|
376
|
+
general_config.check_studio_based_rpc() is False
|
377
|
+
), f"Failed for URL: {url}"
|
378
|
+
|
379
|
+
|
380
|
+
def test_check_studio_based_rpc_with_plugin_override():
|
381
|
+
"""Test check_studio_based_rpc with plugin config RPC URL override."""
|
382
|
+
# User config has external URL, but plugin overrides with GenLayer domain
|
383
|
+
user_config = UserConfig(
|
384
|
+
networks={"localnet": NetworkConfigData(url="https://external.com")},
|
385
|
+
default_network="localnet",
|
386
|
+
)
|
387
|
+
|
388
|
+
plugin_config = PluginConfig(rpc_url="https://api.genlayer.com:9000")
|
389
|
+
general_config = GeneralConfig(user_config=user_config, plugin_config=plugin_config)
|
390
|
+
|
391
|
+
# Plugin config should take precedence
|
392
|
+
assert general_config.check_studio_based_rpc() is True
|
393
|
+
|
394
|
+
# Test opposite case: user has GenLayer domain, plugin overrides with external
|
395
|
+
user_config2 = UserConfig(
|
396
|
+
networks={"localnet": NetworkConfigData(url="https://api.genlayer.com")},
|
397
|
+
default_network="localnet",
|
398
|
+
)
|
399
|
+
|
400
|
+
plugin_config2 = PluginConfig(rpc_url="https://external.com:9000")
|
401
|
+
general_config2 = GeneralConfig(
|
402
|
+
user_config=user_config2, plugin_config=plugin_config2
|
403
|
+
)
|
404
|
+
|
405
|
+
# Plugin config should take precedence
|
406
|
+
assert general_config2.check_studio_based_rpc() is False
|
@@ -17,6 +17,8 @@ def test_help_message(pytester):
|
|
17
17
|
" --rpc-url=RPC_URL RPC endpoint URL for the GenLayer network",
|
18
18
|
" --network=NETWORK Target network (defaults to 'localnet' if no config",
|
19
19
|
" file)",
|
20
|
+
" --test-with-mocks Test with mocks",
|
21
|
+
" --leader-only Run contracts in leader-only mode",
|
20
22
|
]
|
21
23
|
)
|
22
24
|
|
@@ -178,6 +180,50 @@ def test_contracts_and_artifacts_dirs(pytester):
|
|
178
180
|
assert result.ret == 0
|
179
181
|
|
180
182
|
|
183
|
+
def test_test_with_mocks_true(pytester):
|
184
|
+
pytester.makepyfile(
|
185
|
+
"""
|
186
|
+
from gltest_cli.config.general import get_general_config
|
187
|
+
|
188
|
+
def test_test_with_mocks():
|
189
|
+
general_config = get_general_config()
|
190
|
+
assert general_config.get_test_with_mocks() == True
|
191
|
+
"""
|
192
|
+
)
|
193
|
+
|
194
|
+
result = pytester.runpytest("--test-with-mocks", "-v")
|
195
|
+
|
196
|
+
result.stdout.fnmatch_lines(
|
197
|
+
[
|
198
|
+
"*::test_test_with_mocks PASSED*",
|
199
|
+
]
|
200
|
+
)
|
201
|
+
assert result.ret == 0
|
202
|
+
|
203
|
+
|
204
|
+
def test_test_with_mocks_false(pytester):
|
205
|
+
pytester.makepyfile(
|
206
|
+
"""
|
207
|
+
from gltest_cli.config.general import get_general_config
|
208
|
+
|
209
|
+
def test_test_with_mocks():
|
210
|
+
general_config = get_general_config()
|
211
|
+
assert general_config.get_test_with_mocks() == False
|
212
|
+
"*::test_test_with_mocks PASSED*",
|
213
|
+
|
214
|
+
"""
|
215
|
+
)
|
216
|
+
|
217
|
+
result = pytester.runpytest("-v")
|
218
|
+
|
219
|
+
result.stdout.fnmatch_lines(
|
220
|
+
[
|
221
|
+
"*::test_test_with_mocks PASSED*",
|
222
|
+
]
|
223
|
+
)
|
224
|
+
assert result.ret == 0
|
225
|
+
|
226
|
+
|
181
227
|
def test_artifacts_dir_default_fallback(pytester):
|
182
228
|
"""Test that artifacts directory falls back to config file default when CLI not provided."""
|
183
229
|
pytester.makepyfile(
|
@@ -192,6 +238,7 @@ def test_artifacts_dir_default_fallback(pytester):
|
|
192
238
|
assert isinstance(artifacts_dir, Path)
|
193
239
|
# Default should be 'artifacts'
|
194
240
|
assert str(artifacts_dir) == "artifacts"
|
241
|
+
|
195
242
|
"""
|
196
243
|
)
|
197
244
|
|
@@ -203,3 +250,45 @@ def test_artifacts_dir_default_fallback(pytester):
|
|
203
250
|
]
|
204
251
|
)
|
205
252
|
assert result.ret == 0
|
253
|
+
|
254
|
+
|
255
|
+
def test_leader_only_true(pytester):
|
256
|
+
pytester.makepyfile(
|
257
|
+
"""
|
258
|
+
from gltest_cli.config.general import get_general_config
|
259
|
+
|
260
|
+
def test_leader_only():
|
261
|
+
general_config = get_general_config()
|
262
|
+
assert general_config.get_leader_only() == True
|
263
|
+
"""
|
264
|
+
)
|
265
|
+
|
266
|
+
result = pytester.runpytest("--leader-only", "-v")
|
267
|
+
|
268
|
+
result.stdout.fnmatch_lines(
|
269
|
+
[
|
270
|
+
"*::test_leader_only PASSED*",
|
271
|
+
]
|
272
|
+
)
|
273
|
+
assert result.ret == 0
|
274
|
+
|
275
|
+
|
276
|
+
def test_leader_only_false(pytester):
|
277
|
+
pytester.makepyfile(
|
278
|
+
"""
|
279
|
+
from gltest_cli.config.general import get_general_config
|
280
|
+
|
281
|
+
def test_leader_only():
|
282
|
+
general_config = get_general_config()
|
283
|
+
assert general_config.get_leader_only() == False
|
284
|
+
"""
|
285
|
+
)
|
286
|
+
|
287
|
+
result = pytester.runpytest("-v")
|
288
|
+
|
289
|
+
result.stdout.fnmatch_lines(
|
290
|
+
[
|
291
|
+
"*::test_leader_only PASSED*",
|
292
|
+
]
|
293
|
+
)
|
294
|
+
assert result.ret == 0
|
@@ -137,6 +137,11 @@ def test_validate_raw_user_config_invalid():
|
|
137
137
|
{"networks": {"default": "localnet", "localnet": {"from": 123}}}
|
138
138
|
)
|
139
139
|
|
140
|
+
with pytest.raises(ValueError, match="leader_only must be a boolean"):
|
141
|
+
validate_raw_user_config(
|
142
|
+
{"networks": {"default": "localnet", "localnet": {"leader_only": "true"}}}
|
143
|
+
)
|
144
|
+
|
140
145
|
with pytest.raises(ValueError, match="paths must be a dictionary"):
|
141
146
|
validate_raw_user_config({"paths": "not_a_dict"})
|
142
147
|
|
@@ -182,6 +187,11 @@ def test_validate_raw_user_config_invalid():
|
|
182
187
|
{"networks": {"default": "localnet", "testnet": {"from": 123}}}
|
183
188
|
)
|
184
189
|
|
190
|
+
with pytest.raises(ValueError, match="leader_only must be a boolean"):
|
191
|
+
validate_raw_user_config(
|
192
|
+
{"networks": {"default": "localnet", "testnet": {"leader_only": "true"}}}
|
193
|
+
)
|
194
|
+
|
185
195
|
# Test required fields for non-default networks
|
186
196
|
with pytest.raises(ValueError, match="network testnet must have an id"):
|
187
197
|
validate_raw_user_config(
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|