This is an automated email from the ASF dual-hosted git repository.

imbajin pushed a commit to branch goal-test
in repository https://gitbox.apache.org/repos/asf/hugegraph-ai.git

commit 01127359706919cdc4dcfe01e3f57ea046ad3af0
Author: imbajin <[email protected]>
AuthorDate: Sun May 31 01:43:02 2026 +0800

    test(client): preserve backend error details
    
    - add response validation contracts for backend envelopes
    - preserve parsed server messages for non-404 HTTP errors
    - gate legacy HugeGraph tests through the service fixture
    - record G2 service-bound blocker and production evidence
---
 .../checkpoints/03-client-contract.md              | 43 +++++++++++++++++++---
 .workflow/quality-program/quality-state.json       | 30 ++++++++++++---
 .../reports/production-change-ledger.md            |  5 +++
 .../2026-05-31-hugegraph-ai-quality-program.md     |  8 ++--
 .../src/pyhugegraph/utils/util.py                  |  6 +--
 .../src/tests/api/test_response_validation.py      | 23 ++++++++++++
 hugegraph-python-client/src/tests/client_utils.py  |  8 ++++
 hugegraph-python-client/src/tests/conftest.py      |  8 ++++
 8 files changed, 112 insertions(+), 19 deletions(-)

diff --git a/.workflow/quality-program/checkpoints/03-client-contract.md 
b/.workflow/quality-program/checkpoints/03-client-contract.md
index 594a24c7..a242bdc3 100644
--- a/.workflow/quality-program/checkpoints/03-client-contract.md
+++ b/.workflow/quality-program/checkpoints/03-client-contract.md
@@ -2,7 +2,7 @@
 
 ## Status
 
-Blocked before client contract edits.
+Partially complete; service-bound client contract tests remain blocked.
 
 ## Blocker
 
@@ -11,13 +11,32 @@ Required HugeGraph service is unavailable locally:
 - `docker ps --format '{{.Names}} {{.Image}} {{.Ports}}'` failed because the 
Docker daemon is not reachable at `/Users/imbajin/.orbstack/run/docker.sock`.
 - Direct `GET http://127.0.0.1:8080/versions` failed with 
`ConnectionRefusedError`.
 
-The plan requires Layer B client contract tests to run against a real 
HugeGraph service and forbids silently skipping selected HugeGraph integration 
tests. Continuing into G2 test additions without a service would leave the core 
contract proof unverifiable.
+The plan requires Layer B client contract tests to run against a real 
HugeGraph service and forbids silently skipping selected HugeGraph integration 
tests. Service-bound schema, graph ID, and Gremlin contract additions remain 
paused until HugeGraph `1.7.0` is reachable.
+
+## Completed Non-Service Slice
+
+- Added malformed/backend error envelope contract tests for 
`ResponseValidation`.
+- Proved the 500 backend envelope regression red first:
+  - `test_backend_error_envelope_preserves_message` failed because the raised 
exception was plain `500 Server Error`.
+- Applied the minimal production fix:
+  - Prefer backend `message` over `exception` when extracting error details.
+  - Raise `Server Exception: {details}` for non-404 HTTP status codes instead 
of re-raising raw `HTTPError`.
+- Tightened legacy client integration setup:
+  - `ClientUtils()` now honors `HUGEGRAPH_*` env vars when no explicit service 
object is passed.
+  - A class-scope autouse fixture gates `hugegraph`-marked tests through 
`hugegraph_service` before unittest `setUpClass()`.
 
 ## Commands Run
 
 ```bash
 docker ps --format '{{.Names}} {{.Image}} {{.Ports}}'
 uv run python -c "import requests; 
print(requests.get('http://127.0.0.1:8080/versions', timeout=5).text[:200])"
+uv run pytest 
hugegraph-python-client/src/tests/api/test_response_validation.py -q
+uv run pytest hugegraph-python-client/src/tests -m "unit or contract" -q
+HUGEGRAPH_REQUIRED=true uv run pytest hugegraph-python-client/src/tests -m 
"integration and hugegraph" -v --tb=short --maxfail=1
+uv run ruff format --check hugegraph-python-client/src/tests/conftest.py 
hugegraph-python-client/src/tests/client_utils.py 
hugegraph-python-client/src/tests/api/test_response_validation.py 
hugegraph-python-client/src/pyhugegraph/utils/util.py
+uv run ruff check hugegraph-python-client/src/tests/conftest.py 
hugegraph-python-client/src/tests/client_utils.py 
hugegraph-python-client/src/tests/api/test_response_validation.py 
hugegraph-python-client/src/pyhugegraph/utils/util.py
+uv run pre-commit run trailing-whitespace --files 
hugegraph-python-client/src/pyhugegraph/utils/util.py
+git -c core.whitespace=blank-at-eol,blank-at-eof,space-before-tab,cr-at-eol 
diff --check
 ```
 
 ## Files Touched
@@ -25,18 +44,30 @@ uv run python -c "import requests; 
print(requests.get('http://127.0.0.1:8080/ver
 - `.workflow/quality-program/checkpoints/03-client-contract.md`
 - `.workflow/quality-program/quality-state.json`
 - `.workflow/quality-program/reports/flaky-risk-ledger.md`
+- `.workflow/quality-program/reports/production-change-ledger.md`
+- `hugegraph-python-client/src/tests/conftest.py`
+- `hugegraph-python-client/src/tests/client_utils.py`
+- `hugegraph-python-client/src/tests/api/test_response_validation.py`
+- `hugegraph-python-client/src/pyhugegraph/utils/util.py`
 
 ## Production Changes
 
-None.
+- `hugegraph-python-client/src/pyhugegraph/utils/util.py`
+  - Preserves parsed backend error details for 500-class error envelopes.
+  - Proving test: `uv run pytest 
hugegraph-python-client/src/tests/api/test_response_validation.py -q`
 
 ## Tests Added or Changed
 
-None.
+- Added `test_backend_error_envelope_preserves_message`.
+- Added `test_malformed_error_body_uses_response_text`.
+- Added client conftest service gate for `hugegraph`-marked tests.
+- Updated `ClientUtils()` to read `HUGEGRAPH_*` env vars for legacy 
unittest-style integrations.
 
 ## Failure Classification
 
-Service setup failure: HugeGraph Server `1.7.0` is not reachable, and Docker 
cannot start or inspect containers because the local daemon is unavailable.
+- Service setup failure: HugeGraph Server `1.7.0` is not reachable, and Docker 
cannot start or inspect containers because the local daemon is unavailable.
+- `HUGEGRAPH_REQUIRED=true` integration command now fails at the shared 
`/versions` readiness fixture before test class setup, not during unrelated 
cleanup.
+- `util.py` keeps its existing CRLF line-ending style; whitespace verification 
used `cr-at-eol` to avoid a whole-file line-ending rewrite.
 
 ## Resume Condition
 
@@ -46,4 +77,4 @@ Start a HugeGraph `1.7.0` service reachable at 
`http://127.0.0.1:8080`, or start
 docker run -d --name hugegraph-quality -p 8080:8080 -e PASSWORD=admin 
hugegraph/hugegraph:1.7.0
 ```
 
-Then resume G2 from Step G2.1.
+Then resume G2 from Step G2.2.
diff --git a/.workflow/quality-program/quality-state.json 
b/.workflow/quality-program/quality-state.json
index 3f68f08c..8414533f 100644
--- a/.workflow/quality-program/quality-state.json
+++ b/.workflow/quality-program/quality-state.json
@@ -58,12 +58,23 @@
     ".github/workflows/hugegraph-llm.yml",
     "docs/quality/hugegraph-integration.md",
     ".workflow/quality-program/checkpoints/02-service-fixture.md",
-    ".workflow/quality-program/checkpoints/03-client-contract.md"
+    ".workflow/quality-program/checkpoints/03-client-contract.md",
+    ".workflow/quality-program/reports/production-change-ledger.md",
+    "hugegraph-python-client/src/pyhugegraph/utils/util.py"
+  ],
+  "production_changes": [
+    {
+      "goal": "G2",
+      "file": "hugegraph-python-client/src/pyhugegraph/utils/util.py",
+      "change": "Preserve backend error envelope details for non-404 HTTP 
errors and prefer server message fields.",
+      "test": "uv run pytest 
hugegraph-python-client/src/tests/api/test_response_validation.py -q"
+    }
   ],
-  "production_changes": [],
   "tests_added_or_changed": [
     "Added file-level unit/contract/integration/hugegraph markers to selected 
existing tests for Layer A and Layer B selection.",
-    "Added HugeGraph service fixtures and conftest wiring for client and LLM 
test suites."
+    "Added HugeGraph service fixtures and conftest wiring for client and LLM 
test suites.",
+    "Added response validation contract tests for backend and malformed error 
envelopes.",
+    "Updated client integration harness so hugegraph-marked legacy tests use 
the service fixture gate before unittest setUpClass."
   ],
   "commands_run": [
     "sed -n '1,220p' AGENTS.md",
@@ -100,13 +111,20 @@
     "git diff --check",
     "uv run python -c \"import yaml, pathlib; 
[yaml.safe_load(pathlib.Path(p).read_text()) for p in 
['.github/workflows/hugegraph-llm.yml','.github/workflows/hugegraph-python-client.yml']]\"",
     "docker ps --format '{{.Names}} {{.Image}} {{.Ports}}'",
-    "uv run python -c \"import requests; 
print(requests.get('http://127.0.0.1:8080/versions', timeout=5).text[:200])\""
+    "uv run python -c \"import requests; 
print(requests.get('http://127.0.0.1:8080/versions', timeout=5).text[:200])\"",
+    "uv run pytest 
hugegraph-python-client/src/tests/api/test_response_validation.py -q",
+    "uv run pytest hugegraph-python-client/src/tests -m \"unit or contract\" 
-q",
+    "HUGEGRAPH_REQUIRED=true uv run pytest hugegraph-python-client/src/tests 
-m \"integration and hugegraph\" -v --tb=short --maxfail=1",
+    "uv run ruff format --check hugegraph-python-client/src/tests/conftest.py 
hugegraph-python-client/src/tests/client_utils.py 
hugegraph-python-client/src/tests/api/test_response_validation.py 
hugegraph-python-client/src/pyhugegraph/utils/util.py",
+    "uv run ruff check hugegraph-python-client/src/tests/conftest.py 
hugegraph-python-client/src/tests/client_utils.py 
hugegraph-python-client/src/tests/api/test_response_validation.py 
hugegraph-python-client/src/pyhugegraph/utils/util.py",
+    "uv run pre-commit run trailing-whitespace --files 
hugegraph-python-client/src/pyhugegraph/utils/util.py",
+    "git -c 
core.whitespace=blank-at-eol,blank-at-eof,space-before-tab,cr-at-eol diff 
--check"
   ],
   "known_failures": [
     "Root-level LLM collection command fails before collection because 
existing prompt config requires cwd hugegraph-llm.",
     "Initial coverage command failed before uv sync because pytest-cov was 
missing from the active environment.",
     "LLM integration and hugegraph collection is empty before G3 adds 
real-boundary tests; pytest returns exit code 5.",
-    "G2 blocked: Docker daemon is unavailable and no HugeGraph service is 
reachable at http://127.0.0.1:8080/versions.";
+    "G2 service-bound tests blocked: Docker daemon is unavailable and no 
HugeGraph service is reachable at http://127.0.0.1:8080/versions.";
   ],
   "deferred_items": [
     "Avoid #350/#277 YAML config migration files unless tests require a narrow 
compatibility assertion.",
@@ -115,5 +133,5 @@
     "Avoid #240 vector/property embedding abstraction surface.",
     "Quarantine client/test fixes overlapping #323/#329/#342 until exact 
changed files are inspected."
   ],
-  "next_recommended_action": "Start Docker/OrbStack and HugeGraph 1.7.0, then 
resume G2 from Step G2.1"
+  "next_recommended_action": "Start Docker/OrbStack and HugeGraph 1.7.0, then 
resume G2 from Step G2.2"
 }
diff --git a/.workflow/quality-program/reports/production-change-ledger.md 
b/.workflow/quality-program/reports/production-change-ledger.md
new file mode 100644
index 00000000..b5f2eb99
--- /dev/null
+++ b/.workflow/quality-program/reports/production-change-ledger.md
@@ -0,0 +1,5 @@
+# Production Change Ledger
+
+| Goal | File | Change | Test proving it | Reason | Risk |
+|---|---|---|---|---|---|
+| G2 | `hugegraph-python-client/src/pyhugegraph/utils/util.py` | Preserve 
backend error envelope details for non-404 HTTP errors and prefer server 
`message` over `exception`. | `uv run pytest 
hugegraph-python-client/src/tests/api/test_response_validation.py -q` | 
Response validation contract gap for 500 backend envelopes. | Low; narrows 
error wrapping to preserve parsed server details while keeping 404 mapping. |
diff --git a/docs/superpowers/plans/2026-05-31-hugegraph-ai-quality-program.md 
b/docs/superpowers/plans/2026-05-31-hugegraph-ai-quality-program.md
index 51246039..a7bf466e 100644
--- a/docs/superpowers/plans/2026-05-31-hugegraph-ai-quality-program.md
+++ b/docs/superpowers/plans/2026-05-31-hugegraph-ai-quality-program.md
@@ -687,7 +687,7 @@ git commit -m "test(quality): standardize hugegraph 
integration harness" -m "- a
 - Update: `.workflow/quality-program/reports/production-change-ledger.md`
 - Create/Update: `.workflow/quality-program/checkpoints/03-client-contract.md`
 
-- [ ] **Step G2.1: Convert client integration tests to fixture-driven setup**
+- [x] **Step G2.1: Convert client integration tests to fixture-driven setup**
 
 In tests that instantiate `ClientUtils()`, use the `hugegraph_service` fixture.
 
@@ -767,7 +767,7 @@ def test_gremlin_error_surface_is_explicit(client_utils):
 
 Keep security-operation tests. Do not add connectivity probes that turn 
failures into skips.
 
-- [ ] **Step G2.5: Add response validation malformed envelope tests**
+- [x] **Step G2.5: Add response validation malformed envelope tests**
 
 Extend `hugegraph-python-client/src/tests/api/test_response_validation.py` 
with malformed and backend-error bodies:
 
@@ -800,7 +800,7 @@ Add the missing `requests` import if the file does not 
already have it:
 import requests
 ```
 
-- [ ] **Step G2.6: Run client contract suites**
+- [x] **Step G2.6: Run client contract suites**
 
 Run:
 
@@ -811,7 +811,7 @@ HUGEGRAPH_REQUIRED=true uv run pytest 
hugegraph-python-client/src/tests -m "inte
 
 Expected: Layer A passes; Layer B either passes against running HugeGraph or 
fails with a classified client/server/setup issue.
 
-- [ ] **Step G2.7: Apply minimal production fixes only when tests prove a 
contract gap**
+- [x] **Step G2.7: Apply minimal production fixes only when tests prove a 
contract gap**
 
 For each production fix:
 
diff --git a/hugegraph-python-client/src/pyhugegraph/utils/util.py 
b/hugegraph-python-client/src/pyhugegraph/utils/util.py
index b4a6f644..c1b806cb 100644
--- a/hugegraph-python-client/src/pyhugegraph/utils/util.py
+++ b/hugegraph-python-client/src/pyhugegraph/utils/util.py
@@ -106,8 +106,8 @@ class ResponseValidation:
                         status = body.get("status")
                         status_message = status.get("message") if 
isinstance(status, dict) else None
                         details = (
-                            body.get("exception")
-                            or body.get("message")
+                            body.get("message")
+                            or body.get("exception")
                             or status_message
                             or response.text
                             or "unknown error"
@@ -129,7 +129,7 @@ class ResponseValidation:
 
                 if response.status_code == 404:
                     raise NotFoundError(response.content) from e
-                if response.status_code == 400:
+                if response.status_code >= 400:
                     raise Exception(f"Server Exception: {details}") from e
                 raise e
 
diff --git a/hugegraph-python-client/src/tests/api/test_response_validation.py 
b/hugegraph-python-client/src/tests/api/test_response_validation.py
index da8e771c..c66a2086 100644
--- a/hugegraph-python-client/src/tests/api/test_response_validation.py
+++ b/hugegraph-python-client/src/tests/api/test_response_validation.py
@@ -54,6 +54,29 @@ class TestResponseValidation(unittest.TestCase):
         with self.assertRaisesRegex(Exception, "Server Exception: bad 
gremlin"):
             validator(response, "POST", "/gremlin")
 
+    def test_backend_error_envelope_preserves_message(self):
+        response = Mock(spec=requests.Response)
+        response.status_code = 500
+        response.text = '{"exception":"BackendException","message":"quality 
failure"}'
+        response.content = response.text.encode("utf-8")
+        response.json.return_value = {"exception": "BackendException", 
"message": "quality failure"}
+        response.request = Mock(body='{"gremlin":"g.V2()"}', 
url="http://127.0.0.1:8080/gremlin";)
+        response.raise_for_status.side_effect = 
requests.exceptions.HTTPError("500 Server Error")
+        validator = ResponseValidation()
+
+        with pytest.raises(Exception) as exc_info:
+            validator(response, method="POST", path="/gremlin")
+
+        assert "quality failure" in str(exc_info.value)
+
+    def test_malformed_error_body_uses_response_text(self):
+        response = self._mock_error_response(ValueError("not json"), "not 
json")
+        response.json.side_effect = ValueError("not json")
+        validator = ResponseValidation()
+
+        with self.assertRaisesRegex(Exception, "Server Exception: not json"):
+            validator(response, "POST", "/gremlin")
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/hugegraph-python-client/src/tests/client_utils.py 
b/hugegraph-python-client/src/tests/client_utils.py
index 340fb101..7d209cc6 100644
--- a/hugegraph-python-client/src/tests/client_utils.py
+++ b/hugegraph-python-client/src/tests/client_utils.py
@@ -15,6 +15,8 @@
 # specific language governing permissions and limitations
 # under the License.
 
+import os
+
 from pyhugegraph.client import PyHugeClient
 
 
@@ -33,6 +35,12 @@ class ClientUtils:
             self.USERNAME = service.user
             self.PASSWORD = service.password
             self.GRAPHSPACE = service.graphspace
+        else:
+            self.URL = os.getenv("HUGEGRAPH_URL", self.URL)
+            self.GRAPH = os.getenv("HUGEGRAPH_GRAPH", self.GRAPH)
+            self.USERNAME = os.getenv("HUGEGRAPH_USER", self.USERNAME)
+            self.PASSWORD = os.getenv("HUGEGRAPH_PASSWORD", self.PASSWORD)
+            self.GRAPHSPACE = os.getenv("HUGEGRAPH_GRAPHSPACE") or 
self.GRAPHSPACE
         self.client = PyHugeClient(
             url=self.URL,
             user=self.USERNAME,
diff --git a/hugegraph-python-client/src/tests/conftest.py 
b/hugegraph-python-client/src/tests/conftest.py
index 6adf0295..b31142ee 100644
--- a/hugegraph-python-client/src/tests/conftest.py
+++ b/hugegraph-python-client/src/tests/conftest.py
@@ -15,6 +15,14 @@
 # specific language governing permissions and limitations
 # under the License.
 
+import pytest
+
 from .fixtures.hugegraph_service import hugegraph_service
 
 __all__ = ["hugegraph_service"]
+
+
[email protected](scope="class", autouse=True)
+def require_hugegraph_for_marked_tests(request):
+    if request.node.get_closest_marker("hugegraph") is not None:
+        request.getfixturevalue("hugegraph_service")

Reply via email to