Skip to content

Conversation

@danieljrc888
Copy link
Contributor

@danieljrc888 danieljrc888 commented Sep 11, 2025

Fixes DXP-655

What

  • Implemented mocked web requests

Summary by CodeRabbit

  • New Features

    • Support for mocking external web requests alongside existing LLM mocks, with validator helpers to create mock validators.
    • Example contract demonstrating fetching and storing a username via mocked web calls.
  • Tests

    • End-to-end tests exercising deployment, transactions, state updates, and read-after-write with mocked web responses.
    • Integration tests for wait-interval/retries configuration behavior.
  • Documentation

    • Expanded README section covering mock web responses, examples, and best practices.
  • Chores

    • CLI defaults for wait interval/retries made optional.

@danieljrc888 danieljrc888 self-assigned this Sep 11, 2025
@coderabbitai
Copy link

coderabbitai bot commented Sep 11, 2025

Walkthrough

Adds typed TypedDicts for mocking HTTP responses, extends Validator and ValidatorFactory to carry and emit web mock configuration alongside LLM mocks, introduces an example contract that performs gl.nondet.web.get calls, adds tests exercising that contract with URL-keyed mocked web responses, and documents the feature in README.

Changes

Cohort / File(s) Summary of changes
Web mock types
gltest/types.py
Added TypedDicts: MockedWebResponseData (method: str, status: int, body: str) and MockedWebResponse (nondet_web_request: Dict[str, MockedWebResponseData]). Inserted before ValidatorConfig.
Validator + factory updates
gltest/validators/validator_factory.py
Imported MockedWebResponse. Validator dataclass adds mock_web_response: Optional[MockedWebResponse]. to_dict now emits plugin configs for both LLM mocks (mock_response) and web mocks (mock_web_response) under plugin_config. clone deep-copies mock_web_response. Added create_mock_validator(..., mock_web_response=...) and batch_create_mock_validators(..., mock_web_response=...). create_validator still passes mock_web_response=None.
Example contract using web.get
tests/examples/contracts/x_username_storage.py
New XUsernameStorage contract with username and tweet_api_url. Implements get_username() and update_username(username) which calls request_to_x that builds a URL, calls gl.nondet.web.get(url), checks status/body, parses JSON, and stores the username wrapped with eq_principle.strict_eq.
Tests for web-mocked contract
tests/examples/tests/test_x_username_storage.py
New test: constructs MockedWebResponse entries for two users, creates mock validators, deploys the contract with transaction_context, performs two update transactions hitting mocked URLs, and asserts stored username after each update.
CLI plugin config defaults
gltest_cli/config/plugin.py
CLI options --default-wait-interval and --default-wait-retries now default to None; assignment to PluginConfig is None-guarded and converted to int only if provided. Removed imports of default-constant references.
Config integration tests
tests/gltest_cli/config/test_config_integration.py
Added three integration tests covering wait-retries/default-interval behavior for network config, CLI overrides, and defaults when unset.
Docs: README
README.md
Added a "Mock Web Responses" section describing mock format, supported methods, behavior, examples (including the X username storage example). Content appears duplicated in two locations.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Tester
  participant VF as ValidatorFactory
  participant V as Validator
  participant C as XUsernameStorage
  participant MW as MockStore

  Tester->>VF: batch_create_mock_validators(count, mock_web_response)
  VF->>V: create_mock_validator(mock_web_response)
  Note right of V: Validator.mock_web_response populated

  Tester->>C: deploy(tx_context with Validators)
  Tester->>C: update_username("user_a").transact(ctx)
  C->>MW: web.get(built_url)
  Note over MW,C: Lookup by exact URL key and method in nondet_web_request
  MW-->>C: {status:200, body: "{\"username\":\"user_a\"}"}
  C->>C: parse JSON → set self.username (wrapped with strict_eq)
  Tester->>C: get_username().call(ctx)
  C-->>Tester: "user_a"
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas to focus review on:

  • gltest/validators/validator_factory.py: correctness of mock_web_response handling in to_dict, cloning, and new factory helpers.
  • gltest/types.py: TypedDict definitions and export/order effects.
  • tests/examples/contracts/x_username_storage.py and tests/.../test_x_username_storage.py: URL construction, nondet web semantics, error handling, and deterministic wrapping.
  • README additions for duplication and accuracy.

Possibly related PRs

Suggested reviewers

  • cristiam86
  • MuncleUscles

Poem

I twitch my nose — mock maps in paw,
URLs aligned, obey the law.
I hop through tests with hopping glee,
Web replies return — username sets free.
Carrot cheers for deterministic victory! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: implement mock web request' accurately and specifically describes the main change: implementing mock web request functionality across the codebase.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dxp-655-mock-web-request-in-genlayer-test

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (7)
gltest/types.py (1)

26-28: Optional: constrain HTTP method values.

Consider narrowing method: str to a Literal[...] for basic validation at type-check time.

Example:

-from typing import List, TypedDict, Dict, Any, Union
+from typing import List, TypedDict, Dict, Any, Union, Literal
@@
-    method: str  # GET, POST, PUT, DELETE, etc.
+    method: Literal["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]
tests/examples/contracts/x_username_storage.py (3)

8-8: Avoid star import; import the namespace you use.

Use from genlayer import gl to satisfy linters and keep explicit symbols.

Apply:

-from genlayer import *
+from genlayer import gl

28-33: Return type mismatch: function annotated to return str but returns None.

Either change the return annotation to -> None or return the updated username.

Apply:

     def update_username(self, username: str) -> str:
         user_data = self.request_to_x(
             f"users/by/username/{username}", {"user.fields": "public_metrics,verified"}
         )
         self.username = user_data["username"]
+        return self.username

38-41: Safer URL join to avoid accidental double slashes.

Normalize slashes on both sides.

Apply:

-        base_url = f"{proxy_url}/{endpoint}"
+        base_url = f"{proxy_url.rstrip('/')}/{endpoint.lstrip('/')}"
 
-        url = f"{base_url}?{urllib.parse.urlencode(params)}"
+        url = f"{base_url}?{urllib.parse.urlencode(params)}"
gltest/validators/validator_factory.py (3)

33-47: Make mock config merge consistent and avoid returning references.
In the non-mock path you deepcopy plugin_config, but in the mock path you rebuild from self.plugin_config (not a deepcopy). This reintroduces references and breaks to_dict’s implicit copy contract.

Apply:

-        # Mock llm response
-        mock_llm = self.mock_llm_response or {}
+        # Mock llm response
+        mock_llm = self.mock_llm_response or {}
         mock_llm_config = {
             "response": mock_llm.get("nondet_exec_prompt", {}),
             "eq_principle_prompt_comparative": mock_llm.get(
                 "eq_principle_prompt_comparative", {}
             ),
             "eq_principle_prompt_non_comparative": mock_llm.get(
                 "eq_principle_prompt_non_comparative", {}
             ),
         }
-        mock_web = self.mock_web_request or {}
-        mock_web_config = {
-            "nondet_web_request": mock_web.get("nondet_web_request", {}),
-        }
+        mock_web = self.mock_web_request or {}
+        mock_web_config = {
+            "nondet_web_request": mock_web.get("nondet_web_request", {}),
+        }

Optionally omit empty sections to keep plugin_config minimal:

+        plugin_cfg = deepcopy(self.plugin_config)
+        if any(mock_llm_config.values()):
+            plugin_cfg["mock_response"] = mock_llm_config
+        if mock_web_config["nondet_web_request"]:
+            plugin_cfg["mock_web_request"] = mock_web_config

50-54: Use the deep-copied base when composing plugin_config.
Build from a deepcopy (or the already-built normal_config copy) to avoid aliasing and keep behavior consistent with the non-mock path.

-        return {
-            **normal_config,
-            "plugin_config": {
-                **self.plugin_config,
-                "mock_response": mock_llm_config,
-                "mock_web_request": mock_web_config,
-            },
-        }
+        return {
+            **normal_config,
+            "plugin_config": plugin_cfg,
+        }

121-129: Tighten the guard and satisfy Ruff TRY003.
Inline the message to avoid multi-line exception string and keep it concise.

-        if mock_llm_response is None and mock_web_request is None:
-            raise ValueError(
-                "mock_llm_response and mock_web_request cannot both be None"
-            )
+        if mock_llm_response is None and mock_web_request is None:
+            raise ValueError("At least one of mock_llm_response or mock_web_request must be provided")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e69a3c5 and 1934a73.

📒 Files selected for processing (4)
  • gltest/types.py (1 hunks)
  • gltest/validators/validator_factory.py (7 hunks)
  • tests/examples/contracts/x_username_storage.py (1 hunks)
  • tests/examples/tests/test_x_username_storage.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
tests/examples/tests/test_x_username_storage.py (6)
gltest/contracts/contract_factory.py (2)
  • get_contract_factory (216-239)
  • deploy (108-142)
gltest/assertions.py (1)
  • tx_execution_succeeded (6-51)
gltest/validators/validator_factory.py (2)
  • get_validator_factory (161-162)
  • batch_create_mock_validators (149-158)
gltest/types.py (1)
  • MockedWebRequest (31-34)
tests/examples/contracts/x_username_storage.py (2)
  • get_username (24-25)
  • update_username (28-32)
gltest/contracts/contract_functions.py (2)
  • call (14-24)
  • transact (26-48)
tests/examples/contracts/x_username_storage.py (1)
gltest/contracts/contract.py (1)
  • Contract (178-229)
gltest/validators/validator_factory.py (1)
gltest/types.py (2)
  • MockedLLMResponse (12-20)
  • MockedWebRequest (31-34)
🪛 Ruff (0.12.2)
tests/examples/contracts/x_username_storage.py

8-8: from genlayer import * used; unable to detect undefined names

(F403)


15-15: gl may be undefined, or defined from star imports

(F405)


23-23: gl may be undefined, or defined from star imports

(F405)


27-27: gl may be undefined, or defined from star imports

(F405)


44-44: gl may be undefined, or defined from star imports

(F405)


48-50: Avoid specifying long messages outside the exception class

(TRY003)


56-56: gl may be undefined, or defined from star imports

(F405)

gltest/validators/validator_factory.py

127-129: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (8)
tests/examples/tests/test_x_username_storage.py (1)

29-55: Nice test flow and context wiring.

End-to-end deploy → transact → verify reads looks solid and exercises the new mock-web path.

gltest/validators/validator_factory.py (7)

1-1: Import addition looks good.
Type import for MockedWebRequest is correct and scoped appropriately.


19-19: Dataclass extended safely with web-mock field.
Optional typing aligns with factory defaults and clone behavior below.


70-71: Clone covers new field.
Deepcopy on mock_web_request prevents shared-state bugs across clones.


96-97: Factory default remains backward-compatible.
Non-mock validators initialize mock_web_request=None as expected.


141-147: Good: defensive copies of provided mocks.
Prevents accidental cross-test mutations in batch creation.


152-158: Batch helper is correct and delegates appropriately.
Signature parity with create_mock_validator is nice.


33-47: Confirm expected plugin keys — verified.
Downstream code uses plugin_config.mock_response.response and plugin_config.mock_web_request.nondet_web_request (e.g. tests/examples/tests/test_llm_erc20.py:15, tests/examples/tests/test_x_username_storage.py:15, gltest/fixtures.py:72).

@danieljrc888 danieljrc888 force-pushed the dxp-655-mock-web-request-in-genlayer-test branch from 1934a73 to 90d3f0a Compare September 15, 2025 11:04
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
tests/examples/contracts/x_username_storage.py (1)

52-54: Robustly handle body as bytes or str.

Same as the earlier suggestion; apply to avoid .decode on str.

-            web_data = web_data_res.body.decode("utf-8")
-
-            return json.loads(web_data)
+            body = web_data_res.body
+            if isinstance(body, (bytes, bytearray)):
+                web_text = body.decode("utf-8")
+            else:
+                web_text = str(body)
+            return json.loads(web_text)
🧹 Nitpick comments (7)
tests/examples/contracts/x_username_storage.py (4)

8-8: Avoid star import; import gl explicitly.

Prevents F403/F405 and clarifies the dependency.

-from genlayer import *
+from genlayer import gl

37-41: Harden URL join and query encoding.

Avoid double slashes and support list params with doseq.

-        proxy_url = self.tweet_api_url
-        base_url = f"{proxy_url}/{endpoint}"
-
-        url = f"{base_url}?{urllib.parse.urlencode(params)}"
+        proxy_url = self.tweet_api_url
+        base_url = "/".join([proxy_url.rstrip("/"), endpoint.lstrip("/")])
+        query = urllib.parse.urlencode(params, doseq=True)
+        url = f"{base_url}?{query}"

43-46: Prefer logging over print for library/example code.

Keeps output controllable in tests and downstream apps.


47-51: Shorten exception message and avoid dumping full body (TRY003, leakage).

Return a concise message; details are already printed/logged.

-            if web_data_res.status != 200 or not web_data_res.body:
-                raise ValueError(
-                    f"Failed to fetch data from X API: {web_data_res.body}"
-                )
+            if web_data_res.status != 200 or not web_data_res.body:
+                raise ValueError("X API request failed")
gltest/validators/validator_factory.py (3)

44-55: Only include mock blocks when provided to reduce config noise.

Avoid sending empty dicts that may toggle plugin behavior.

-        mock_web = self.mock_web_response or {}
-        mock_web_config = {
-            "nondet_web_request": mock_web.get("nondet_web_request", {}),
-        }
-        return {
-            **normal_config,
-            "plugin_config": {
-                **self.plugin_config,
-                "mock_response": mock_llm_config,
-                "mock_web_response": mock_web_config,
-            },
-        }
+        mock_web = self.mock_web_response or {}
+        mock_web_config = {
+            "nondet_web_request": mock_web.get("nondet_web_request", {}),
+        }
+        plugin_cfg = {**self.plugin_config}
+        if mock_llm:
+            plugin_cfg["mock_response"] = mock_llm_config
+        if mock_web:
+            plugin_cfg["mock_web_response"] = mock_web_config
+        return {**normal_config, "plugin_config": plugin_cfg}

126-129: Tighten the ValueError message (TRY003).

Keep it short and actionable.

-        if mock_llm_response is None and mock_web_response is None:
-            raise ValueError(
-                "mock_llm_response and mock_web_response cannot both be None"
-            )
+        if mock_llm_response is None and mock_web_response is None:
+            raise ValueError("At least one mock is required")

155-158: Use keywords to guard against positional arg drift.

Safer if the signature changes.

-        return [
-            self.create_mock_validator(mock_llm_response, mock_web_response)
-            for _ in range(count)
-        ]
+        return [
+            self.create_mock_validator(
+                mock_llm_response=mock_llm_response,
+                mock_web_response=mock_web_response,
+            )
+            for _ in range(count)
+        ]
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1934a73 and 90d3f0a.

📒 Files selected for processing (4)
  • gltest/types.py (1 hunks)
  • gltest/validators/validator_factory.py (7 hunks)
  • tests/examples/contracts/x_username_storage.py (1 hunks)
  • tests/examples/tests/test_x_username_storage.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/examples/tests/test_x_username_storage.py
  • gltest/types.py
🧰 Additional context used
🧬 Code graph analysis (2)
gltest/validators/validator_factory.py (1)
gltest/types.py (2)
  • MockedLLMResponse (12-20)
  • MockedWebResponse (31-34)
tests/examples/contracts/x_username_storage.py (1)
gltest/contracts/contract.py (1)
  • Contract (178-229)
🪛 Ruff (0.12.2)
gltest/validators/validator_factory.py

127-129: Avoid specifying long messages outside the exception class

(TRY003)

tests/examples/contracts/x_username_storage.py

8-8: from genlayer import * used; unable to detect undefined names

(F403)


15-15: gl may be undefined, or defined from star imports

(F405)


23-23: gl may be undefined, or defined from star imports

(F405)


27-27: gl may be undefined, or defined from star imports

(F405)


44-44: gl may be undefined, or defined from star imports

(F405)


48-50: Avoid specifying long messages outside the exception class

(TRY003)


56-56: gl may be undefined, or defined from star imports

(F405)

🔇 Additional comments (1)
tests/examples/contracts/x_username_storage.py (1)

56-56: Verify strict_eq executes the passed callable and returns its result.

Two call sites pass callables: tests/examples/contracts/x_username_storage.py:56 and tests/examples/contracts/football_prediction_market.py:85. The strict_eq implementation is not in this repo (external dependency), so confirm gl.eq_principle.strict_eq invokes the callable and returns its value; if it expects a value instead, change the calls to pass call_x_api() / get_match_result() (i.e., call the function before passing).

@danieljrc888 danieljrc888 force-pushed the dxp-655-mock-web-request-in-genlayer-test branch from 90d3f0a to d87a519 Compare September 15, 2025 11:16
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
tests/examples/contracts/x_username_storage.py (1)

52-55: Robustly handle body as bytes or str before JSON parse.
This mirrors prior feedback and avoids .decode() on an existing str.

Apply:

-            web_data = web_data_res.body.decode("utf-8")
-
-            return json.loads(web_data)
+            body = web_data_res.body
+            if isinstance(body, (bytes, bytearray)):
+                web_text = body.decode("utf-8")
+            else:
+                web_text = str(body)
+            return json.loads(web_text)
🧹 Nitpick comments (3)
tests/examples/contracts/x_username_storage.py (3)

37-41: Build the URL robustly (no double slashes; support empty or list-valued query).
Current string concatenation can produce // and doesn’t handle list params.

Apply:

-        proxy_url = self.tweet_api_url
-        base_url = f"{proxy_url}/{endpoint}"
-
-        url = f"{base_url}?{urllib.parse.urlencode(params)}"
+        base_url = urllib.parse.urljoin(self.tweet_api_url.rstrip("/") + "/", endpoint.lstrip("/"))
+        query = urllib.parse.urlencode(params, doseq=True)
+        url = f"{base_url}?{query}" if query else base_url

42-46: Use logging instead of prints (cleaner tests, controllable verbosity).

Apply:

-            print(f"Requesting {url}")
+            logging.debug("Requesting %s", url)
             web_data_res = gl.nondet.web.get(url)
-            print(f"Response status: {web_data_res.status}")
+            logging.debug("Response status: %s", web_data_res.status)

Also add:

import logging

near the other imports.


47-51: Don’t dump full response bodies in exceptions; include status and a short preview.
Prevents noisy logs and accidental leakage while keeping failures diagnosable.

Apply:

-            if web_data_res.status != 200 or not web_data_res.body:
-                raise ValueError(
-                    f"Failed to fetch data from X API: {web_data_res.body}"
-                )
+            if web_data_res.status != 200 or not web_data_res.body:
+                body_preview = web_data_res.body
+                if isinstance(body_preview, (bytes, bytearray)):
+                    body_preview = body_preview[:256]
+                    try:
+                        body_preview = body_preview.decode("utf-8", errors="replace")
+                    except Exception:
+                        body_preview = str(body_preview)
+                else:
+                    body_preview = str(body_preview)[:256]
+                raise ValueError(f"X API request failed (status={web_data_res.status}). body[:256]={body_preview!r}")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 90d3f0a and d87a519.

📒 Files selected for processing (2)
  • tests/examples/contracts/x_username_storage.py (1 hunks)
  • tests/examples/tests/test_x_username_storage.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/examples/tests/test_x_username_storage.py
🧰 Additional context used
🧬 Code graph analysis (1)
tests/examples/contracts/x_username_storage.py (1)
gltest/contracts/contract.py (1)
  • Contract (178-229)
🪛 Ruff (0.12.2)
tests/examples/contracts/x_username_storage.py

8-8: from genlayer import * used; unable to detect undefined names

(F403)


15-15: gl may be undefined, or defined from star imports

(F405)


23-23: gl may be undefined, or defined from star imports

(F405)


27-27: gl may be undefined, or defined from star imports

(F405)


44-44: gl may be undefined, or defined from star imports

(F405)


48-50: Avoid specifying long messages outside the exception class

(TRY003)


56-56: gl may be undefined, or defined from star imports

(F405)

🔇 Additional comments (2)
tests/examples/contracts/x_username_storage.py (2)

56-56: Good use of gl.eq_principle.strict_eq to stabilize nondeterminism around the web call.


8-8: Convert from genlayer import *import genlayer as gl — but apply consistently across the repo (don’t change only this file).

rg found these occurrences:

  • tests/gltest/artifact/contracts/duplicate_ic_contract_1.py:3
  • tests/gltest/artifact/contracts/not_ic_contract.py:3
  • tests/gltest/artifact/contracts/duplicate_ic_contract_2.py:3
  • tests/examples/contracts/football_prediction_market.py:4
  • tests/examples/contracts/storage.py:4
  • tests/examples/contracts/x_username_storage.py:8
  • tests/examples/contracts/wizard_of_coin.py:3
  • tests/examples/contracts/user_storage.py:4
  • tests/examples/contracts/simple_time_contract.py:9
  • tests/examples/contracts/read_erc20.py:4
  • tests/examples/contracts/multi_tenant_storage.py:4
  • tests/examples/contracts/multi_read_erc20.py:4
  • tests/examples/contracts/llm_erc20.py:6
  • tests/examples/contracts/intelligent_oracle_factory.py:4
  • tests/examples/contracts/log_indexer.py:10
  • tests/examples/contracts/intelligent_oracle.py:8
  • tests/examples/contracts/multi_file_contract/other.py:3
  • tests/examples/contracts/multi_file_contract/init.py:1

If proceeding, apply (example):

-from genlayer import *
+import genlayer as gl

Confirm no remaining wildcard imports:
rg -nP --type=py 'from\s+genlayer\s+import\s+*'

Repo note: tests commonly use from genlayer import * — confirm whether to standardize to explicit imports or keep the project convention.

@danieljrc888 danieljrc888 force-pushed the dxp-655-mock-web-request-in-genlayer-test branch from d87a519 to ea931bf Compare November 6, 2025 16:57
@danieljrc888 danieljrc888 force-pushed the dxp-655-mock-web-request-in-genlayer-test branch from ea931bf to 1ee311a Compare November 13, 2025 20:14
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
tests/examples/contracts/x_username_storage.py (3)

27-33: Add return statement to match method semantics.

The update_username method updates self.username but doesn't return it, which may confuse callers expecting a return value based on the method name.

Apply this diff:

-    def update_username(self, username: str):
+    def update_username(self, username: str) -> str:
         user_data = self.request_to_x(
             f"users/by/username/{username}", {"user.fields": "public_metrics,verified"}
         )
         self.username = user_data["username"]
+        return self.username

28-32: URL-encode username to prevent path injection.

The unescaped username interpolated directly into the URL path can cause malformed URLs or enable unintended endpoint access.

Apply this diff:

     @gl.public.write
     def update_username(self, username: str):
+        safe_username = urllib.parse.quote(username, safe="")
         user_data = self.request_to_x(
-            f"users/by/username/{username}", {"user.fields": "public_metrics,verified"}
+            f"users/by/username/{safe_username}", {"user.fields": "public_metrics,verified"}
         )
         self.username = user_data["username"]

47-55: Handle body as bytes or str.

The code assumes web_data_res.body is bytes and calls .decode("utf-8"), which will fail if body is already a string.

Apply this diff:

             if web_data_res.status != 200 or not web_data_res.body:
                 raise ValueError(
                     f"Failed to fetch data from X API: {web_data_res.body}"
                 )
 
-            web_data = web_data_res.body.decode("utf-8")
-
-            return json.loads(web_data)
+            body = web_data_res.body
+            if isinstance(body, (bytes, bytearray)):
+                web_text = body.decode("utf-8")
+            else:
+                web_text = str(body)
+            return json.loads(web_text)
🧹 Nitpick comments (2)
tests/examples/contracts/x_username_storage.py (1)

48-50: Consider extracting error message to a constant.

The inline error message is flagged by TRY003. For maintainability, consider extracting it to a module-level constant or custom exception class.

gltest/validators/validator_factory.py (1)

121-158: LGTM! Mock validator creation handles dual mock types correctly.

The updated methods properly validate that at least one mock type is provided, correctly deepcopy mock data, and maintain consistency with the existing API pattern.

Optional: The error message at lines 127-129 could be extracted to a module constant to address TRY003, but this is a minor style point.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ea931bf and 1ee311a.

📒 Files selected for processing (7)
  • README.md (1 hunks)
  • gltest/types.py (1 hunks)
  • gltest/validators/validator_factory.py (7 hunks)
  • gltest_cli/config/plugin.py (2 hunks)
  • tests/examples/contracts/x_username_storage.py (1 hunks)
  • tests/examples/tests/test_x_username_storage.py (1 hunks)
  • tests/gltest_cli/config/test_config_integration.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • README.md
  • tests/examples/tests/test_x_username_storage.py
  • gltest/types.py
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-15T11:15:12.026Z
Learnt from: danielrc888
Repo: genlayerlabs/genlayer-testing-suite PR: 53
File: gltest/validators/validator_factory.py:33-47
Timestamp: 2025-09-15T11:15:12.026Z
Learning: In the genlayer-testing-suite project, the "response" key in MockedLLMResponse is being deprecated in favor of "nondet_exec_prompt". This is an intentional breaking change as part of a planned API migration.

Applied to files:

  • gltest/validators/validator_factory.py
📚 Learning: 2025-06-10T20:24:58.774Z
Learnt from: danielrc888
Repo: genlayerlabs/genlayer-testing-suite PR: 27
File: tests/artifact/contracts/duplicate_ic_contract_2.py:7-22
Timestamp: 2025-06-10T20:24:58.774Z
Learning: In this codebase, contract/test files commonly use `from genlayer import *`, which brings a `gl` symbol into the local namespace; therefore references like `gl.public.view` are valid and linter warnings about `gl` being undefined can be ignored.

Applied to files:

  • tests/examples/contracts/x_username_storage.py
🧬 Code graph analysis (2)
gltest/validators/validator_factory.py (1)
gltest/types.py (2)
  • MockedLLMResponse (12-20)
  • MockedWebResponse (31-34)
tests/examples/contracts/x_username_storage.py (1)
gltest/contracts/contract.py (1)
  • Contract (178-229)
🪛 Ruff (0.14.4)
gltest/validators/validator_factory.py

127-129: Avoid specifying long messages outside the exception class

(TRY003)

tests/examples/contracts/x_username_storage.py

8-8: from genlayer import * used; unable to detect undefined names

(F403)


15-15: gl may be undefined, or defined from star imports

(F405)


23-23: gl may be undefined, or defined from star imports

(F405)


27-27: gl may be undefined, or defined from star imports

(F405)


44-44: gl may be undefined, or defined from star imports

(F405)


48-50: Avoid specifying long messages outside the exception class

(TRY003)


56-56: gl may be undefined, or defined from star imports

(F405)

🔇 Additional comments (6)
gltest_cli/config/plugin.py (1)

40-40: LGTM! Configuration precedence logic is well-structured.

The shift to None defaults with conditional assignment enables proper config hierarchy resolution (network config → CLI → system defaults), which is validated by the new integration tests.

Also applies to: 47-47, 123-128

tests/gltest_cli/config/test_config_integration.py (3)

437-472: LGTM! Test validates network config precedence.

The test correctly verifies that default_wait_retries and default_wait_interval from network configuration are properly applied.


475-513: LGTM! CLI override logic is properly validated.

The test confirms that CLI arguments (--default-wait-retries, --default-wait-interval) correctly take precedence over network configuration values.


516-550: LGTM! Default fallback behavior is validated.

The test ensures system defaults (50 retries, 3000ms interval) are correctly applied when values are absent from both network configuration and CLI arguments.

gltest/validators/validator_factory.py (2)

1-1: LGTM! MockedWebResponse integration is clean.

The import and new field follow the existing pattern for mock_llm_response, maintaining consistency in the data model.

Also applies to: 19-19


33-55: LGTM! Dual mock configuration is well-structured.

The restructured config building properly handles both LLM and web mocks, with appropriate None-safe defaults and correct nesting under plugin_config.

@cristiam86 cristiam86 merged commit bdb3a04 into main Nov 27, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants