diff --git a/CHANGELOG.md b/CHANGELOG.md index c390fc7..97c902c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to this project are documented in this file. +## Unreleased + +### Fixed + +- `create_domain()` no longer fails on every real call. The API answers the + `PUT` with a status message (`{"id", "message", "type"}`), not the vhost, and + the method tried to parse it as a `Domain`, raising `InvalidResponseError`. + The message is now accepted and the returned `Domain` carries the requested + name; a message whose `type` is `"error"` raises `InvalidResponseError` + instead of being reported as a success. Reported in + [issue #9](https://github.com/CleverCloud/clevercloud-sdk-python/issues/9). + ## 0.2.1 Addresses [issue #9](https://github.com/CleverCloud/clevercloud-sdk-python/issues/9), diff --git a/src/clever_cloud/client.py b/src/clever_cloud/client.py index c5e9fe2..fba7815 100644 --- a/src/clever_cloud/client.py +++ b/src/clever_cloud/client.py @@ -649,9 +649,10 @@ async def create_domain( ignored, so the value round-trips with :attr:`Domain.domain`. Returns: - The domain now attached to the application. This endpoint answers - with an empty body on some deployments; the returned value is then - built from the requested name. + The domain now attached to the application. The API answers with a + status message (``{"id", "message", "type"}``) or an empty body + rather than the vhost, so the returned value is built from the + requested name. Raises: ValueError: If ``domain`` is empty. @@ -659,6 +660,9 @@ async def create_domain( exist. HttpError: If the domain is invalid, or is already attached to another application. + InvalidResponseError: If the API answers with a message whose + ``type`` is ``"error"`` despite a successful status, or with a + body that is neither a message nor a vhost. """ owner = encode_path_segment(owner_id, name="owner_id") app = encode_path_segment(app_id, name="app_id") @@ -671,7 +675,16 @@ async def create_domain( ) if data is None: return Domain(domain=fqdn, is_primary=False) - return Domain.from_api_response(data) + if isinstance(data, dict) and "fqdn" in data: + return Domain.from_api_response(data) + # The documented answer is a status message, not the vhost. + if isinstance(data, dict) and "type" in data: + if data["type"] == "error": + msg = f"Domain {fqdn!r} was not attached: {data.get('message')!r}" + raise InvalidResponseError(msg, response_body=str(data)) + return Domain(domain=fqdn, is_primary=False) + msg = f"Domain: unexpected response to the attach request: {data!r}" + raise InvalidResponseError(msg, response_body=str(data)) async def list_domains(self, owner_id: str, app_id: str) -> list[Domain]: """List all domains (vhosts) for an application. diff --git a/tests/test_client.py b/tests/test_client.py index b765029..b9a6353 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -560,6 +560,43 @@ async def test_create_domain_accepts_an_empty_body( ) assert domain.domain == "app.example.test" + async def test_create_domain_accepts_a_status_message( + self, make_client: Callable[..., CleverCloudClient] + ) -> None: + """The API answers the PUT with a message, not the vhost (issue #9).""" + client = make_client( + lambda r: httpx.Response( + 200, json={"id": 0, "message": "The vhost was added", "type": "success"} + ) + ) + async with client: + domain = await client.create_domain( + "orga_1", "app_1", domain="app.example.test/" + ) + assert domain.domain == "app.example.test" + assert domain.is_primary is False + + async def test_create_domain_rejects_an_error_message( + self, make_client: Callable[..., CleverCloudClient] + ) -> None: + """An error message must not be reported as an attached domain.""" + client = make_client( + lambda r: httpx.Response( + 200, json={"id": 0, "message": "vhost refused", "type": "error"} + ) + ) + async with client: + with pytest.raises(InvalidResponseError, match="vhost refused"): + await client.create_domain("orga_1", "app_1", domain="app.example.test") + + async def test_create_domain_rejects_an_unknown_body( + self, make_client: Callable[..., CleverCloudClient] + ) -> None: + client = make_client(lambda r: httpx.Response(200, json=["app.example.test"])) + async with client: + with pytest.raises(InvalidResponseError): + await client.create_domain("orga_1", "app_1", domain="app.example.test") + async def test_create_domain_encodes_a_path_suffix( self, make_client: Callable[..., CleverCloudClient],