"""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.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 """ # Create background loader service loader_service = BackgroundLoaderService( websocket_service=mock_websocket_service, anime_service=mock_anime_service, series_app=mock_series_app ) # Start the worker await loader_service.start() try: # Add a new anime to the loading queue new_anime_key = "new-anime" new_anime_folder = "New Anime (2024)" new_anime_name = "New Anime" new_anime_year = 2024 # Create directory for the new anime new_anime_dir = Path(temp_anime_dir) / new_anime_folder new_anime_dir.mkdir() # Queue the loading task await loader_service.add_series_loading_task( key=new_anime_key, folder=new_anime_folder, name=new_anime_name, year=new_anime_year ) # Wait for the task to be processed await asyncio.sleep(0.5) # Verify NFO service was called exactly once assert mock_series_app.nfo_service.create_tvshow_nfo.call_count == 1 # Verify the call was made with the correct parameters for the NEW anime only call_args = mock_series_app.nfo_service.create_tvshow_nfo.call_args assert call_args is not None # Check positional and keyword arguments 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 # Verify that existing anime were NOT processed # The NFO service should not be called with "Existing Anime 1" or "Existing Anime 2" 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: # Stop the worker 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.""" # Create background loader service loader_service = BackgroundLoaderService( websocket_service=mock_websocket_service, anime_service=mock_anime_service, series_app=mock_series_app ) 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() # Queue the loading task await loader_service.add_series_loading_task( key="specific-anime", folder=new_anime_folder, name="Specific Anime", year=2024 ) # Wait for processing await asyncio.sleep(0.5) # Verify has_nfo was called with the correct folder assert mock_series_app.nfo_service.has_nfo.call_count >= 1 # Verify it was called with the NEW anime folder, not existing ones 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 ) await loader_service.start() try: # Add three new anime 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 ) # Wait for all tasks to be processed await asyncio.sleep(1.5) # Verify NFO service was called exactly 3 times (once for each) assert mock_series_app.nfo_service.create_tvshow_nfo.call_count == 3 # Verify each call was made with the correct parameters all_calls = mock_series_app.nfo_service.create_tvshow_nfo.call_args_list # Extract the anime names from the calls 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] # Verify each anime was processed 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 # Verify existing anime were not processed 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 ) await loader_service.start() try: # Add an anime with specific metadata 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(0.5) # Verify the NFO service call has all the correct parameters 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 # Verify no other anime metadata was used assert "Existing Anime" not in str(call_kwargs) finally: await loader_service.stop()