Add comprehensive NFO and media download tests

- Add 23 new unit tests for media downloads in test_nfo_service.py
- Create test_nfo_integration.py with 10 integration tests
- Test all media download scenarios (poster/logo/fanart)
- Test various image sizes and configurations
- Test concurrent NFO operations
- Test error handling and edge cases
- All 44 NFO service tests passing
- All 10 integration tests passing
This commit is contained in:
2026-01-17 22:18:54 +01:00
parent 22a41ba93f
commit c6919ac124
5 changed files with 824 additions and 65 deletions

View File

@@ -1,9 +1,10 @@
"""Unit tests for NFO service."""
import pytest
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from src.core.services.nfo_service import NFOService
from src.core.services.tmdb_client import TMDBAPIError
@@ -408,3 +409,245 @@ class TestNFOServiceEdgeCases:
# NFO now exists
exists = await nfo_service.check_nfo_exists("Test Series")
assert exists
class TestMediaDownloads:
"""Test media file (poster, logo, fanart) download functionality."""
@pytest.mark.asyncio
async def test_download_media_all_enabled(self, nfo_service, tmp_path, mock_tmdb_data):
"""Test downloading all media files when enabled."""
series_folder = tmp_path / "Test Series"
series_folder.mkdir()
with patch.object(nfo_service.image_downloader, 'download_all_media', new_callable=AsyncMock) as mock_download:
mock_download.return_value = {
"poster": True,
"logo": True,
"fanart": True
}
results = await nfo_service._download_media_files(
mock_tmdb_data,
series_folder,
download_poster=True,
download_logo=True,
download_fanart=True
)
assert results["poster"] is True
assert results["logo"] is True
assert results["fanart"] is True
mock_download.assert_called_once()
@pytest.mark.asyncio
async def test_download_media_poster_only(self, nfo_service, tmp_path, mock_tmdb_data):
"""Test downloading only poster."""
series_folder = tmp_path / "Test Series"
series_folder.mkdir()
with patch.object(nfo_service.image_downloader, 'download_all_media', new_callable=AsyncMock) as mock_download:
mock_download.return_value = {"poster": True}
results = await nfo_service._download_media_files(
mock_tmdb_data,
series_folder,
download_poster=True,
download_logo=False,
download_fanart=False
)
# Verify only poster URL was passed
call_args = mock_download.call_args
assert call_args.kwargs['poster_url'] is not None
assert call_args.kwargs['logo_url'] is None
assert call_args.kwargs['fanart_url'] is None
@pytest.mark.asyncio
async def test_download_media_with_image_size(self, nfo_service, tmp_path, mock_tmdb_data):
"""Test that image size configuration is used."""
nfo_service.image_size = "w500"
series_folder = tmp_path / "Test Series"
series_folder.mkdir()
with patch.object(nfo_service.image_downloader, 'download_all_media', new_callable=AsyncMock) as mock_download:
mock_download.return_value = {"poster": True}
await nfo_service._download_media_files(
mock_tmdb_data,
series_folder,
download_poster=True,
download_logo=False,
download_fanart=False
)
# Verify image size was used for poster
call_args = mock_download.call_args
poster_url = call_args.kwargs['poster_url']
assert "w500" in poster_url
@pytest.mark.asyncio
async def test_download_media_missing_poster_path(self, nfo_service, tmp_path):
"""Test media download when poster path is missing."""
series_folder = tmp_path / "Test Series"
series_folder.mkdir()
tmdb_data_no_poster = {
"id": 1,
"name": "Test",
"poster_path": None,
"backdrop_path": "/backdrop.jpg",
"images": {"logos": [{"file_path": "/logo.png"}]}
}
with patch.object(nfo_service.image_downloader, 'download_all_media', new_callable=AsyncMock) as mock_download:
mock_download.return_value = {}
await nfo_service._download_media_files(
tmdb_data_no_poster,
series_folder,
download_poster=True,
download_logo=True,
download_fanart=True
)
# Poster URL should be None
call_args = mock_download.call_args
assert call_args.kwargs['poster_url'] is None
@pytest.mark.asyncio
async def test_download_media_no_logo_available(self, nfo_service, tmp_path):
"""Test media download when logo is not available."""
series_folder = tmp_path / "Test Series"
series_folder.mkdir()
tmdb_data_no_logo = {
"id": 1,
"name": "Test",
"poster_path": "/poster.jpg",
"backdrop_path": "/backdrop.jpg",
"images": {"logos": []} # Empty logos array
}
with patch.object(nfo_service.image_downloader, 'download_all_media', new_callable=AsyncMock) as mock_download:
mock_download.return_value = {"poster": True, "fanart": True}
await nfo_service._download_media_files(
tmdb_data_no_logo,
series_folder,
download_poster=True,
download_logo=True,
download_fanart=True
)
# Logo URL should be None
call_args = mock_download.call_args
assert call_args.kwargs['logo_url'] is None
@pytest.mark.asyncio
async def test_download_media_all_disabled(self, nfo_service, tmp_path, mock_tmdb_data):
"""Test that no downloads occur when all disabled."""
series_folder = tmp_path / "Test Series"
series_folder.mkdir()
with patch.object(nfo_service.image_downloader, 'download_all_media', new_callable=AsyncMock) as mock_download:
mock_download.return_value = {}
await nfo_service._download_media_files(
mock_tmdb_data,
series_folder,
download_poster=False,
download_logo=False,
download_fanart=False
)
# All URLs should be None
call_args = mock_download.call_args
assert call_args.kwargs['poster_url'] is None
assert call_args.kwargs['logo_url'] is None
assert call_args.kwargs['fanart_url'] is None
@pytest.mark.asyncio
async def test_download_media_fanart_uses_original_size(self, nfo_service, tmp_path, mock_tmdb_data):
"""Test that fanart always uses original size regardless of config."""
nfo_service.image_size = "w500"
series_folder = tmp_path / "Test Series"
series_folder.mkdir()
with patch.object(nfo_service.image_downloader, 'download_all_media', new_callable=AsyncMock) as mock_download:
mock_download.return_value = {"fanart": True}
await nfo_service._download_media_files(
mock_tmdb_data,
series_folder,
download_poster=False,
download_logo=False,
download_fanart=True
)
# Fanart should use original size
call_args = mock_download.call_args
fanart_url = call_args.kwargs['fanart_url']
assert "original" in fanart_url
@pytest.mark.asyncio
async def test_download_media_logo_uses_original_size(self, nfo_service, tmp_path, mock_tmdb_data):
"""Test that logo always uses original size."""
nfo_service.image_size = "w500"
series_folder = tmp_path / "Test Series"
series_folder.mkdir()
with patch.object(nfo_service.image_downloader, 'download_all_media', new_callable=AsyncMock) as mock_download:
mock_download.return_value = {"logo": True}
await nfo_service._download_media_files(
mock_tmdb_data,
series_folder,
download_poster=False,
download_logo=True,
download_fanart=False
)
# Logo should use original size
call_args = mock_download.call_args
logo_url = call_args.kwargs['logo_url']
assert "original" in logo_url
class TestNFOServiceConfiguration:
"""Test NFO service with various configuration settings."""
def test_nfo_service_default_config(self, tmp_path):
"""Test NFO service initialization with default config."""
service = NFOService(
tmdb_api_key="test_key",
anime_directory=str(tmp_path)
)
assert service.image_size == "original"
assert service.auto_create is True
def test_nfo_service_custom_config(self, tmp_path):
"""Test NFO service initialization with custom config."""
service = NFOService(
tmdb_api_key="test_key",
anime_directory=str(tmp_path),
image_size="w500",
auto_create=False
)
assert service.image_size == "w500"
assert service.auto_create is False
def test_nfo_service_image_sizes(self, tmp_path):
"""Test NFO service with various image sizes."""
sizes = ["original", "w500", "w780", "w342"]
for size in sizes:
service = NFOService(
tmdb_api_key="test_key",
anime_directory=str(tmp_path),
image_size=size
)
assert service.image_size == size