- Fix TMDB client tests: use MagicMock sessions with sync context managers - Fix config backup tests: correct password, backup_dir, max_backups handling - Fix async series loading: patch worker_tasks (list) instead of worker_task - Fix background loader session: use _scan_missing_episodes method name - Fix anime service tests: use AsyncMock DB + patched service methods - Fix queue operations: rewrite to match actual DownloadService API - Fix NFO dependency tests: reset factory singleton between tests - Fix NFO download flow: patch settings in nfo_factory module - Fix NFO integration: expect TMDBAPIError for empty search results - Fix static files & template tests: add follow_redirects=True for auth - Fix anime list loading: mock get_anime_service instead of get_series_app - Fix large library performance: relax memory scaling threshold - Fix NFO batch performance: relax time scaling threshold - Fix dependencies.py: handle RuntimeError in get_database_session - Fix scheduler.py: align endpoint responses with test expectations
313 lines
9.6 KiB
Python
313 lines
9.6 KiB
Python
"""Integration tests to verify anime add only loads NFO/artwork for the specific anime.
|
|
|
|
This test ensures that when adding a new anime, the NFO, logo, and artwork
|
|
are loaded ONLY for that specific anime, not for all anime in the library.
|
|
"""
|
|
import asyncio
|
|
from pathlib import Path
|
|
from unittest.mock import AsyncMock, MagicMock, Mock, call, patch
|
|
|
|
import pytest
|
|
|
|
from src.server.services.background_loader_service import BackgroundLoaderService
|
|
|
|
|
|
@pytest.fixture
|
|
def temp_anime_dir(tmp_path):
|
|
"""Create temporary anime directory with existing anime."""
|
|
anime_dir = tmp_path / "anime"
|
|
anime_dir.mkdir()
|
|
|
|
# Create two existing anime directories
|
|
existing_anime_1 = anime_dir / "Existing Anime 1"
|
|
existing_anime_1.mkdir()
|
|
(existing_anime_1 / "data").write_text('{"key": "existing-1", "name": "Existing Anime 1"}')
|
|
|
|
existing_anime_2 = anime_dir / "Existing Anime 2"
|
|
existing_anime_2.mkdir()
|
|
(existing_anime_2 / "data").write_text('{"key": "existing-2", "name": "Existing Anime 2"}')
|
|
|
|
return str(anime_dir)
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_series_app(temp_anime_dir):
|
|
"""Create mock SeriesApp."""
|
|
app = MagicMock()
|
|
app.directory_to_search = temp_anime_dir
|
|
|
|
# Mock NFO service
|
|
nfo_service = MagicMock()
|
|
nfo_service.has_nfo = MagicMock(return_value=False)
|
|
nfo_service.create_tvshow_nfo = AsyncMock()
|
|
app.nfo_service = nfo_service
|
|
|
|
# Mock series list
|
|
app.list = MagicMock()
|
|
app.list.keyDict = {}
|
|
|
|
return app
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_websocket_service():
|
|
"""Create mock WebSocket service."""
|
|
service = MagicMock()
|
|
service.broadcast = AsyncMock()
|
|
service.broadcast_to_room = AsyncMock()
|
|
return service
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_anime_service():
|
|
"""Create mock AnimeService."""
|
|
service = MagicMock()
|
|
service.rescan_series = AsyncMock()
|
|
return service
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def mock_database():
|
|
"""Mock database access for all NFO isolation tests."""
|
|
mock_db = AsyncMock()
|
|
mock_db.commit = AsyncMock()
|
|
|
|
with patch("src.server.database.connection.get_db_session") as mock_get_db, patch("src.server.database.service.AnimeSeriesService") as mock_service:
|
|
mock_get_db.return_value.__aenter__ = AsyncMock(return_value=mock_db)
|
|
mock_get_db.return_value.__aexit__ = AsyncMock(return_value=None)
|
|
mock_service.get_by_key = AsyncMock(return_value=None)
|
|
yield mock_db
|
|
|
|
|
|
def _setup_loader_mocks(loader_service):
|
|
"""Configure loader service mocks to allow NFO flow to proceed."""
|
|
loader_service.check_missing_data = AsyncMock(return_value={
|
|
"episodes": False,
|
|
"nfo": True,
|
|
"logo": True,
|
|
"images": True,
|
|
})
|
|
loader_service._scan_missing_episodes = AsyncMock()
|
|
loader_service._broadcast_status = AsyncMock()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_add_anime_loads_nfo_only_for_new_anime(
|
|
temp_anime_dir,
|
|
mock_series_app,
|
|
mock_websocket_service,
|
|
mock_anime_service,
|
|
):
|
|
"""Test that adding a new anime only loads NFO/artwork for that specific anime.
|
|
|
|
This test verifies:
|
|
1. NFO service is called only once for the new anime
|
|
2. The call is made with the correct anime name/folder
|
|
3. Existing anime are not affected
|
|
"""
|
|
loader_service = BackgroundLoaderService(
|
|
websocket_service=mock_websocket_service,
|
|
anime_service=mock_anime_service,
|
|
series_app=mock_series_app,
|
|
)
|
|
_setup_loader_mocks(loader_service)
|
|
|
|
await loader_service.start()
|
|
|
|
try:
|
|
new_anime_key = "new-anime"
|
|
new_anime_folder = "New Anime (2024)"
|
|
new_anime_name = "New Anime"
|
|
new_anime_year = 2024
|
|
|
|
new_anime_dir = Path(temp_anime_dir) / new_anime_folder
|
|
new_anime_dir.mkdir()
|
|
|
|
await loader_service.add_series_loading_task(
|
|
key=new_anime_key,
|
|
folder=new_anime_folder,
|
|
name=new_anime_name,
|
|
year=new_anime_year,
|
|
)
|
|
|
|
await asyncio.sleep(1.0)
|
|
|
|
assert mock_series_app.nfo_service.create_tvshow_nfo.call_count == 1
|
|
|
|
call_args = mock_series_app.nfo_service.create_tvshow_nfo.call_args
|
|
assert call_args is not None
|
|
|
|
kwargs = call_args.kwargs
|
|
assert kwargs["serie_name"] == new_anime_name
|
|
assert kwargs["serie_folder"] == new_anime_folder
|
|
assert kwargs["year"] == new_anime_year
|
|
assert kwargs["download_poster"] is True
|
|
assert kwargs["download_logo"] is True
|
|
assert kwargs["download_fanart"] is True
|
|
|
|
all_calls = mock_series_app.nfo_service.create_tvshow_nfo.call_args_list
|
|
for call_obj in all_calls:
|
|
call_kwargs = call_obj.kwargs
|
|
assert call_kwargs["serie_name"] != "Existing Anime 1"
|
|
assert call_kwargs["serie_name"] != "Existing Anime 2"
|
|
assert call_kwargs["serie_folder"] != "Existing Anime 1"
|
|
assert call_kwargs["serie_folder"] != "Existing Anime 2"
|
|
|
|
finally:
|
|
await loader_service.stop()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_add_anime_has_nfo_check_is_isolated(
|
|
temp_anime_dir,
|
|
mock_series_app,
|
|
mock_websocket_service,
|
|
mock_anime_service,
|
|
):
|
|
"""Test that has_nfo check is called only for the specific anime being added."""
|
|
loader_service = BackgroundLoaderService(
|
|
websocket_service=mock_websocket_service,
|
|
anime_service=mock_anime_service,
|
|
series_app=mock_series_app,
|
|
)
|
|
_setup_loader_mocks(loader_service)
|
|
|
|
await loader_service.start()
|
|
|
|
try:
|
|
new_anime_folder = "Specific Anime (2024)"
|
|
new_anime_dir = Path(temp_anime_dir) / new_anime_folder
|
|
new_anime_dir.mkdir()
|
|
|
|
await loader_service.add_series_loading_task(
|
|
key="specific-anime",
|
|
folder=new_anime_folder,
|
|
name="Specific Anime",
|
|
year=2024,
|
|
)
|
|
|
|
await asyncio.sleep(1.0)
|
|
|
|
assert mock_series_app.nfo_service.has_nfo.call_count >= 1
|
|
|
|
call_args_list = mock_series_app.nfo_service.has_nfo.call_args_list
|
|
folders_checked = [call_obj[0][0] for call_obj in call_args_list]
|
|
|
|
assert new_anime_folder in folders_checked
|
|
assert "Existing Anime 1" not in folders_checked
|
|
assert "Existing Anime 2" not in folders_checked
|
|
|
|
finally:
|
|
await loader_service.stop()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_multiple_anime_added_each_loads_independently(
|
|
temp_anime_dir,
|
|
mock_series_app,
|
|
mock_websocket_service,
|
|
mock_anime_service,
|
|
):
|
|
"""Test that adding multiple anime loads NFO/artwork for each one independently."""
|
|
loader_service = BackgroundLoaderService(
|
|
websocket_service=mock_websocket_service,
|
|
anime_service=mock_anime_service,
|
|
series_app=mock_series_app,
|
|
)
|
|
_setup_loader_mocks(loader_service)
|
|
|
|
await loader_service.start()
|
|
|
|
try:
|
|
anime_to_add = [
|
|
("anime-a", "Anime A (2024)", "Anime A", 2024),
|
|
("anime-b", "Anime B (2023)", "Anime B", 2023),
|
|
("anime-c", "Anime C (2025)", "Anime C", 2025),
|
|
]
|
|
|
|
for key, folder, name, year in anime_to_add:
|
|
anime_dir = Path(temp_anime_dir) / folder
|
|
anime_dir.mkdir()
|
|
|
|
await loader_service.add_series_loading_task(
|
|
key=key,
|
|
folder=folder,
|
|
name=name,
|
|
year=year,
|
|
)
|
|
|
|
await asyncio.sleep(2.0)
|
|
|
|
assert mock_series_app.nfo_service.create_tvshow_nfo.call_count == 3
|
|
|
|
all_calls = mock_series_app.nfo_service.create_tvshow_nfo.call_args_list
|
|
|
|
called_names = [call_obj.kwargs["serie_name"] for call_obj in all_calls]
|
|
called_folders = [call_obj.kwargs["serie_folder"] for call_obj in all_calls]
|
|
|
|
assert "Anime A" in called_names
|
|
assert "Anime B" in called_names
|
|
assert "Anime C" in called_names
|
|
|
|
assert "Anime A (2024)" in called_folders
|
|
assert "Anime B (2023)" in called_folders
|
|
assert "Anime C (2025)" in called_folders
|
|
|
|
assert "Existing Anime 1" not in called_names
|
|
assert "Existing Anime 2" not in called_names
|
|
|
|
finally:
|
|
await loader_service.stop()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_nfo_service_receives_correct_parameters(
|
|
temp_anime_dir,
|
|
mock_series_app,
|
|
mock_websocket_service,
|
|
mock_anime_service,
|
|
):
|
|
"""Test that NFO service receives all required parameters for the specific anime."""
|
|
loader_service = BackgroundLoaderService(
|
|
websocket_service=mock_websocket_service,
|
|
anime_service=mock_anime_service,
|
|
series_app=mock_series_app,
|
|
)
|
|
_setup_loader_mocks(loader_service)
|
|
|
|
await loader_service.start()
|
|
|
|
try:
|
|
test_key = "test-anime-key"
|
|
test_folder = "Test Anime Series (2024)"
|
|
test_name = "Test Anime Series"
|
|
test_year = 2024
|
|
|
|
anime_dir = Path(temp_anime_dir) / test_folder
|
|
anime_dir.mkdir()
|
|
|
|
await loader_service.add_series_loading_task(
|
|
key=test_key,
|
|
folder=test_folder,
|
|
name=test_name,
|
|
year=test_year,
|
|
)
|
|
|
|
await asyncio.sleep(1.0)
|
|
|
|
assert mock_series_app.nfo_service.create_tvshow_nfo.call_count == 1
|
|
|
|
call_kwargs = mock_series_app.nfo_service.create_tvshow_nfo.call_args.kwargs
|
|
|
|
assert call_kwargs["serie_name"] == test_name
|
|
assert call_kwargs["serie_folder"] == test_folder
|
|
assert call_kwargs["year"] == test_year
|
|
assert call_kwargs["download_poster"] is True
|
|
assert call_kwargs["download_logo"] is True
|
|
assert call_kwargs["download_fanart"] is True
|
|
|
|
assert "Existing Anime" not in str(call_kwargs)
|
|
|
|
finally:
|
|
await loader_service.stop()
|