audio streaming app plyr.fm
at main 112 lines 4.2 kB view raw
1"""tests for resilient PDS record fetching with DID resolution fallback.""" 2 3from unittest.mock import AsyncMock, MagicMock, patch 4 5import pytest 6 7from backend._internal.atproto.records.fm_plyr.track import ( 8 get_record_public_resilient, 9) 10 11RECORD_URI = "at://did:plc:abc123/fm.plyr.track/rkey1" 12STALE_PDS = "https://shiitake.us-east.host.bsky.network" 13CORRECT_PDS = "https://eurosky.social" 14RECORD_DATA = {"uri": RECORD_URI, "value": {"title": "test track"}} 15 16 17async def test_resilient_retries_with_resolved_pds() -> None: 18 """when the cached PDS URL fails, resolve the DID and retry.""" 19 mock_atproto_data = MagicMock(pds=CORRECT_PDS) 20 21 with ( 22 patch( 23 "backend._internal.atproto.records.fm_plyr.track.get_record_public", 24 new_callable=AsyncMock, 25 side_effect=[Exception("404 not found"), RECORD_DATA], 26 ) as mock_get, 27 patch( 28 "backend._internal.atproto.records.fm_plyr.track.AsyncDidResolver" 29 ) as mock_resolver_cls, 30 ): 31 mock_resolver = MagicMock() 32 mock_resolver.resolve_atproto_data = AsyncMock(return_value=mock_atproto_data) 33 mock_resolver_cls.return_value = mock_resolver 34 35 data, resolved_url = await get_record_public_resilient(RECORD_URI, STALE_PDS) 36 37 assert data == RECORD_DATA 38 assert resolved_url == CORRECT_PDS 39 # first call with stale PDS, second with resolved 40 assert mock_get.call_count == 2 41 mock_get.assert_any_call(RECORD_URI, STALE_PDS) 42 mock_get.assert_any_call(RECORD_URI, CORRECT_PDS) 43 44 45async def test_resilient_reraises_when_resolved_url_same() -> None: 46 """when DID resolution returns the same PDS URL, re-raise original error.""" 47 mock_atproto_data = MagicMock(pds=STALE_PDS) 48 49 with ( 50 patch( 51 "backend._internal.atproto.records.fm_plyr.track.get_record_public", 52 new_callable=AsyncMock, 53 side_effect=Exception("404 not found"), 54 ), 55 patch( 56 "backend._internal.atproto.records.fm_plyr.track.AsyncDidResolver" 57 ) as mock_resolver_cls, 58 ): 59 mock_resolver = MagicMock() 60 mock_resolver.resolve_atproto_data = AsyncMock(return_value=mock_atproto_data) 61 mock_resolver_cls.return_value = mock_resolver 62 63 with pytest.raises(Exception, match="404 not found"): 64 await get_record_public_resilient(RECORD_URI, STALE_PDS) 65 66 67async def test_resilient_reraises_when_no_pds_url_provided() -> None: 68 """when no pds_url is provided, don't attempt DID resolution.""" 69 with ( 70 patch( 71 "backend._internal.atproto.records.fm_plyr.track.get_record_public", 72 new_callable=AsyncMock, 73 side_effect=Exception("fetch failed"), 74 ), 75 pytest.raises(Exception, match="fetch failed"), 76 ): 77 await get_record_public_resilient(RECORD_URI, pds_url=None) 78 79 80async def test_resilient_returns_none_when_first_try_succeeds() -> None: 81 """when the initial fetch succeeds, resolved_pds_url is None.""" 82 with patch( 83 "backend._internal.atproto.records.fm_plyr.track.get_record_public", 84 new_callable=AsyncMock, 85 return_value=RECORD_DATA, 86 ): 87 data, resolved_url = await get_record_public_resilient(RECORD_URI, STALE_PDS) 88 89 assert data == RECORD_DATA 90 assert resolved_url is None 91 92 93async def test_resilient_reraises_when_did_resolution_fails() -> None: 94 """when DID resolution itself fails, re-raise the original PDS error.""" 95 with ( 96 patch( 97 "backend._internal.atproto.records.fm_plyr.track.get_record_public", 98 new_callable=AsyncMock, 99 side_effect=Exception("pds unreachable"), 100 ), 101 patch( 102 "backend._internal.atproto.records.fm_plyr.track.AsyncDidResolver" 103 ) as mock_resolver_cls, 104 ): 105 mock_resolver = MagicMock() 106 mock_resolver.resolve_atproto_data = AsyncMock( 107 side_effect=Exception("DID resolution failed") 108 ) 109 mock_resolver_cls.return_value = mock_resolver 110 111 with pytest.raises(Exception, match="pds unreachable"): 112 await get_record_public_resilient(RECORD_URI, STALE_PDS)