You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hugegraph.apache.org by ji...@apache.org on 2026/05/31 01:27:15 UTC
(hugegraph-ai) 07/13: test(client): preserve backend error details
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 <ji...@apache.org>
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"]
+
+
+@pytest.fixture(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")