diff --git a/backend/api/v1/routes/navidrome_library.py b/backend/api/v1/routes/navidrome_library.py index 6256d00..f82f26f 100644 --- a/backend/api/v1/routes/navidrome_library.py +++ b/backend/api/v1/routes/navidrome_library.py @@ -15,6 +15,7 @@ from api.v1.schemas.navidrome import ( from core.dependencies import get_navidrome_library_service, get_navidrome_repository from core.exceptions import ExternalServiceError from infrastructure.msgspec_fastapi import MsgSpecRoute +from infrastructure.resilience.retry import CircuitOpenError from repositories.navidrome_repository import NavidromeRepository from services.navidrome_library_service import NavidromeLibraryService @@ -50,7 +51,7 @@ async def get_navidrome_albums( try: stats = await service.get_stats() total = stats.total_albums if len(items) >= limit else offset + len(items) - except ExternalServiceError: + except (ExternalServiceError, CircuitOpenError): logger.warning("Navidrome stats unavailable, using heuristic pagination total") total = offset + len(items) + (1 if len(items) >= limit else 0) diff --git a/backend/tests/routes/test_navidrome_routes.py b/backend/tests/routes/test_navidrome_routes.py index 8121d95..77cf3e3 100644 --- a/backend/tests/routes/test_navidrome_routes.py +++ b/backend/tests/routes/test_navidrome_routes.py @@ -19,6 +19,7 @@ from api.v1.schemas.navidrome import ( ) from core.dependencies import get_navidrome_library_service, get_navidrome_playback_service from core.exceptions import ExternalServiceError +from infrastructure.resilience.retry import CircuitOpenError def _album_summary(id: str = "a1", name: str = "Album") -> NavidromeAlbumSummary: @@ -110,6 +111,18 @@ class TestLibraryAlbums: data = resp.json() assert data["total"] == 5 + def test_get_albums_stats_fallback_circuit_open(self, library_client, mock_library_service): + """When CB is open, stats raises CircuitOpenError — albums still work.""" + mock_library_service.get_albums = AsyncMock(return_value=[_album_summary(id=f"a{i}") for i in range(48)]) + mock_library_service.get_stats = AsyncMock( + side_effect=CircuitOpenError("Circuit breaker 'navidrome' is OPEN", breaker_name="navidrome"), + ) + resp = library_client.get("/navidrome/albums?limit=48") + assert resp.status_code == 200 + data = resp.json() + assert len(data["items"]) == 48 + assert data["total"] == 49 + def test_get_album_detail(self, library_client): resp = library_client.get("/navidrome/albums/a1") assert resp.status_code == 200