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.
159 lines
5.2 KiB
Python
159 lines
5.2 KiB
Python
"""Unit tests for SchedulerConfig model fields and validators (Task 3)."""
|
||
import pytest
|
||
from pydantic import ValidationError
|
||
|
||
from src.server.models.config import SchedulerConfig
|
||
|
||
ALL_DAYS = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
|
||
|
||
|
||
class TestSchedulerConfigDefaults:
|
||
"""3.1 – Default values."""
|
||
|
||
def test_default_schedule_time(self) -> None:
|
||
config = SchedulerConfig()
|
||
assert config.schedule_time == "03:00"
|
||
|
||
def test_default_schedule_days(self) -> None:
|
||
config = SchedulerConfig()
|
||
assert config.schedule_days == ALL_DAYS
|
||
|
||
def test_default_auto_download(self) -> None:
|
||
config = SchedulerConfig()
|
||
assert config.auto_download_after_rescan is False
|
||
|
||
def test_default_enabled(self) -> None:
|
||
config = SchedulerConfig()
|
||
assert config.enabled is True
|
||
|
||
def test_default_interval_minutes(self) -> None:
|
||
config = SchedulerConfig()
|
||
assert config.interval_minutes == 60
|
||
|
||
|
||
class TestSchedulerConfigValidScheduleTime:
|
||
"""3.2 – Valid schedule_time values."""
|
||
|
||
@pytest.mark.parametrize("time_val", ["00:00", "03:00", "12:30", "23:59"])
|
||
def test_valid_times(self, time_val: str) -> None:
|
||
config = SchedulerConfig(schedule_time=time_val)
|
||
assert config.schedule_time == time_val
|
||
|
||
|
||
class TestSchedulerConfigInvalidScheduleTime:
|
||
"""3.3 – Invalid schedule_time values must raise ValidationError."""
|
||
|
||
@pytest.mark.parametrize(
|
||
"time_val",
|
||
["25:00", "3pm", "", "3:00pm", "24:00", "-1:00", "9:00", "1:60"],
|
||
)
|
||
def test_invalid_times(self, time_val: str) -> None:
|
||
with pytest.raises(ValidationError):
|
||
SchedulerConfig(schedule_time=time_val)
|
||
|
||
|
||
class TestSchedulerConfigValidScheduleDays:
|
||
"""3.4 – Valid schedule_days values."""
|
||
|
||
def test_single_day(self) -> None:
|
||
config = SchedulerConfig(schedule_days=["mon"])
|
||
assert config.schedule_days == ["mon"]
|
||
|
||
def test_multiple_days(self) -> None:
|
||
config = SchedulerConfig(schedule_days=["mon", "fri"])
|
||
assert config.schedule_days == ["mon", "fri"]
|
||
|
||
def test_all_days(self) -> None:
|
||
config = SchedulerConfig(schedule_days=ALL_DAYS)
|
||
assert config.schedule_days == ALL_DAYS
|
||
|
||
def test_empty_list(self) -> None:
|
||
config = SchedulerConfig(schedule_days=[])
|
||
assert config.schedule_days == []
|
||
|
||
|
||
class TestSchedulerConfigInvalidScheduleDays:
|
||
"""3.5 – Invalid schedule_days values must raise ValidationError."""
|
||
|
||
@pytest.mark.parametrize(
|
||
"days",
|
||
[
|
||
["monday"],
|
||
["xyz"],
|
||
["Mon"], # Case-sensitive — must be lowercase
|
||
[""],
|
||
],
|
||
)
|
||
def test_invalid_days(self, days: list) -> None:
|
||
with pytest.raises(ValidationError):
|
||
SchedulerConfig(schedule_days=days)
|
||
|
||
|
||
class TestSchedulerConfigAutoDownload:
|
||
"""3.6 – auto_download_after_rescan field."""
|
||
|
||
def test_set_true(self) -> None:
|
||
config = SchedulerConfig(auto_download_after_rescan=True)
|
||
assert config.auto_download_after_rescan is True
|
||
|
||
def test_set_false(self) -> None:
|
||
config = SchedulerConfig(auto_download_after_rescan=False)
|
||
assert config.auto_download_after_rescan is False
|
||
|
||
|
||
class TestSchedulerConfigBackwardCompat:
|
||
"""3.7 – Backward compatibility: old fields still work."""
|
||
|
||
def test_legacy_fields_use_defaults(self) -> None:
|
||
config = SchedulerConfig(enabled=True, interval_minutes=30)
|
||
assert config.schedule_time == "03:00"
|
||
assert config.schedule_days == ALL_DAYS
|
||
assert config.auto_download_after_rescan is False
|
||
assert config.enabled is True
|
||
assert config.interval_minutes == 30
|
||
|
||
|
||
class TestSchedulerConfigFolderScanEnabled:
|
||
"""3.8 – folder_scan_enabled field (Task 1.1)."""
|
||
|
||
def test_default_folder_scan_enabled(self) -> None:
|
||
config = SchedulerConfig()
|
||
assert config.folder_scan_enabled is False
|
||
|
||
def test_set_folder_scan_enabled_true(self) -> None:
|
||
config = SchedulerConfig(folder_scan_enabled=True)
|
||
assert config.folder_scan_enabled is True
|
||
|
||
def test_set_folder_scan_enabled_false(self) -> None:
|
||
config = SchedulerConfig(folder_scan_enabled=False)
|
||
assert config.folder_scan_enabled is False
|
||
|
||
def test_backward_compat_missing_field(self) -> None:
|
||
"""Old configs without folder_scan_enabled load successfully."""
|
||
dumped = {
|
||
"enabled": True,
|
||
"interval_minutes": 60,
|
||
"schedule_time": "03:00",
|
||
"schedule_days": ALL_DAYS,
|
||
"auto_download_after_rescan": False,
|
||
}
|
||
config = SchedulerConfig(**dumped)
|
||
assert config.folder_scan_enabled is False
|
||
|
||
|
||
class TestSchedulerConfigSerialisation:
|
||
"""3.9 – Serialisation roundtrip."""
|
||
|
||
def test_roundtrip(self) -> None:
|
||
original = SchedulerConfig(
|
||
enabled=True,
|
||
interval_minutes=120,
|
||
schedule_time="04:30",
|
||
schedule_days=["mon", "wed", "fri"],
|
||
auto_download_after_rescan=True,
|
||
folder_scan_enabled=True,
|
||
)
|
||
dumped = original.model_dump()
|
||
restored = SchedulerConfig(**dumped)
|
||
assert restored == original
|