- Add FolderScanService.run_folder_scan() calling perform_nfo_repair_scan() - Remove startup-time NFO repair from fastapi_app lifespan - Update docs/NFO_GUIDE.md: repair now runs as part of daily scan - Update tests to verify integration wiring - Update ARCHITECTURE.md and scheduler_service for scan scheduling
114 lines
4.6 KiB
Python
114 lines
4.6 KiB
Python
"""Integration tests verifying perform_nfo_repair_scan is wired into folder scan.
|
|
|
|
These tests confirm that:
|
|
1. FolderScanService.run_folder_scan calls perform_nfo_repair_scan.
|
|
2. Series with incomplete NFO files are queued via asyncio.create_task.
|
|
"""
|
|
from unittest.mock import AsyncMock, MagicMock, call, patch
|
|
|
|
import pytest
|
|
|
|
|
|
class TestNfoRepairScanCalledInFolderScan:
|
|
"""Verify perform_nfo_repair_scan is invoked from FolderScanService."""
|
|
|
|
def test_perform_nfo_repair_scan_imported_in_folder_scan_service(self):
|
|
"""folder_scan_service.py imports perform_nfo_repair_scan."""
|
|
import importlib
|
|
|
|
source = importlib.util.find_spec("src.server.services.folder_scan_service").origin
|
|
with open(source, "r", encoding="utf-8") as fh:
|
|
content = fh.read()
|
|
|
|
assert "perform_nfo_repair_scan" in content, (
|
|
"perform_nfo_repair_scan must be imported in folder_scan_service.py"
|
|
)
|
|
|
|
def test_perform_nfo_repair_scan_called_in_run_folder_scan(self):
|
|
"""perform_nfo_repair_scan must be called inside run_folder_scan."""
|
|
import importlib
|
|
|
|
source = importlib.util.find_spec("src.server.services.folder_scan_service").origin
|
|
with open(source, "r", encoding="utf-8") as fh:
|
|
content = fh.read()
|
|
|
|
run_folder_scan_pos = content.find("def run_folder_scan")
|
|
# Find the call inside the method body (after the import line)
|
|
repair_scan_call_pos = content.find("await perform_nfo_repair_scan(background_loader=None)")
|
|
|
|
assert run_folder_scan_pos != -1, "run_folder_scan method not found"
|
|
assert repair_scan_call_pos != -1, "perform_nfo_repair_scan call not found"
|
|
assert repair_scan_call_pos > run_folder_scan_pos, (
|
|
"perform_nfo_repair_scan must be called INSIDE run_folder_scan"
|
|
)
|
|
|
|
|
|
class TestNfoRepairScanIntegrationWithBackgroundLoader:
|
|
"""Integration test: incomplete NFO series are queued via background_loader."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_incomplete_nfo_series_scheduled_for_repair(self, tmp_path):
|
|
"""Series whose tvshow.nfo is missing required tags are scheduled via asyncio.create_task."""
|
|
from src.server.services.initialization_service import perform_nfo_repair_scan
|
|
|
|
series_dir = tmp_path / "IncompleteAnime"
|
|
series_dir.mkdir()
|
|
(series_dir / "tvshow.nfo").write_text(
|
|
"<tvshow><title>IncompleteAnime</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.initialization_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, patch(
|
|
"src.core.services.nfo_repair_service.NfoRepairService",
|
|
return_value=mock_repair_service,
|
|
), patch(
|
|
"asyncio.create_task"
|
|
) as mock_create_task:
|
|
mock_factory.return_value.create.return_value = MagicMock()
|
|
await perform_nfo_repair_scan(background_loader=AsyncMock())
|
|
|
|
mock_create_task.assert_called_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_complete_nfo_series_not_scheduled(self, tmp_path):
|
|
"""Series whose tvshow.nfo has all required tags are not scheduled for repair."""
|
|
from src.server.services.initialization_service import perform_nfo_repair_scan
|
|
|
|
series_dir = tmp_path / "CompleteAnime"
|
|
series_dir.mkdir()
|
|
(series_dir / "tvshow.nfo").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.initialization_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, patch(
|
|
"asyncio.create_task"
|
|
) as mock_create_task:
|
|
mock_factory.return_value.create.return_value = MagicMock()
|
|
await perform_nfo_repair_scan(background_loader=AsyncMock())
|
|
|
|
mock_create_task.assert_not_called()
|