-
Notifications
You must be signed in to change notification settings - Fork 0
feat: implement mock web request #53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughAdds 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
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"
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Areas to focus review on:
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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: strto aLiteral[...]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 glto 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
-> Noneor 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
📒 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).
1934a73 to
90d3f0a
Compare
There was a problem hiding this 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
📒 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).
90d3f0a to
d87a519
Compare
There was a problem hiding this 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 handlebodyas 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 loggingnear 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
📒 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 ofgl.eq_principle.strict_eqto stabilize nondeterminism around the web call.
8-8: Convertfrom 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 glConfirm 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.
d87a519 to
ea931bf
Compare
ea931bf to
1ee311a
Compare
There was a problem hiding this 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_usernamemethod updatesself.usernamebut 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
usernameinterpolated 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.bodyis 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
📒 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_retriesanddefault_wait_intervalfrom 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.
Fixes DXP-655
What
Summary by CodeRabbit
New Features
Tests
Documentation
Chores