141 lines
4.4 KiB
Python
141 lines
4.4 KiB
Python
"""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 == {}
|