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")
