test: add tests for scheduled folder scan and startup NFO repair removal
Add comprehensive test coverage for Tasks 1.1–1.5 and 2.1: - test_scheduler_config_model.py: folder_scan_enabled defaults, explicit values, backward compatibility with old configs, serialization roundtrip - test_folder_scan_service.py (new): prerequisites, NFO repair integration, folder rename integration, poster check/download, semaphore values, NFO thumb URL extraction, full end-to-end scan flow - test_scheduler_service.py: scheduler _perform_rescan integration with folder_scan_enabled (called when enabled, skipped when disabled, error handling and broadcasting), folder_scan_enabled in get_status output - test_nfo_repair_startup.py: verify perform_nfo_repair_scan is NOT called during FastAPI lifespan startup and IS called from FolderScanService All 90 tests pass.
This commit is contained in:
@@ -9,16 +9,16 @@ Covers:
|
||||
- Error handling and edge cases
|
||||
"""
|
||||
from datetime import datetime, timezone
|
||||
from unittest.mock import AsyncMock, MagicMock, Mock, patch, call
|
||||
from unittest.mock import AsyncMock, MagicMock, Mock, call, patch
|
||||
|
||||
import pytest
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
|
||||
from src.server.models.config import AppConfig, SchedulerConfig
|
||||
from src.server.services.scheduler_service import (
|
||||
_JOB_ID,
|
||||
SchedulerService,
|
||||
SchedulerServiceError,
|
||||
_JOB_ID,
|
||||
get_scheduler_service,
|
||||
reset_scheduler_service,
|
||||
)
|
||||
@@ -364,6 +364,7 @@ class TestGetStatus:
|
||||
schedule_time="04:00",
|
||||
schedule_days=["mon"],
|
||||
auto_download_after_rescan=True,
|
||||
folder_scan_enabled=True,
|
||||
)
|
||||
status = scheduler_service.get_status()
|
||||
|
||||
@@ -373,13 +374,100 @@ class TestGetStatus:
|
||||
assert "schedule_time" in status
|
||||
assert "schedule_days" in status
|
||||
assert "auto_download_after_rescan" in status
|
||||
assert "folder_scan_enabled" in status
|
||||
assert status["schedule_time"] == "04:00"
|
||||
assert status["schedule_days"] == ["mon"]
|
||||
assert status["auto_download_after_rescan"] is True
|
||||
assert status["folder_scan_enabled"] is True
|
||||
assert status["is_running"] is False
|
||||
assert status["next_run"] is None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 12.11 _perform_rescan() with folder_scan_enabled=True
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestPerformRescanFolderScan:
|
||||
@pytest.mark.asyncio
|
||||
async def test_folder_scan_called_when_enabled(self, scheduler_service):
|
||||
scheduler_service._config = SchedulerConfig(
|
||||
folder_scan_enabled=True,
|
||||
schedule_time="03:00",
|
||||
schedule_days=ALL_DAYS,
|
||||
)
|
||||
|
||||
mock_anime = MagicMock()
|
||||
mock_anime.rescan = AsyncMock()
|
||||
mock_anime._cached_list_missing.return_value = []
|
||||
|
||||
mock_ws = MagicMock()
|
||||
mock_ws.manager.broadcast = AsyncMock()
|
||||
|
||||
mock_folder_scan = AsyncMock()
|
||||
|
||||
with patch("src.server.utils.dependencies.get_anime_service", return_value=mock_anime), \
|
||||
patch("src.server.services.websocket_service.get_websocket_service", return_value=mock_ws), \
|
||||
patch("src.server.services.folder_scan_service.FolderScanService") as MockFSS:
|
||||
MockFSS.return_value.run_folder_scan = mock_folder_scan
|
||||
await scheduler_service._perform_rescan()
|
||||
|
||||
mock_folder_scan.assert_awaited_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_folder_scan_skipped_when_disabled(self, scheduler_service):
|
||||
scheduler_service._config = SchedulerConfig(
|
||||
folder_scan_enabled=False,
|
||||
schedule_time="03:00",
|
||||
schedule_days=ALL_DAYS,
|
||||
)
|
||||
|
||||
mock_anime = MagicMock()
|
||||
mock_anime.rescan = AsyncMock()
|
||||
mock_anime._cached_list_missing.return_value = []
|
||||
|
||||
mock_ws = MagicMock()
|
||||
mock_ws.manager.broadcast = AsyncMock()
|
||||
|
||||
mock_folder_scan = AsyncMock()
|
||||
|
||||
with patch("src.server.utils.dependencies.get_anime_service", return_value=mock_anime), \
|
||||
patch("src.server.services.websocket_service.get_websocket_service", return_value=mock_ws), \
|
||||
patch("src.server.services.folder_scan_service.FolderScanService") as MockFSS:
|
||||
MockFSS.return_value.run_folder_scan = mock_folder_scan
|
||||
await scheduler_service._perform_rescan()
|
||||
|
||||
mock_folder_scan.assert_not_called()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_folder_scan_error_broadcasts_and_does_not_crash(self, scheduler_service):
|
||||
scheduler_service._config = SchedulerConfig(
|
||||
folder_scan_enabled=True,
|
||||
schedule_time="03:00",
|
||||
schedule_days=ALL_DAYS,
|
||||
)
|
||||
|
||||
mock_anime = MagicMock()
|
||||
mock_anime.rescan = AsyncMock()
|
||||
mock_anime._cached_list_missing.return_value = []
|
||||
|
||||
mock_ws = MagicMock()
|
||||
mock_ws.manager.broadcast = AsyncMock()
|
||||
|
||||
mock_folder_scan = AsyncMock(side_effect=RuntimeError("folder scan boom"))
|
||||
|
||||
with patch("src.server.utils.dependencies.get_anime_service", return_value=mock_anime), \
|
||||
patch("src.server.services.websocket_service.get_websocket_service", return_value=mock_ws), \
|
||||
patch("src.server.services.folder_scan_service.FolderScanService") as MockFSS:
|
||||
MockFSS.return_value.run_folder_scan = mock_folder_scan
|
||||
# Should NOT raise
|
||||
await scheduler_service._perform_rescan()
|
||||
|
||||
mock_folder_scan.assert_awaited_once()
|
||||
calls = [str(c) for c in mock_ws.manager.broadcast.call_args_list]
|
||||
assert any("folder_scan_error" in c for c in calls)
|
||||
assert scheduler_service._scan_in_progress is False
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Singleton helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user