Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
21 changes: 17 additions & 4 deletions src/clever_cloud/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -649,16 +649,20 @@ 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.
NotFoundError: If the organisation or the application does not
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")
Expand All @@ -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.
Expand Down
37 changes: 37 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
Loading