refactor(scheduler): separate scheduler logic from scan/rescan logic
- Extract rescan logic into new RescanService (src/server/services/rescan_service.py) - SchedulerService now only handles APScheduler cron scheduling - Move scheduler sub-services (folder_rename, folder_scan, key_resolution) to scheduler/ folder - Keep RescanOrchestrator as backward-compatible alias - Update all imports across api/, server/, and test files
This commit is contained in:
@@ -19,7 +19,7 @@ class TestPosterCheckScanCalledInFolderScan:
|
||||
import importlib
|
||||
|
||||
source = importlib.util.find_spec(
|
||||
"src.server.services.folder_scan_service"
|
||||
"src.server.services.scheduler.folder_scan_service"
|
||||
).origin
|
||||
with open(source, "r", encoding="utf-8") as fh:
|
||||
content = fh.read()
|
||||
@@ -33,7 +33,7 @@ class TestPosterCheckScanCalledInFolderScan:
|
||||
import importlib
|
||||
|
||||
source = importlib.util.find_spec(
|
||||
"src.server.services.folder_scan_service"
|
||||
"src.server.services.scheduler.folder_scan_service"
|
||||
).origin
|
||||
with open(source, "r", encoding="utf-8") as fh:
|
||||
content = fh.read()
|
||||
@@ -54,7 +54,7 @@ class TestPosterCheckIntegration:
|
||||
@pytest.mark.asyncio
|
||||
async def test_poster_check_downloads_missing_poster(self, tmp_path):
|
||||
"""When poster.jpg is missing, the scan downloads it from the NFO thumb URL."""
|
||||
from src.server.services.folder_scan_service import FolderScanService
|
||||
from src.server.services.scheduler.folder_scan_service import FolderScanService
|
||||
|
||||
anime_dir = tmp_path / "anime"
|
||||
anime_dir.mkdir()
|
||||
@@ -91,14 +91,14 @@ class TestPosterCheckIntegration:
|
||||
with patch(
|
||||
"src.config.settings.settings", mock_settings
|
||||
), patch(
|
||||
"src.server.services.folder_scan_service.perform_nfo_repair_scan",
|
||||
"src.server.services.scheduler.folder_scan_service.perform_nfo_repair_scan",
|
||||
new_callable=AsyncMock,
|
||||
), patch(
|
||||
"src.server.services.folder_rename_service.validate_and_rename_series_folders",
|
||||
"src.server.services.scheduler.folder_rename_service.validate_and_rename_series_folders",
|
||||
new_callable=AsyncMock,
|
||||
return_value={"scanned": 0, "renamed": 0, "skipped": 0, "errors": 0},
|
||||
), patch(
|
||||
"src.server.services.folder_scan_service.ImageDownloader",
|
||||
"src.server.services.scheduler.folder_scan_service.ImageDownloader",
|
||||
new=MockDownloader,
|
||||
):
|
||||
service = FolderScanService()
|
||||
@@ -112,7 +112,7 @@ class TestPosterCheckIntegration:
|
||||
@pytest.mark.asyncio
|
||||
async def test_poster_check_skips_valid_poster(self, tmp_path):
|
||||
"""When poster.jpg exists and is large enough, the scan skips it."""
|
||||
from src.server.services.folder_scan_service import FolderScanService
|
||||
from src.server.services.scheduler.folder_scan_service import FolderScanService
|
||||
|
||||
anime_dir = tmp_path / "anime"
|
||||
anime_dir.mkdir()
|
||||
@@ -136,14 +136,14 @@ class TestPosterCheckIntegration:
|
||||
with patch(
|
||||
"src.config.settings.settings", mock_settings
|
||||
), patch(
|
||||
"src.server.services.folder_scan_service.perform_nfo_repair_scan",
|
||||
"src.server.services.scheduler.folder_scan_service.perform_nfo_repair_scan",
|
||||
new_callable=AsyncMock,
|
||||
), patch(
|
||||
"src.server.services.folder_rename_service.validate_and_rename_series_folders",
|
||||
"src.server.services.scheduler.folder_rename_service.validate_and_rename_series_folders",
|
||||
new_callable=AsyncMock,
|
||||
return_value={"scanned": 0, "renamed": 0, "skipped": 0, "errors": 0},
|
||||
), patch(
|
||||
"src.server.services.folder_scan_service.ImageDownloader"
|
||||
"src.server.services.scheduler.folder_scan_service.ImageDownloader"
|
||||
) as mock_downloader_cls:
|
||||
service = FolderScanService()
|
||||
await service.run_folder_scan()
|
||||
@@ -153,7 +153,7 @@ class TestPosterCheckIntegration:
|
||||
@pytest.mark.asyncio
|
||||
async def test_poster_check_skips_when_no_thumb_url(self, tmp_path):
|
||||
"""When NFO has no thumb URL, the scan skips the folder."""
|
||||
from src.server.services.folder_scan_service import FolderScanService
|
||||
from src.server.services.scheduler.folder_scan_service import FolderScanService
|
||||
|
||||
anime_dir = tmp_path / "anime"
|
||||
anime_dir.mkdir()
|
||||
@@ -173,14 +173,14 @@ class TestPosterCheckIntegration:
|
||||
with patch(
|
||||
"src.config.settings.settings", mock_settings
|
||||
), patch(
|
||||
"src.server.services.folder_scan_service.perform_nfo_repair_scan",
|
||||
"src.server.services.scheduler.folder_scan_service.perform_nfo_repair_scan",
|
||||
new_callable=AsyncMock,
|
||||
), patch(
|
||||
"src.server.services.folder_rename_service.validate_and_rename_series_folders",
|
||||
"src.server.services.scheduler.folder_rename_service.validate_and_rename_series_folders",
|
||||
new_callable=AsyncMock,
|
||||
return_value={"scanned": 0, "renamed": 0, "skipped": 0, "errors": 0},
|
||||
), patch(
|
||||
"src.server.services.folder_scan_service.ImageDownloader"
|
||||
"src.server.services.scheduler.folder_scan_service.ImageDownloader"
|
||||
) as mock_downloader_cls:
|
||||
service = FolderScanService()
|
||||
await service.run_folder_scan()
|
||||
@@ -190,7 +190,7 @@ class TestPosterCheckIntegration:
|
||||
@pytest.mark.asyncio
|
||||
async def test_poster_check_skipped_when_prerequisites_not_met(self, tmp_path):
|
||||
"""If anime directory is missing, poster check logic is skipped gracefully."""
|
||||
from src.server.services.folder_scan_service import FolderScanService
|
||||
from src.server.services.scheduler.folder_scan_service import FolderScanService
|
||||
|
||||
mock_settings = MagicMock()
|
||||
mock_settings.tmdb_api_key = "test-key"
|
||||
@@ -199,12 +199,12 @@ class TestPosterCheckIntegration:
|
||||
with patch(
|
||||
"src.config.settings.settings", mock_settings
|
||||
), patch(
|
||||
"src.server.services.folder_scan_service.perform_nfo_repair_scan",
|
||||
"src.server.services.scheduler.folder_scan_service.perform_nfo_repair_scan",
|
||||
new_callable=AsyncMock,
|
||||
), patch(
|
||||
"src.server.services.folder_rename_service.validate_and_rename_series_folders"
|
||||
"src.server.services.scheduler.folder_rename_service.validate_and_rename_series_folders"
|
||||
) as mock_rename, patch(
|
||||
"src.server.services.folder_scan_service.ImageDownloader"
|
||||
"src.server.services.scheduler.folder_scan_service.ImageDownloader"
|
||||
) as mock_downloader_cls:
|
||||
service = FolderScanService()
|
||||
await service.run_folder_scan()
|
||||
@@ -220,7 +220,7 @@ class TestPosterCheckSemaphore:
|
||||
import importlib
|
||||
|
||||
source = importlib.util.find_spec(
|
||||
"src.server.services.folder_scan_service"
|
||||
"src.server.services.scheduler.folder_scan_service"
|
||||
).origin
|
||||
with open(source, "r", encoding="utf-8") as fh:
|
||||
content = fh.read()
|
||||
@@ -232,7 +232,7 @@ class TestPosterCheckSemaphore:
|
||||
@pytest.mark.asyncio
|
||||
async def test_poster_download_uses_semaphore(self, tmp_path):
|
||||
"""Poster downloads are gated by the semaphore."""
|
||||
from src.server.services.folder_scan_service import (
|
||||
from src.server.services.scheduler.folder_scan_service import (
|
||||
_POSTER_DOWNLOAD_SEMAPHORE,
|
||||
FolderScanService,
|
||||
)
|
||||
@@ -270,14 +270,14 @@ class TestPosterCheckSemaphore:
|
||||
with patch(
|
||||
"src.config.settings.settings", mock_settings
|
||||
), patch(
|
||||
"src.server.services.folder_scan_service.perform_nfo_repair_scan",
|
||||
"src.server.services.scheduler.folder_scan_service.perform_nfo_repair_scan",
|
||||
new_callable=AsyncMock,
|
||||
), patch(
|
||||
"src.server.services.folder_rename_service.validate_and_rename_series_folders",
|
||||
"src.server.services.scheduler.folder_rename_service.validate_and_rename_series_folders",
|
||||
new_callable=AsyncMock,
|
||||
return_value={"scanned": 0, "renamed": 0, "skipped": 0, "errors": 0},
|
||||
), patch(
|
||||
"src.server.services.folder_scan_service.ImageDownloader"
|
||||
"src.server.services.scheduler.folder_scan_service.ImageDownloader"
|
||||
) as mock_downloader_cls:
|
||||
mock_downloader = AsyncMock()
|
||||
mock_downloader.download_poster = AsyncMock(side_effect=tracked_download)
|
||||
|
||||
Reference in New Issue
Block a user