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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: genlayer-test
3
- Version: 1.0.0
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.1
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
- └── test/ # Test files
255
- └── test_contract.py # Contract test cases
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.0.0.dist-info/licenses/LICENSE,sha256=che_H4vE0QUx3HvWrAa1_jDEVInift0U6VO15-QqEls,1064
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=NGgciplaYINL_unJvLnOSWL4f8cNlHGMOEEbc6a1kw4,6402
14
- gltest/contracts/contract_factory.py,sha256=jjzxgsLW6AiQ4h8LcZwIzdq5Uqm-xkj_JZNhN0djijc,6691
15
- gltest/contracts/contract_functions.py,sha256=E-C9RnBkz3FeJRiswUaZbcDn4HRSWitsdwegSjX7E_Q,2089
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=gMK1xrkJ4G0ddCjKAFB7lEXecdvoZiwndwwI4Srn66w,4857
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=w7KyINjM3_n4MYoRPfMBgudptjXjnSKPficAsqqSUTY,6344
30
- gltest_cli/config/user.py,sha256=VSS7llCUJc2GSIexdL73FqkLthdiECwmQONO3-WYi7o,8242
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=0Lm2x2F1DhmUP1fcfzGQAfc50tWFcaHliwyAzXIRFVw,3247
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/test_general_config.py,sha256=T2haLj41-6tbEfybdXnEycIQQFNkdc_O07qmg_37dqM,5730
69
- tests/gltest_cli/config/test_plugin.py,sha256=GYY56VR39-uI2WXK-rlzTxB5Ui2h9f5oF_kO0iNljRo,5862
70
- tests/gltest_cli/config/test_user.py,sha256=40nEC-gM03Q86SnmJpkGfMRvKKtEkfx53qaGHj8XXGQ,13988
71
- genlayer_test-1.0.0.dist-info/METADATA,sha256=g1QNA8KP65HqSApiaNYcunHwz1Ch2IO_RIkFcbVk5Vo,24143
72
- genlayer_test-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
73
- genlayer_test-1.0.0.dist-info/entry_points.txt,sha256=RWPcSArBpz_G4BYioh5L8Q8hyClRbSgzLimjcWMp-BQ,94
74
- genlayer_test-1.0.0.dist-info/top_level.txt,sha256=-qiGZxTRBytujzgVcKpxjvQ-WNeUDjXa59ceGMwMpko,24
75
- genlayer_test-1.0.0.dist-info/RECORD,,
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,,
@@ -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,
@@ -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
@@ -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
 
@@ -97,4 +97,4 @@ This result should be perfectly parsable by a JSON parser without errors.
97
97
  "winner": self.winner,
98
98
  "score": self.score,
99
99
  "has_resolved": self.has_resolved,
100
- }
100
+ }
@@ -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(