feat: add ImageLoadingService for downloading series artwork
- Create ImageLoadingService that downloads poster.jpg, fanart.jpg, and logo.png from TMDB when anime is added or during scheduler rescan - Integrate into BackgroundLoaderService._load_nfo_and_images() to trigger image downloads when new anime is added - Add image_scan_after_rescan config option to scheduler (default: true) - Add _run_image_scan() to scheduler rescan flow, processing series in batches of 10 to respect TMDB rate limits - Fix SearchResult model missing folder, snippet, and score fields - Update background_loader tests to match new image loading behavior
This commit is contained in:
@@ -512,14 +512,16 @@ class TestScanSeriesEpisodes:
|
||||
|
||||
|
||||
class TestLoadNfoAndImages:
|
||||
"""Tests for loading NFO files and images."""
|
||||
|
||||
"""Tests for loading NFO files and images via ImageLoadingService.
|
||||
|
||||
These tests verify the behavior of _load_nfo_and_images by mocking
|
||||
the external dependencies at the correct import locations.
|
||||
"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_nfo_creates_new_nfo(self, background_loader_service, mock_websocket_service):
|
||||
"""Test creating new NFO file - NFO service removed, stub returns False."""
|
||||
async def test_load_nfo_and_images_success(self, background_loader_service):
|
||||
"""Test successful image loading via ImageLoadingService."""
|
||||
mock_db = AsyncMock()
|
||||
mock_series = MagicMock()
|
||||
mock_series.has_nfo = False
|
||||
|
||||
task = SeriesLoadingTask(
|
||||
key="test",
|
||||
@@ -528,51 +530,78 @@ class TestLoadNfoAndImages:
|
||||
year=2020
|
||||
)
|
||||
|
||||
# NFO service removed, _load_nfo_and_images is now a stub that returns False
|
||||
result = await background_loader_service._load_nfo_and_images(task, mock_db)
|
||||
# Mock get_tmdb_client and init_image_loading_service at source
|
||||
with patch("src.server.nfo.tmdb_client.get_tmdb_client") as mock_get_tmdb:
|
||||
mock_tmdb = MagicMock()
|
||||
mock_get_tmdb.return_value = mock_tmdb
|
||||
|
||||
with patch("src.server.services.image_loading_service.ImageLoadingService") as mock_service_class:
|
||||
mock_service = MagicMock()
|
||||
mock_service.load_series_images = AsyncMock(return_value={
|
||||
"poster": True,
|
||||
"fanart": True,
|
||||
"logo": True,
|
||||
})
|
||||
mock_service_class.return_value = mock_service
|
||||
|
||||
result = await background_loader_service._load_nfo_and_images(task, mock_db)
|
||||
|
||||
assert result is True
|
||||
assert task.progress["nfo"] is True
|
||||
assert task.progress["logo"] is True
|
||||
assert task.progress["images"] is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_nfo_and_images_partial(self, background_loader_service):
|
||||
"""Test partial image loading (some images fail)."""
|
||||
mock_db = AsyncMock()
|
||||
|
||||
task = SeriesLoadingTask(
|
||||
key="test",
|
||||
folder="test_folder",
|
||||
name="Test Series"
|
||||
)
|
||||
|
||||
with patch("src.server.nfo.tmdb_client.get_tmdb_client") as mock_get_tmdb:
|
||||
mock_tmdb = MagicMock()
|
||||
mock_get_tmdb.return_value = mock_tmdb
|
||||
|
||||
with patch("src.server.services.image_loading_service.ImageLoadingService") as mock_service_class:
|
||||
mock_service = MagicMock()
|
||||
mock_service.load_series_images = AsyncMock(return_value={
|
||||
"poster": True,
|
||||
"fanart": False,
|
||||
"logo": True,
|
||||
})
|
||||
mock_service_class.return_value = mock_service
|
||||
|
||||
result = await background_loader_service._load_nfo_and_images(task, mock_db)
|
||||
|
||||
assert result is True
|
||||
assert task.progress["nfo"] is True
|
||||
assert task.progress["logo"] is True
|
||||
assert task.progress["images"] is False
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_nfo_and_images_service_error(self, background_loader_service):
|
||||
"""Test graceful handling when ImageLoadingService throws exception."""
|
||||
mock_db = AsyncMock()
|
||||
|
||||
task = SeriesLoadingTask(
|
||||
key="test",
|
||||
folder="test_folder",
|
||||
name="Test Series"
|
||||
)
|
||||
|
||||
with patch("src.server.nfo.tmdb_client.get_tmdb_client") as mock_get_tmdb:
|
||||
mock_get_tmdb.side_effect = Exception("TMDB API error")
|
||||
|
||||
result = await background_loader_service._load_nfo_and_images(task, mock_db)
|
||||
|
||||
# Stub returns False since NFO service was removed
|
||||
assert result is False
|
||||
assert task.progress["nfo"] is False
|
||||
assert task.progress["nfo"] is True
|
||||
assert task.progress["logo"] is False
|
||||
assert task.progress["images"] is False
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_nfo_uses_existing(self, background_loader_service):
|
||||
"""Test using existing NFO file - NFO service removed, stub returns False."""
|
||||
mock_db = AsyncMock()
|
||||
mock_series = MagicMock()
|
||||
mock_series.has_nfo = True
|
||||
|
||||
task = SeriesLoadingTask(
|
||||
key="test",
|
||||
folder="test_folder",
|
||||
name="Test Series"
|
||||
)
|
||||
|
||||
# NFO service removed, _load_nfo_and_images is now a stub that returns False
|
||||
result = await background_loader_service._load_nfo_and_images(task, mock_db)
|
||||
|
||||
# Stub returns False since NFO service was removed
|
||||
assert result is False
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_nfo_without_nfo_service(self, background_loader_service):
|
||||
"""Test graceful handling when NFO service not available."""
|
||||
background_loader_service.series_app.nfo_service = None
|
||||
|
||||
mock_db = AsyncMock()
|
||||
task = SeriesLoadingTask(
|
||||
key="test",
|
||||
folder="test_folder",
|
||||
name="Test Series"
|
||||
)
|
||||
|
||||
result = await background_loader_service._load_nfo_and_images(task, mock_db)
|
||||
|
||||
assert result is False
|
||||
assert task.progress["nfo"] is False
|
||||
assert task.progress["logo"] is False
|
||||
|
||||
|
||||
class TestScanMissingEpisodes:
|
||||
@@ -813,23 +842,21 @@ class TestErrorHandling:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_nfo_error_handling(self, background_loader_service):
|
||||
"""Test error handling during NFO creation."""
|
||||
background_loader_service.series_app.nfo_service.has_nfo = MagicMock(return_value=False)
|
||||
background_loader_service.series_app.nfo_service.create_tvshow_nfo = AsyncMock(
|
||||
side_effect=Exception("API error")
|
||||
)
|
||||
|
||||
"""Test error handling when ImageLoadingService fails."""
|
||||
mock_db = AsyncMock()
|
||||
task = SeriesLoadingTask(
|
||||
key="test",
|
||||
folder="test_folder",
|
||||
name="Test Series"
|
||||
)
|
||||
|
||||
result = await background_loader_service._load_nfo_and_images(task, mock_db)
|
||||
|
||||
|
||||
with patch("src.server.nfo.tmdb_client.get_tmdb_client") as mock_get_tmdb:
|
||||
mock_get_tmdb.side_effect = Exception("TMDB API error")
|
||||
|
||||
result = await background_loader_service._load_nfo_and_images(task, mock_db)
|
||||
|
||||
assert result is False
|
||||
assert task.progress["nfo"] is False
|
||||
assert task.progress["nfo"] is True # Optimistic - NFO created elsewhere
|
||||
assert task.progress["logo"] is False
|
||||
assert task.progress["images"] is False
|
||||
|
||||
|
||||
Reference in New Issue
Block a user