diff --git a/src/colony_sdk/client.py b/src/colony_sdk/client.py index c2eab19..88e31a1 100644 --- a/src/colony_sdk/client.py +++ b/src/colony_sdk/client.py @@ -388,23 +388,20 @@ def update_profile(self, **fields: str) -> dict: # ── Following ──────────────────────────────────────────────────── def follow(self, user_id: str) -> dict: - """Follow a user. If already following, this unfollows them (toggle). + """Follow a user. Args: - user_id: The UUID of the user to follow/unfollow. + user_id: The UUID of the user to follow. """ return self._raw_request("POST", f"/users/{user_id}/follow") def unfollow(self, user_id: str) -> dict: """Unfollow a user. - This is an alias for :meth:`follow` since the API toggles the - follow state. Provided for readability. - Args: user_id: The UUID of the user to unfollow. """ - return self.follow(user_id) + return self._raw_request("DELETE", f"/users/{user_id}/follow") # ── Notifications ─────────────────────────────────────────────── diff --git a/tests/test_api_methods.py b/tests/test_api_methods.py index 133a0e8..db6ef6c 100644 --- a/tests/test_api_methods.py +++ b/tests/test_api_methods.py @@ -686,6 +686,35 @@ def test_update_profile(self, mock_urlopen: MagicMock) -> None: assert body == {"bio": "New bio", "lightning_address": "me@getalby.com"} +# --------------------------------------------------------------------------- +# Following +# --------------------------------------------------------------------------- + + +class TestFollowing: + @patch("colony_sdk.client.urlopen") + def test_follow(self, mock_urlopen: MagicMock) -> None: + mock_urlopen.return_value = _mock_response({"status": "following"}) + client = _authed_client() + + client.follow("u1") + + req = _last_request(mock_urlopen) + assert req.get_method() == "POST" + assert req.full_url == f"{BASE}/users/u1/follow" + + @patch("colony_sdk.client.urlopen") + def test_unfollow(self, mock_urlopen: MagicMock) -> None: + mock_urlopen.return_value = _mock_response({}) + client = _authed_client() + + client.unfollow("u1") + + req = _last_request(mock_urlopen) + assert req.get_method() == "DELETE" + assert req.full_url == f"{BASE}/users/u1/follow" + + # --------------------------------------------------------------------------- # Notifications # --------------------------------------------------------------------------- diff --git a/tests/test_client.py b/tests/test_client.py index 08967af..0f580ae 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -118,15 +118,11 @@ def test_follow_calls_correct_endpoint(): assert callable(client.follow) -def test_unfollow_aliases_follow(): - """unfollow() should be an alias for follow().""" +def test_unfollow_is_separate_method(): + """unfollow() should be a distinct method from follow().""" client = ColonyClient("col_test") + assert callable(client.unfollow) assert client.unfollow.__func__ is not client.follow.__func__ - # But unfollow delegates to follow internally — check source - import inspect - - source = inspect.getsource(client.unfollow) - assert "self.follow(user_id)" in source def test_api_error_exported(): diff --git a/tests/test_integration_follow.py b/tests/test_integration_follow.py new file mode 100644 index 0000000..b9eead4 --- /dev/null +++ b/tests/test_integration_follow.py @@ -0,0 +1,63 @@ +"""Integration tests for follow/unfollow endpoints. + +These tests hit the real Colony API and require a valid API key. + +Run with: + COLONY_TEST_API_KEY=col_xxx pytest tests/test_integration_follow.py -v + +Skipped automatically when the env var is not set. +""" + +import contextlib +import os +import sys +from pathlib import Path + +import pytest + +sys.path.insert(0, str(Path(__file__).parent.parent / "src")) + +from colony_sdk import ColonyAPIError, ColonyClient + +API_KEY = os.environ.get("COLONY_TEST_API_KEY") +# ColonistOne's user ID on thecolony.cc +COLONIST_ONE_ID = "324ab98e-955c-4274-bd30-8570cbdf58f1" + +pytestmark = pytest.mark.skipif(not API_KEY, reason="set COLONY_TEST_API_KEY to run") + + +@pytest.fixture +def client() -> ColonyClient: + assert API_KEY is not None + return ColonyClient(API_KEY) + + +class TestFollowIntegration: + def test_follow_unfollow_lifecycle(self, client: ColonyClient) -> None: + """Follow a user, then unfollow them.""" + # Ensure we start unfollowed (ignore errors if already unfollowed) + with contextlib.suppress(ColonyAPIError): + client.unfollow(COLONIST_ONE_ID) + + # Follow + result = client.follow(COLONIST_ONE_ID) + assert result.get("status") == "following" + + try: + # Following again should fail with 409 + with pytest.raises(ColonyAPIError) as exc_info: + client.follow(COLONIST_ONE_ID) + assert exc_info.value.status == 409 + finally: + # Unfollow (cleanup) + client.unfollow(COLONIST_ONE_ID) + + def test_unfollow_not_following_raises(self, client: ColonyClient) -> None: + """Unfollowing a user you don't follow should raise an error.""" + # Ensure we're not following + with contextlib.suppress(ColonyAPIError): + client.unfollow(COLONIST_ONE_ID) + + with pytest.raises(ColonyAPIError) as exc_info: + client.unfollow(COLONIST_ONE_ID) + assert exc_info.value.status in (404, 409)