refactor: simplify NFO handling, remove legacy services

- Drop nfo_factory, nfo_repair_service, nfo_service, series_manager_service
- Delete key_resolution_service, consolidate into folder_rename_service
- Remove bulk of NFO-related tests (coverage via integration tests)
- Streamline SeriesApp, background_loader, initialization services
- Add folder_rename_service to scheduler

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-06-04 18:54:31 +02:00
parent 97caaf0d18
commit 21af502184
53 changed files with 175 additions and 16588 deletions

View File

@@ -458,40 +458,21 @@ class TestNFOScanFunctions:
class TestExecuteNFOScan:
"""Test NFO scan execution."""
"""Test NFO scan execution - NFO service removed."""
@pytest.mark.asyncio
async def test_execute_nfo_scan_without_progress(self):
"""Test executing NFO scan without progress service."""
mock_manager = MagicMock()
mock_manager.scan_and_process_nfo = AsyncMock()
mock_manager.close = AsyncMock()
with patch('src.core.services.series_manager_service.SeriesManagerService') as mock_sms:
mock_sms.from_settings.return_value = mock_manager
await _execute_nfo_scan()
mock_manager.scan_and_process_nfo.assert_called_once()
mock_manager.close.assert_called_once()
"""Test executing NFO scan without progress service - now no-op."""
# NFO service removed, so _execute_nfo_scan should be a no-op
await _execute_nfo_scan()
# If we got here without exception, the no-op worked
@pytest.mark.asyncio
async def test_execute_nfo_scan_with_progress(self):
"""Test executing NFO scan with progress updates."""
mock_manager = MagicMock()
mock_manager.scan_and_process_nfo = AsyncMock()
mock_manager.close = AsyncMock()
"""Test executing NFO scan with progress updates - now no-op."""
mock_progress = AsyncMock()
with patch('src.core.services.series_manager_service.SeriesManagerService') as mock_sms:
mock_sms.from_settings.return_value = mock_manager
await _execute_nfo_scan(progress_service=mock_progress)
mock_manager.scan_and_process_nfo.assert_called_once()
mock_manager.close.assert_called_once()
assert mock_progress.update_progress.call_count == 2
mock_progress.complete_progress.assert_called_once()
await _execute_nfo_scan(progress_service=mock_progress)
# If we got here without exception, the no-op worked
class TestPerformNFOScan:
@@ -761,7 +742,10 @@ class TestInitializationIntegration:
class TestPerformNfoRepairScan:
"""Tests for the perform_nfo_repair_scan startup hook."""
"""Tests for the perform_nfo_repair_scan startup hook.
Note: NFO service removed, so these tests verify no-op behavior.
"""
@pytest.mark.asyncio
async def test_skips_without_tmdb_api_key(self, tmp_path):
@@ -790,100 +774,20 @@ class TestPerformNfoRepairScan:
await perform_nfo_repair_scan()
@pytest.mark.asyncio
async def test_queues_deficient_series_as_asyncio_task(self, tmp_path):
"""Series with incomplete NFO should be scheduled via asyncio.create_task."""
async def test_is_no_op(self, tmp_path):
"""perform_nfo_repair_scan is now a no-op - just verify it returns without error."""
mock_settings = MagicMock()
mock_settings.tmdb_api_key = "test-key"
mock_settings.anime_directory = str(tmp_path)
series_dir = tmp_path / "MyAnime"
series_dir.mkdir()
nfo_file = series_dir / "tvshow.nfo"
nfo_file.write_text("<tvshow><title>MyAnime</title></tvshow>")
mock_settings = MagicMock()
mock_settings.tmdb_api_key = "test-key"
mock_settings.anime_directory = str(tmp_path)
mock_repair_service = AsyncMock()
mock_repair_service.repair_series = AsyncMock(return_value=True)
with patch(
"src.server.services.scheduler.folder_scan_service._settings", mock_settings
), patch(
"src.core.services.nfo_repair_service.nfo_needs_repair",
return_value=True,
), patch(
"src.core.services.nfo_factory.NFOServiceFactory"
) as mock_factory_cls, patch(
"src.core.services.nfo_repair_service.NfoRepairService",
return_value=mock_repair_service,
), patch(
"asyncio.create_task"
) as mock_create_task, patch(
"asyncio.gather", new_callable=AsyncMock
) as mock_gather:
mock_factory_cls.return_value.create.return_value = MagicMock()
):
await perform_nfo_repair_scan(background_loader=AsyncMock())
mock_create_task.assert_called_once()
mock_gather.assert_called_once()
@pytest.mark.asyncio
async def test_skips_complete_series(self, tmp_path):
"""Series with complete NFO should not be scheduled for repair."""
series_dir = tmp_path / "CompleteAnime"
series_dir.mkdir()
nfo_file = series_dir / "tvshow.nfo"
nfo_file.write_text("<tvshow><title>CompleteAnime</title></tvshow>")
mock_settings = MagicMock()
mock_settings.tmdb_api_key = "test-key"
mock_settings.anime_directory = str(tmp_path)
with patch(
"src.server.services.scheduler.folder_scan_service._settings", mock_settings
), patch(
"src.core.services.nfo_repair_service.nfo_needs_repair",
return_value=False,
), patch(
"src.core.services.nfo_factory.NFOServiceFactory"
) as mock_factory_cls, patch(
"asyncio.create_task"
) as mock_create_task:
mock_factory_cls.return_value.create.return_value = MagicMock()
await perform_nfo_repair_scan(background_loader=AsyncMock())
mock_create_task.assert_not_called()
@pytest.mark.asyncio
async def test_repairs_via_asyncio_task_without_background_loader(self, tmp_path):
"""When no background_loader provided, repair is still scheduled via asyncio.create_task."""
series_dir = tmp_path / "NeedsRepair"
series_dir.mkdir()
nfo_file = series_dir / "tvshow.nfo"
nfo_file.write_text("<tvshow><title>NeedsRepair</title></tvshow>")
mock_settings = MagicMock()
mock_settings.tmdb_api_key = "test-key"
mock_settings.anime_directory = str(tmp_path)
mock_repair_service = AsyncMock()
mock_repair_service.repair_series = AsyncMock(return_value=True)
with patch(
"src.server.services.scheduler.folder_scan_service._settings", mock_settings
), patch(
"src.core.services.nfo_repair_service.nfo_needs_repair",
return_value=True,
), patch(
"src.core.services.nfo_factory.NFOServiceFactory"
) as mock_factory_cls, patch(
"src.core.services.nfo_repair_service.NfoRepairService",
return_value=mock_repair_service,
), patch(
"asyncio.create_task"
) as mock_create_task, patch(
"asyncio.gather", new_callable=AsyncMock
) as mock_gather:
mock_factory_cls.return_value.create.return_value = MagicMock()
await perform_nfo_repair_scan(background_loader=None)
mock_create_task.assert_called_once()
mock_gather.assert_called_once()
# If we got here, the no-op worked correctly