Files
musicseerr/backend/tests/routes/test_covers_routes.py
T
2026-04-03 15:53:00 +01:00

158 lines
5.9 KiB
Python

import pytest
from unittest.mock import AsyncMock, MagicMock, ANY
from fastapi import FastAPI
from fastapi.testclient import TestClient
from api.v1.routes.covers import router
from core.dependencies import get_coverart_repository
from core.exceptions import ClientDisconnectedError
from core.exception_handlers import client_disconnected_handler
@pytest.fixture
def mock_cover_repo():
mock = MagicMock()
mock.get_release_group_cover = AsyncMock(return_value=(b'rg-image', 'image/jpeg', 'lidarr'))
mock.get_release_cover = AsyncMock(return_value=(b'rel-image', 'image/jpeg', 'jellyfin'))
mock.get_artist_image = AsyncMock(return_value=(b'artist-image', 'image/png', 'wikidata'))
mock.get_release_group_cover_etag = AsyncMock(return_value='etag-rg')
mock.get_release_cover_etag = AsyncMock(return_value='etag-rel')
mock.get_artist_image_etag = AsyncMock(return_value='etag-artist')
mock.debug_artist_image = AsyncMock(side_effect=lambda _artist_id, debug_info: debug_info)
return mock
@pytest.fixture
def client(mock_cover_repo):
app = FastAPI()
app.include_router(router)
app.dependency_overrides[get_coverart_repository] = lambda: mock_cover_repo
app.add_exception_handler(ClientDisconnectedError, client_disconnected_handler)
return TestClient(app)
def test_release_group_uses_dynamic_source_header(client):
response = client.get('/covers/release-group/11111111-1111-1111-1111-111111111111?size=500')
assert response.status_code == 200
assert response.headers['x-cover-source'] == 'lidarr'
def test_release_uses_dynamic_source_header(client):
response = client.get('/covers/release/22222222-2222-2222-2222-222222222222?size=500')
assert response.status_code == 200
assert response.headers['x-cover-source'] == 'jellyfin'
def test_artist_uses_dynamic_source_header(client, mock_cover_repo):
mock_cover_repo.get_artist_image = AsyncMock(return_value=(b'artist-image', 'image/png', 'lidarr'))
response = client.get('/covers/artist/33333333-3333-3333-3333-333333333333?size=250')
assert response.status_code == 200
assert response.headers['x-cover-source'] == 'lidarr'
def test_release_group_uses_placeholder_header_when_missing(client, mock_cover_repo):
mock_cover_repo.get_release_group_cover = AsyncMock(return_value=None)
response = client.get('/covers/release-group/44444444-4444-4444-4444-444444444444?size=500')
assert response.status_code == 200
assert response.headers['x-cover-source'] == 'placeholder'
def test_artist_uses_placeholder_header_when_missing(client, mock_cover_repo):
mock_cover_repo.get_artist_image = AsyncMock(return_value=None)
response = client.get('/covers/artist/55555555-5555-5555-5555-555555555555')
assert response.status_code == 200
assert response.headers['x-cover-source'] == 'placeholder'
def test_release_group_original_size_maps_to_none(client, mock_cover_repo):
response = client.get('/covers/release-group/66666666-6666-6666-6666-666666666666?size=original')
assert response.status_code == 200
mock_cover_repo.get_release_group_cover.assert_awaited_once_with(
'66666666-6666-6666-6666-666666666666',
None,
is_disconnected=ANY,
)
def test_release_rejects_invalid_size(client):
response = client.get('/covers/release/77777777-7777-7777-7777-777777777777?size=999')
assert response.status_code == 400
def test_release_group_sets_etag_header(client):
response = client.get('/covers/release-group/11111111-1111-1111-1111-111111111111?size=500')
assert response.status_code == 200
assert response.headers['etag'] == '"etag-rg"'
def test_release_group_returns_304_when_etag_matches(client, mock_cover_repo):
response = client.get(
'/covers/release-group/11111111-1111-1111-1111-111111111111?size=500',
headers={'If-None-Match': '"etag-rg"'},
)
assert response.status_code == 304
mock_cover_repo.get_release_group_cover.assert_not_awaited()
def test_artist_returns_304_when_etag_matches(client, mock_cover_repo):
response = client.get(
'/covers/artist/33333333-3333-3333-3333-333333333333?size=250',
headers={'If-None-Match': '"etag-artist"'},
)
assert response.status_code == 304
mock_cover_repo.get_artist_image.assert_not_awaited()
def test_debug_artist_cover_recommends_negative_cache(client, mock_cover_repo):
async def _debug_with_negative(_artist_id, debug_info):
debug_info['disk_cache']['negative_250'] = True
return debug_info
mock_cover_repo.debug_artist_image = AsyncMock(side_effect=_debug_with_negative)
response = client.get('/covers/debug/artist/33333333-3333-3333-3333-333333333333')
assert response.status_code == 200
assert 'negative cache entry' in response.json()['recommendation'].lower()
def test_release_group_returns_204_on_disconnect(client, mock_cover_repo):
mock_cover_repo.get_release_group_cover_etag = AsyncMock(return_value=None)
mock_cover_repo.get_release_group_cover = AsyncMock(
side_effect=ClientDisconnectedError("disconnected")
)
response = client.get('/covers/release-group/66666666-6666-6666-6666-666666666666')
assert response.status_code == 204
def test_release_returns_204_on_disconnect(client, mock_cover_repo):
mock_cover_repo.get_release_cover_etag = AsyncMock(return_value=None)
mock_cover_repo.get_release_cover = AsyncMock(
side_effect=ClientDisconnectedError("disconnected")
)
response = client.get('/covers/release/66666666-6666-6666-6666-666666666666')
assert response.status_code == 204
def test_artist_returns_204_on_disconnect(client, mock_cover_repo):
mock_cover_repo.get_artist_image_etag = AsyncMock(return_value=None)
mock_cover_repo.get_artist_image = AsyncMock(
side_effect=ClientDisconnectedError("disconnected")
)
response = client.get('/covers/artist/33333333-3333-3333-3333-333333333333')
assert response.status_code == 204