diff --git a/src/presa_mano/clients/open3dmodel.py b/src/presa_mano/clients/open3dmodel.py index 2beea4f..206e5e6 100644 --- a/src/presa_mano/clients/open3dmodel.py +++ b/src/presa_mano/clients/open3dmodel.py @@ -59,10 +59,24 @@ class Open3DModelClient: body = r.json() except ValueError as e: raise Open3DModelUnavailable(f"non-JSON response: {e}") from e - if not isinstance(body, list): - raise Open3DModelError("UNEXPECTED_PAYLOAD", - f"expected list, got {type(body).__name__}") - return body + # Open3DModel wraps list responses in the {success, data, error} envelope + # too; older mocks may return a bare list. Accept either shape. + if isinstance(body, dict) and "success" in body: + if not body.get("success"): + err = body.get("error") or {} + raise Open3DModelError( + str(err.get("code") or "UNKNOWN"), + str(err.get("message") or "no message"), + ) + data = body.get("data") + if not isinstance(data, list): + raise Open3DModelError("UNEXPECTED_PAYLOAD", + f"expected list in data, got {type(data).__name__}") + return data + if isinstance(body, list): + return body + raise Open3DModelError("UNEXPECTED_PAYLOAD", + f"expected list or envelope, got {type(body).__name__}") def estimate_pose(self, recipe_id: str, points_cv_m: np.ndarray, intrinsics: dict[str, Any]) -> dict[str, Any]: diff --git a/tests/test_open3dmodel_client.py b/tests/test_open3dmodel_client.py index 868c84d..a7971ed 100644 --- a/tests/test_open3dmodel_client.py +++ b/tests/test_open3dmodel_client.py @@ -59,6 +59,18 @@ def test_list_recipes_happy_path(): assert headers.get("X-Api-Key") == "K" +def test_list_recipes_envelope_shape(): + """Open3DModel wraps /api/recipes in {success, data, error}.""" + body = {"success": True, + "data": [{"id": "abc", "name": "P2", "symmetry_type": "cylindrical"}], + "error": None} + response = httpx.Response(200, json=body) + client = Open3DModelClient("http://o3d:8878", api_key="K", timeout_s=2.0) + with patch("httpx.Client.get", return_value=response): + recs = client.list_recipes() + assert recs == body["data"] + + def test_list_recipes_connect_error_raises_unavailable(): client = Open3DModelClient("http://o3d:8878", api_key="K", timeout_s=2.0) with patch("httpx.Client.get", side_effect=httpx.ConnectError("nope")):