From 53fe09351f23ba0272a9a7a7b66f2d607afdada6 Mon Sep 17 00:00:00 2001 From: Lukas Date: Sat, 6 Jun 2026 19:39:32 +0200 Subject: [PATCH] fix: prevent duplicate series when same anime key exists in different folder - Add check for existing series by key in SetupService.run to skip duplicates - Fix Path construction in initialization_service.py cleanup function - Update unit tests to mock get_by_key and get_series_app --- src/server/services/initialization_service.py | 2 +- src/server/services/setup_service.py | 12 ++++++++ tests/unit/test_setup_service.py | 28 +++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/server/services/initialization_service.py b/src/server/services/initialization_service.py index 34a5c56..ef0197d 100644 --- a/src/server/services/initialization_service.py +++ b/src/server/services/initialization_service.py @@ -165,7 +165,7 @@ async def _cleanup_legacy_key_files() -> int: db_folders: set[str] = {series.folder for series in all_series if series.folder} for folder_name in db_folders: - folder_path = settings.anime_directory / folder_name + folder_path = Path(settings.anime_directory) / folder_name key_file = folder_path / "key" if not key_file.exists(): diff --git a/src/server/services/setup_service.py b/src/server/services/setup_service.py index 5aa67f8..f6145f1 100644 --- a/src/server/services/setup_service.py +++ b/src/server/services/setup_service.py @@ -378,6 +378,18 @@ class SetupService: ) continue + # Also check if a series with this key already exists (different folder, same anime) + existing_by_key = await AnimeSeriesService.get_by_key(db, resolved_key) + if existing_by_key: + logger.debug( + "Series with key already exists, skipping", + folder=folder_name, + key=resolved_key, + existing_folder=existing_by_key.folder + ) + skipped_existing += 1 + continue + # Check filesystem properties props = cls._get_series_properties(folder) diff --git a/tests/unit/test_setup_service.py b/tests/unit/test_setup_service.py index 3740657..9784f99 100644 --- a/tests/unit/test_setup_service.py +++ b/tests/unit/test_setup_service.py @@ -167,10 +167,16 @@ class TestSetupServiceRun: mock_get_db = MagicMock() mock_get_db.__aenter__.return_value = mock_db mock_get_db.__aexit__.return_value = None + mock_series_app = AsyncMock() + mock_series_app.search.return_value = [] with patch( 'src.server.services.setup_service.settings' ) as mock_settings, \ + patch( + 'src.server.services.setup_service.get_series_app', + return_value=mock_series_app + ), \ patch( 'src.server.services.setup_service.get_db_session', return_value=mock_get_db @@ -179,6 +185,10 @@ class TestSetupServiceRun: 'src.server.services.setup_service.AnimeSeriesService.get_by_folder', new_callable=AsyncMock, return_value=None ), \ + patch( + 'src.server.services.setup_service.AnimeSeriesService.get_by_key', + new_callable=AsyncMock, return_value=None + ), \ patch( 'src.server.services.setup_service.UnresolvedFolderService.get_by_folder_name', new_callable=AsyncMock, return_value=None @@ -258,10 +268,16 @@ class TestSetupServiceRun: mock_get_db = MagicMock() mock_get_db.__aenter__.return_value = mock_db mock_get_db.__aexit__.return_value = None + mock_series_app = AsyncMock() + mock_series_app.search.return_value = [] with patch( 'src.server.services.setup_service.settings' ) as mock_settings, \ + patch( + 'src.server.services.setup_service.get_series_app', + return_value=mock_series_app + ), \ patch( 'src.server.services.setup_service.get_db_session', return_value=mock_get_db @@ -270,6 +286,10 @@ class TestSetupServiceRun: 'src.server.services.setup_service.AnimeSeriesService.get_by_folder', new_callable=AsyncMock, return_value=None ), \ + patch( + 'src.server.services.setup_service.AnimeSeriesService.get_by_key', + new_callable=AsyncMock, return_value=None + ), \ patch( 'src.server.services.setup_service.UnresolvedFolderService.get_by_folder_name', new_callable=AsyncMock, return_value=None @@ -323,6 +343,10 @@ class TestSetupServiceRun: 'src.server.services.setup_service.AnimeSeriesService.get_by_folder', new_callable=AsyncMock, return_value=None ), \ + patch( + 'src.server.services.setup_service.AnimeSeriesService.get_by_key', + new_callable=AsyncMock, return_value=None + ), \ patch( 'src.server.services.setup_service.UnresolvedFolderService.get_by_folder_name', new_callable=AsyncMock, return_value=None @@ -401,6 +425,10 @@ class TestSetupServiceRun: 'src.server.services.setup_service.AnimeSeriesService.get_by_folder', new_callable=AsyncMock, return_value=None ), \ + patch( + 'src.server.services.setup_service.AnimeSeriesService.get_by_key', + new_callable=AsyncMock, return_value=None + ), \ patch( 'src.server.services.setup_service.UnresolvedFolderService.get_by_folder_name', new_callable=AsyncMock, return_value=None