Files
Aniworld/tests/integration/test_anime_add_nfo_isolation.py
Lukas 0d2ce07ad7 fix: resolve all failing tests across unit, integration, and performance suites
- 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
2026-02-15 17:49:11 +01:00

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()