Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
8 changes: 7 additions & 1 deletion pyicloud/services/invites/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,13 @@ def _post_public(self, path: str, payload: dict[str, Any]) -> dict[str, Any]:
url = f"{url}?{urlencode(params)}"
LOGGER.debug("CloudKit Invites POST %s", path)

resp = self._session.post(url, json=payload, timeout=self._timeout)
try:
resp = self._session.post(url, json=payload, timeout=self._timeout)
except PyiCloudAPIResponseException as exc:
# Same trap as the scoped wrappers above: PyiCloudSession raises on
# a non-ok JSON response, so every status check below is dead code
# for a 4xx. Map it the way those checks would have.
self._raise_invites_error(exc)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
code = getattr(resp, "status_code", 0)
if not isinstance(code, int):
code = 200
Expand Down
30 changes: 30 additions & 0 deletions tests/test_invites.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,36 @@ def test_an_unknown_shared_zone_falls_back_to_no_owner(self) -> None:
self.assertEqual(zone_id.zoneName, "MISSING-ZONE")
self.assertIsNone(zone_id.ownerRecordName)

def test_the_public_endpoint_translates_a_transport_error(self) -> None:
"""resolve() and accept() go through a different path to the rest.

`_post_public` inspects `resp.status_code` itself, but PyiCloudSession
raises on a non-ok JSON response before it ever returns, so those
checks are dead for a 4xx and the raw exception reached callers. The
five scoped wrappers were fixed for this; this sixth site uses a
different shape and was missed.
"""
session = MagicMock()
session.post.side_effect = PyiCloudAPIResponseException("Bad Request", 400)
self._monkeypatch.setattr(self.service.raw, "_session", session)

with self.assertRaises(InvitesApiError):
self.service.raw.resolve(["008FIXTURE"])
with self.assertRaises(InvitesApiError):
self.service.raw.accept(["008FIXTURE"])

def test_the_public_endpoint_maps_auth_and_rate_limits(self) -> None:
"""The same mapping as everywhere else, not a generic error."""
for code, expected in (
(401, InvitesAuthError),
(429, InvitesRateLimited),
):
session = MagicMock()
session.post.side_effect = PyiCloudAPIResponseException("nope", code)
self._monkeypatch.setattr(self.service.raw, "_session", session)
with self.subTest(code=code), self.assertRaises(expected):
self.service.raw.resolve(["008FIXTURE"])

def test_a_status_code_maps_the_same_as_a_string_or_an_int(self) -> None:
"""`PyiCloudAPIResponseException.code` is typed `int | str | None`.

Expand Down