feat: add NfoRepairService for missing NFO tag detection
This commit is contained in:
140
tests/unit/test_nfo_repair_service.py
Normal file
140
tests/unit/test_nfo_repair_service.py
Normal file
@@ -0,0 +1,140 @@
|
||||
"""Unit tests for NfoRepairService — Task 1."""
|
||||
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from src.core.services.nfo_repair_service import (
|
||||
NfoRepairService,
|
||||
REQUIRED_TAGS,
|
||||
find_missing_tags,
|
||||
nfo_needs_repair,
|
||||
parse_nfo_tags,
|
||||
)
|
||||
|
||||
REPO_ROOT = Path(__file__).parents[2]
|
||||
BAD_NFO = REPO_ROOT / "tvshow.nfo.bad"
|
||||
GOOD_NFO = REPO_ROOT / "tvshow.nfo.good"
|
||||
|
||||
# Tags known to be absent/empty in tvshow.nfo.bad
|
||||
EXPECTED_MISSING_FROM_BAD = {
|
||||
"originaltitle", "year", "plot", "runtime", "premiered",
|
||||
"status", "imdbid", "genre", "studio", "country", "actor/name", "watched",
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fixtures
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def bad_nfo(tmp_path: Path) -> Path:
|
||||
"""Copy tvshow.nfo.bad into a temp dir and return path to the copy."""
|
||||
dest = tmp_path / "tvshow.nfo"
|
||||
shutil.copy(BAD_NFO, dest)
|
||||
return dest
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def good_nfo(tmp_path: Path) -> Path:
|
||||
"""Copy tvshow.nfo.good into a temp dir and return path to the copy."""
|
||||
dest = tmp_path / "tvshow.nfo"
|
||||
shutil.copy(GOOD_NFO, dest)
|
||||
return dest
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def mock_nfo_service() -> MagicMock:
|
||||
"""Return a MagicMock NFOService with an async update_tvshow_nfo."""
|
||||
svc = MagicMock()
|
||||
svc.update_tvshow_nfo = AsyncMock(return_value=Path("/fake/tvshow.nfo"))
|
||||
return svc
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# find_missing_tags
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_find_missing_tags_with_bad_nfo(bad_nfo: Path) -> None:
|
||||
"""Bad NFO must report all 12 incomplete/missing tags."""
|
||||
missing = find_missing_tags(bad_nfo)
|
||||
assert set(missing) == EXPECTED_MISSING_FROM_BAD, (
|
||||
f"Unexpected missing set: {set(missing)}"
|
||||
)
|
||||
|
||||
|
||||
def test_find_missing_tags_with_good_nfo(good_nfo: Path) -> None:
|
||||
"""Good NFO must report no missing tags."""
|
||||
missing = find_missing_tags(good_nfo)
|
||||
assert missing == []
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# nfo_needs_repair
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_nfo_needs_repair_returns_true_for_bad_nfo(bad_nfo: Path) -> None:
|
||||
assert nfo_needs_repair(bad_nfo) is True
|
||||
|
||||
|
||||
def test_nfo_needs_repair_returns_false_for_good_nfo(good_nfo: Path) -> None:
|
||||
assert nfo_needs_repair(good_nfo) is False
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# NfoRepairService.repair_series
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_repair_series_calls_update_when_nfo_needs_repair(
|
||||
tmp_path: Path, mock_nfo_service: MagicMock
|
||||
) -> None:
|
||||
"""repair_series must call update_tvshow_nfo exactly once for a bad NFO."""
|
||||
shutil.copy(BAD_NFO, tmp_path / "tvshow.nfo")
|
||||
service = NfoRepairService(mock_nfo_service)
|
||||
|
||||
result = await service.repair_series(tmp_path, "Test Series")
|
||||
|
||||
assert result is True
|
||||
mock_nfo_service.update_tvshow_nfo.assert_called_once_with(
|
||||
"Test Series", download_media=False
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_repair_series_skips_when_nfo_is_complete(
|
||||
tmp_path: Path, mock_nfo_service: MagicMock
|
||||
) -> None:
|
||||
"""repair_series must NOT call update_tvshow_nfo for a complete NFO."""
|
||||
shutil.copy(GOOD_NFO, tmp_path / "tvshow.nfo")
|
||||
service = NfoRepairService(mock_nfo_service)
|
||||
|
||||
result = await service.repair_series(tmp_path, "Test Series")
|
||||
|
||||
assert result is False
|
||||
mock_nfo_service.update_tvshow_nfo.assert_not_called()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# parse_nfo_tags edge cases
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_parse_nfo_tags_handles_missing_file_gracefully() -> None:
|
||||
"""parse_nfo_tags must return empty dict for non-existent path."""
|
||||
result = parse_nfo_tags(Path("/nonexistent/dir/tvshow.nfo"))
|
||||
assert result == {}
|
||||
|
||||
|
||||
def test_parse_nfo_tags_handles_malformed_xml_gracefully(tmp_path: Path) -> None:
|
||||
"""parse_nfo_tags must return empty dict for malformed XML."""
|
||||
bad_xml = tmp_path / "tvshow.nfo"
|
||||
bad_xml.write_text("<<< not valid xml >>>", encoding="utf-8")
|
||||
result = parse_nfo_tags(bad_xml)
|
||||
assert result == {}
|
||||
Reference in New Issue
Block a user