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:
2026-05-13 09:43:34 +02:00
parent 756731cd5d
commit 9c0f7ce08d
3 changed files with 727 additions and 3 deletions

View File

@@ -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
# ---------------------------------------------------------------------------