1"""tests for constellation client.""" 2 3from unittest.mock import AsyncMock, MagicMock, patch 4 5import pytest 6 7from backend._internal.constellation import get_like_count, get_like_count_safe 8 9 10@pytest.fixture 11def mock_settings(): 12 """mock settings with like_collection.""" 13 with patch("backend._internal.constellation.settings") as mock: 14 mock.atproto.like_collection = "fm.plyr.dev.like" 15 yield mock 16 17 18class TestGetLikeCount: 19 """tests for get_like_count.""" 20 21 async def test_returns_count_from_response(self, mock_settings): 22 """should return count from constellation response.""" 23 mock_response = MagicMock() 24 mock_response.json.return_value = {"count": 42} 25 mock_response.raise_for_status = MagicMock() 26 27 with patch("backend._internal.constellation.httpx.AsyncClient") as mock_client: 28 mock_client.return_value.__aenter__.return_value.get = AsyncMock( 29 return_value=mock_response 30 ) 31 32 result = await get_like_count("at://did:plc:xxx/fm.plyr.track/abc") 33 34 assert result == 42 35 36 async def test_calls_correct_endpoint(self, mock_settings): 37 """should call constellation with correct params.""" 38 mock_response = MagicMock() 39 mock_response.json.return_value = {"count": 0} 40 mock_response.raise_for_status = MagicMock() 41 42 with patch("backend._internal.constellation.httpx.AsyncClient") as mock_client: 43 mock_get = AsyncMock(return_value=mock_response) 44 mock_client.return_value.__aenter__.return_value.get = mock_get 45 46 await get_like_count("at://did:plc:xxx/fm.plyr.track/abc") 47 48 mock_get.assert_called_once_with( 49 "https://constellation.microcosm.blue/links/count", 50 params={ 51 "target": "at://did:plc:xxx/fm.plyr.track/abc", 52 "collection": "fm.plyr.dev.like", 53 "path": ".subject.uri", 54 }, 55 ) 56 57 async def test_raises_on_http_error(self, mock_settings): 58 """should raise when constellation returns error.""" 59 mock_response = MagicMock() 60 mock_response.raise_for_status.side_effect = Exception("500 error") 61 62 with patch("backend._internal.constellation.httpx.AsyncClient") as mock_client: 63 mock_client.return_value.__aenter__.return_value.get = AsyncMock( 64 return_value=mock_response 65 ) 66 67 with pytest.raises(Exception, match="500 error"): 68 await get_like_count("at://did:plc:xxx/fm.plyr.track/abc") 69 70 71class TestGetLikeCountSafe: 72 """tests for get_like_count_safe.""" 73 74 async def test_returns_count_on_success(self, mock_settings): 75 """should return count when successful.""" 76 mock_response = MagicMock() 77 mock_response.json.return_value = {"count": 10} 78 mock_response.raise_for_status = MagicMock() 79 80 with patch("backend._internal.constellation.httpx.AsyncClient") as mock_client: 81 mock_client.return_value.__aenter__.return_value.get = AsyncMock( 82 return_value=mock_response 83 ) 84 85 result = await get_like_count_safe("at://did:plc:xxx/fm.plyr.track/abc") 86 87 assert result == 10 88 89 async def test_returns_fallback_on_error(self, mock_settings): 90 """should return fallback when constellation fails.""" 91 with patch("backend._internal.constellation.httpx.AsyncClient") as mock_client: 92 mock_client.return_value.__aenter__.return_value.get = AsyncMock( 93 side_effect=Exception("connection failed") 94 ) 95 96 result = await get_like_count_safe( 97 "at://did:plc:xxx/fm.plyr.track/abc", fallback=99 98 ) 99 100 assert result == 99 101 102 async def test_default_fallback_is_zero(self, mock_settings): 103 """should default to 0 fallback.""" 104 with patch("backend._internal.constellation.httpx.AsyncClient") as mock_client: 105 mock_client.return_value.__aenter__.return_value.get = AsyncMock( 106 side_effect=Exception("connection failed") 107 ) 108 109 result = await get_like_count_safe("at://did:plc:xxx/fm.plyr.track/abc") 110 111 assert result == 0