music on atproto
plyr.fm
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