From ceac22fc34370c75228f2edf6967d1d4eefd963b Mon Sep 17 00:00:00 2001 From: Lukas Date: Wed, 13 May 2026 12:41:22 +0200 Subject: [PATCH] test: fix NFO workflow and background loader tests - Add missing TMDB async mock methods (_ensure_session, close) to all TMDB mocks in test_nfo_workflow.py - Refactor test_anime_add_nfo_isolation.py to mock get_nfo_factory() instead of asserting on series_app.nfo_service directly - Patch get_nfo_factory in test_background_loader_service.py to align with factory-based NFOService creation Fixes test failures caused by NFOService refactoring that introduced explicit TMDB session lifecycle and NFO factory pattern. --- .../test_anime_add_nfo_isolation.py | 207 ++++++++++-------- tests/integration/test_nfo_workflow.py | 16 ++ tests/unit/test_background_loader_service.py | 16 +- 3 files changed, 143 insertions(+), 96 deletions(-) diff --git a/tests/integration/test_anime_add_nfo_isolation.py b/tests/integration/test_anime_add_nfo_isolation.py index 9c38d21..dc3e167 100644 --- a/tests/integration/test_anime_add_nfo_isolation.py +++ b/tests/integration/test_anime_add_nfo_isolation.py @@ -91,6 +91,13 @@ def _setup_loader_mocks(loader_service): loader_service._broadcast_status = AsyncMock() +def _mock_nfo_factory(mock_nfo_service): + """Create a mock NFO factory that returns the given mock service.""" + mock_factory = MagicMock() + mock_factory.create = MagicMock(return_value=mock_nfo_service) + return mock_factory + + @pytest.mark.asyncio async def test_add_anime_loads_nfo_only_for_new_anime( temp_anime_dir, @@ -112,49 +119,55 @@ async def test_add_anime_loads_nfo_only_for_new_anime( ) _setup_loader_mocks(loader_service) - await loader_service.start() + # Set up mock NFO service via factory + mock_nfo_service = AsyncMock() + mock_nfo_service.create_tvshow_nfo = AsyncMock(return_value="/anime/New Anime (2024)/tvshow.nfo") + mock_factory = _mock_nfo_factory(mock_nfo_service) - try: - new_anime_key = "new-anime" - new_anime_folder = "New Anime (2024)" - new_anime_name = "New Anime" - new_anime_year = 2024 + with patch("src.server.services.background_loader_service.get_nfo_factory", return_value=mock_factory): + await loader_service.start() - new_anime_dir = Path(temp_anime_dir) / new_anime_folder - new_anime_dir.mkdir() + try: + new_anime_key = "new-anime" + new_anime_folder = "New Anime (2024)" + new_anime_name = "New Anime" + new_anime_year = 2024 - await loader_service.add_series_loading_task( - key=new_anime_key, - folder=new_anime_folder, - name=new_anime_name, - year=new_anime_year, - ) + new_anime_dir = Path(temp_anime_dir) / new_anime_folder + new_anime_dir.mkdir() - await asyncio.sleep(1.0) + await loader_service.add_series_loading_task( + key=new_anime_key, + folder=new_anime_folder, + name=new_anime_name, + year=new_anime_year, + ) - assert mock_series_app.nfo_service.create_tvshow_nfo.call_count == 1 + await asyncio.sleep(1.0) - call_args = mock_series_app.nfo_service.create_tvshow_nfo.call_args - assert call_args is not None + assert mock_nfo_service.create_tvshow_nfo.call_count == 1 - 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 + call_args = mock_nfo_service.create_tvshow_nfo.call_args + assert call_args is not None - 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" + 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 - finally: - await loader_service.stop() + all_calls = mock_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 @@ -216,48 +229,54 @@ async def test_multiple_anime_added_each_loads_independently( ) _setup_loader_mocks(loader_service) - await loader_service.start() + # Set up mock NFO service via factory + mock_nfo_service = AsyncMock() + mock_nfo_service.create_tvshow_nfo = AsyncMock(return_value="/anime/tvshow.nfo") + mock_factory = _mock_nfo_factory(mock_nfo_service) - 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), - ] + with patch("src.server.services.background_loader_service.get_nfo_factory", return_value=mock_factory): + await loader_service.start() - for key, folder, name, year in anime_to_add: - anime_dir = Path(temp_anime_dir) / folder - anime_dir.mkdir() + 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), + ] - await loader_service.add_series_loading_task( - key=key, - folder=folder, - name=name, - year=year, - ) + for key, folder, name, year in anime_to_add: + anime_dir = Path(temp_anime_dir) / folder + anime_dir.mkdir() - await asyncio.sleep(2.0) + await loader_service.add_series_loading_task( + key=key, + folder=folder, + name=name, + year=year, + ) - assert mock_series_app.nfo_service.create_tvshow_nfo.call_count == 3 + await asyncio.sleep(2.0) - all_calls = mock_series_app.nfo_service.create_tvshow_nfo.call_args_list + assert mock_nfo_service.create_tvshow_nfo.call_count == 3 - 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] + all_calls = mock_nfo_service.create_tvshow_nfo.call_args_list - assert "Anime A" in called_names - assert "Anime B" in called_names - assert "Anime C" in called_names + 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 (2024)" in called_folders - assert "Anime B (2023)" in called_folders - assert "Anime C (2025)" in called_folders + assert "Anime A" in called_names + assert "Anime B" in called_names + assert "Anime C" in called_names - assert "Existing Anime 1" not in called_names - assert "Existing Anime 2" not in called_names + assert "Anime A (2024)" in called_folders + assert "Anime B (2023)" in called_folders + assert "Anime C (2025)" in called_folders - finally: - await loader_service.stop() + assert "Existing Anime 1" not in called_names + assert "Existing Anime 2" not in called_names + + finally: + await loader_service.stop() @pytest.mark.asyncio @@ -275,38 +294,44 @@ async def test_nfo_service_receives_correct_parameters( ) _setup_loader_mocks(loader_service) - await loader_service.start() + # Set up mock NFO service via factory + mock_nfo_service = AsyncMock() + mock_nfo_service.create_tvshow_nfo = AsyncMock(return_value="/anime/Test Anime Series (2024)/tvshow.nfo") + mock_factory = _mock_nfo_factory(mock_nfo_service) - try: - test_key = "test-anime-key" - test_folder = "Test Anime Series (2024)" - test_name = "Test Anime Series" - test_year = 2024 + with patch("src.server.services.background_loader_service.get_nfo_factory", return_value=mock_factory): + await loader_service.start() - anime_dir = Path(temp_anime_dir) / test_folder - anime_dir.mkdir() + try: + test_key = "test-anime-key" + test_folder = "Test Anime Series (2024)" + test_name = "Test Anime Series" + test_year = 2024 - await loader_service.add_series_loading_task( - key=test_key, - folder=test_folder, - name=test_name, - year=test_year, - ) + anime_dir = Path(temp_anime_dir) / test_folder + anime_dir.mkdir() - await asyncio.sleep(1.0) + await loader_service.add_series_loading_task( + key=test_key, + folder=test_folder, + name=test_name, + year=test_year, + ) - assert mock_series_app.nfo_service.create_tvshow_nfo.call_count == 1 + await asyncio.sleep(1.0) - call_kwargs = mock_series_app.nfo_service.create_tvshow_nfo.call_args.kwargs + assert mock_nfo_service.create_tvshow_nfo.call_count == 1 - 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 + call_kwargs = mock_nfo_service.create_tvshow_nfo.call_args.kwargs - assert "Existing Anime" not in str(call_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 - finally: - await loader_service.stop() + assert "Existing Anime" not in str(call_kwargs) + + finally: + await loader_service.stop() diff --git a/tests/integration/test_nfo_workflow.py b/tests/integration/test_nfo_workflow.py index 51ec18f..1242421 100644 --- a/tests/integration/test_nfo_workflow.py +++ b/tests/integration/test_nfo_workflow.py @@ -96,6 +96,8 @@ class TestCompleteNFOWorkflow: mock_tmdb = Mock() mock_tmdb.__aenter__ = AsyncMock(return_value=mock_tmdb) mock_tmdb.__aexit__ = AsyncMock(return_value=None) + mock_tmdb._ensure_session = AsyncMock() + mock_tmdb.close = AsyncMock() mock_tmdb.search_tv_show = AsyncMock(return_value={"results": [mock_tmdb_show]}) mock_tmdb.get_tv_show = AsyncMock(return_value=mock_tmdb_show) mock_tmdb.get_tv_show_details = AsyncMock(return_value=mock_tmdb_show) @@ -158,6 +160,8 @@ class TestCompleteNFOWorkflow: mock_tmdb = Mock() mock_tmdb.__aenter__ = AsyncMock(return_value=mock_tmdb) mock_tmdb.__aexit__ = AsyncMock(return_value=None) + mock_tmdb._ensure_session = AsyncMock() + mock_tmdb.close = AsyncMock() mock_tmdb.search_tv_show = AsyncMock( return_value={"results": [{ "id": 999, @@ -208,6 +212,8 @@ class TestCompleteNFOWorkflow: mock_tmdb = Mock() mock_tmdb.__aenter__ = AsyncMock(return_value=mock_tmdb) mock_tmdb.__aexit__ = AsyncMock(return_value=None) + mock_tmdb._ensure_session = AsyncMock() + mock_tmdb.close = AsyncMock() mock_tmdb.search_tv_show = AsyncMock( side_effect=TMDBAPIError("API error") ) @@ -253,6 +259,8 @@ class TestCompleteNFOWorkflow: mock_tmdb = Mock() mock_tmdb.__aenter__ = AsyncMock(return_value=mock_tmdb) mock_tmdb.__aexit__ = AsyncMock(return_value=None) + mock_tmdb._ensure_session = AsyncMock() + mock_tmdb.close = AsyncMock() mock_tmdb.search_tv_show = AsyncMock( return_value={"results": [{ "id": 999, @@ -307,16 +315,22 @@ class TestCompleteNFOWorkflow: mock_tmdb = Mock() mock_tmdb.__aenter__ = AsyncMock(return_value=mock_tmdb) mock_tmdb.__aexit__ = AsyncMock(return_value=None) + mock_tmdb._ensure_session = AsyncMock() + mock_tmdb.close = AsyncMock() mock_tmdb.search_tv_show = AsyncMock( side_effect=[ {"results": [{"id": 1, "name": "Anime 1", "first_air_date": "2020-01-01"}]}, {"results": [{"id": 2, "name": "Anime 2", "first_air_date": "2021-01-01"}]}, + {"results": [{"id": 1, "name": "Anime 1", "first_air_date": "2020-01-01"}]}, + {"results": [{"id": 2, "name": "Anime 2", "first_air_date": "2021-01-01"}]}, ] ) mock_tmdb.get_tv_show_details = AsyncMock( side_effect=[ {"id": 1, "name": "Anime 1", "first_air_date": "2020-01-01"}, {"id": 2, "name": "Anime 2", "first_air_date": "2021-01-01"}, + {"id": 1, "name": "Anime 1", "first_air_date": "2020-01-01"}, + {"id": 2, "name": "Anime 2", "first_air_date": "2021-01-01"}, ] ) mock_tmdb.get_tv_show_content_ratings = AsyncMock(return_value={"results": []}) @@ -366,6 +380,8 @@ class TestNFOWorkflowWithDownloads: mock_tmdb = Mock() mock_tmdb.__aenter__ = AsyncMock(return_value=mock_tmdb) mock_tmdb.__aexit__ = AsyncMock(return_value=None) + mock_tmdb._ensure_session = AsyncMock() + mock_tmdb.close = AsyncMock() mock_tmdb.search_tv_show = AsyncMock( return_value={"results": [{ "id": 999, diff --git a/tests/unit/test_background_loader_service.py b/tests/unit/test_background_loader_service.py index 2dc370a..3887b71 100644 --- a/tests/unit/test_background_loader_service.py +++ b/tests/unit/test_background_loader_service.py @@ -520,19 +520,25 @@ class TestLoadNfoAndImages: mock_db = AsyncMock() mock_series = MagicMock() mock_series.has_nfo = False - + task = SeriesLoadingTask( key="test", folder="test_folder", name="Test Series", year=2020 ) - - with patch("src.server.database.service.AnimeSeriesService") as mock_service_class: + + mock_nfo_service = AsyncMock() + mock_nfo_service.create_tvshow_nfo = AsyncMock(return_value="/anime/test_folder/tvshow.nfo") + mock_factory = MagicMock() + mock_factory.create = MagicMock(return_value=mock_nfo_service) + + with patch("src.server.database.service.AnimeSeriesService") as mock_service_class, \ + patch("src.server.services.background_loader_service.get_nfo_factory", return_value=mock_factory): mock_service_class.get_by_key = AsyncMock(return_value=mock_series) - + 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