diff --git a/src/colony_sdk/client.py b/src/colony_sdk/client.py index f381a14..2ebb75a 100644 --- a/src/colony_sdk/client.py +++ b/src/colony_sdk/client.py @@ -80,6 +80,23 @@ def refresh_token(self) -> None: self._token = None self._token_expiry = 0 + def rotate_key(self) -> dict: + """Rotate your API key. Returns the new key and invalidates the old one. + + The client's ``api_key`` is automatically updated to the new key. + You should persist the new key — the old one will no longer work. + + Returns: + dict with ``api_key`` containing the new key. + """ + data = self._raw_request("POST", "/auth/rotate-key") + if "api_key" in data: + self.api_key = data["api_key"] + # Force token refresh since the old key is now invalid + self._token = None + self._token_expiry = 0 + return data + # ── HTTP layer ─────────────────────────────────────────────────── def _raw_request( diff --git a/tests/test_api_methods.py b/tests/test_api_methods.py index 591b217..95404de 100644 --- a/tests/test_api_methods.py +++ b/tests/test_api_methods.py @@ -129,6 +129,33 @@ def test_no_auth_header_when_auth_false(self, mock_urlopen: MagicMock) -> None: req = _last_request(mock_urlopen) assert req.get_header("Authorization") is None + @patch("colony_sdk.client.urlopen") + def test_rotate_key(self, mock_urlopen: MagicMock) -> None: + mock_urlopen.return_value = _mock_response({"api_key": "col_new_key"}) + client = _authed_client() + + result = client.rotate_key() + + req = _last_request(mock_urlopen) + assert req.get_method() == "POST" + assert req.full_url == f"{BASE}/auth/rotate-key" + assert result == {"api_key": "col_new_key"} + # Client should update its own key + assert client.api_key == "col_new_key" + # Token should be cleared for refresh + assert client._token is None + assert client._token_expiry == 0 + + @patch("colony_sdk.client.urlopen") + def test_rotate_key_preserves_key_on_missing_field(self, mock_urlopen: MagicMock) -> None: + mock_urlopen.return_value = _mock_response({"status": "ok"}) + client = _authed_client() + + client.rotate_key() + + # Key should remain unchanged if response lacks api_key + assert client.api_key == "col_test" + # --------------------------------------------------------------------------- # Retry logic