diff --git a/docs/instructions.md b/docs/instructions.md
index 5f3d3a0..6f0f25f 100644
--- a/docs/instructions.md
+++ b/docs/instructions.md
@@ -112,58 +112,6 @@ For each task completed:
### 🎬 NFO Metadata Integration
-#### Task 1: Add NFO File Support to Core Entities
-
-**Priority:** High
-**Estimated Time:** 2-3 hours
-
-Create support for tvshow.nfo metadata files in the core domain layer.
-
-**Implementation Steps:**
-
-1. **Add NFO Path Property to Serie Class** ([src/core/entities/series.py](../src/core/entities/series.py))
-
- - Add `nfo_path` optional property to track tvshow.nfo file location
- - Add `has_nfo` method to check if tvshow.nfo exists
- - Add properties to check for media files: `has_poster()`, `has_logo()`, `has_fanart()`
- - Update `to_dict()` and `from_dict()` to include nfo metadata
-
-2. **Update SerieList Class** ([src/core/entities/SerieList.py](../src/core/entities/SerieList.py))
- - Modify `load_series()` to check for tvshow.nfo files
- - Check for associated media files (poster.jpg, logo.png, fanart.jpg)
- - Add logging for series without tvshow.nfo or media files
- - Track NFO and media file status during series loading
-
-**Acceptance Criteria:**
-
-- [ ] Serie class has nfo_path property
-- [ ] Serie class can check if tvshow.nfo exists
-- [ ] SerieList logs missing NFO files
-- [ ] Existing functionality remains unchanged
-- [ ] Unit tests pass
-- [ ] Test coverage > 90% for new code
-
-**Testing Requirements:**
-
-- Test `nfo_path` property getter/setter
-- Test `has_nfo()` method with existing and missing files
-- Test `has_poster()`, `has_logo()`, `has_fanart()` methods
-- Test `to_dict()` includes NFO metadata
-- Test `from_dict()` correctly loads NFO metadata
-- Test SerieList NFO detection during load
-- Test SerieList media file detection during load
-- Test logging output for missing NFO and media files
-- Mock filesystem operations in tests
-
-**Files to Modify:**
-
-- [src/core/entities/series.py](../src/core/entities/series.py)
-- [src/core/entities/SerieList.py](../src/core/entities/SerieList.py)
-- Add tests to `tests/unit/test_serie_class.py`
-- Add tests to `tests/unit/test_serie_list.py`
-
----
-
#### Task 2: Create NFO Models and Schemas
**Priority:** High
diff --git a/src/core/entities/SerieList.py b/src/core/entities/SerieList.py
index ef232cc..d995716 100644
--- a/src/core/entities/SerieList.py
+++ b/src/core/entities/SerieList.py
@@ -132,25 +132,110 @@ class SerieList:
)
return
+ nfo_stats = {"total": 0, "with_nfo": 0, "without_nfo": 0}
+ media_stats = {
+ "with_poster": 0,
+ "without_poster": 0,
+ "with_logo": 0,
+ "without_logo": 0,
+ "with_fanart": 0,
+ "without_fanart": 0
+ }
+
for anime_folder in entries:
anime_path = os.path.join(self.directory, anime_folder, "data")
if os.path.isfile(anime_path):
logging.debug("Found data file for folder %s", anime_folder)
- self._load_data(anime_folder, anime_path)
+ serie = self._load_data(anime_folder, anime_path)
+
+ if serie:
+ nfo_stats["total"] += 1
+ # Check for NFO file
+ nfo_file_path = os.path.join(
+ self.directory, anime_folder, "tvshow.nfo"
+ )
+ if os.path.isfile(nfo_file_path):
+ serie.nfo_path = nfo_file_path
+ nfo_stats["with_nfo"] += 1
+ else:
+ nfo_stats["without_nfo"] += 1
+ logging.debug(
+ "Series '%s' (key: %s) is missing tvshow.nfo",
+ serie.name,
+ serie.key
+ )
+
+ # Check for media files
+ folder_path = os.path.join(self.directory, anime_folder)
+
+ poster_path = os.path.join(folder_path, "poster.jpg")
+ if os.path.isfile(poster_path):
+ media_stats["with_poster"] += 1
+ else:
+ media_stats["without_poster"] += 1
+ logging.debug(
+ "Series '%s' (key: %s) is missing poster.jpg",
+ serie.name,
+ serie.key
+ )
+
+ logo_path = os.path.join(folder_path, "logo.png")
+ if os.path.isfile(logo_path):
+ media_stats["with_logo"] += 1
+ else:
+ media_stats["without_logo"] += 1
+ logging.debug(
+ "Series '%s' (key: %s) is missing logo.png",
+ serie.name,
+ serie.key
+ )
+
+ fanart_path = os.path.join(folder_path, "fanart.jpg")
+ if os.path.isfile(fanart_path):
+ media_stats["with_fanart"] += 1
+ else:
+ media_stats["without_fanart"] += 1
+ logging.debug(
+ "Series '%s' (key: %s) is missing fanart.jpg",
+ serie.name,
+ serie.key
+ )
+
continue
logging.warning(
"Skipping folder %s because no metadata file was found",
anime_folder,
)
+
+ # Log summary statistics
+ if nfo_stats["total"] > 0:
+ logging.info(
+ "NFO scan complete: %d series total, %d with NFO, %d without NFO",
+ nfo_stats["total"],
+ nfo_stats["with_nfo"],
+ nfo_stats["without_nfo"]
+ )
+ logging.info(
+ "Media scan complete: Poster (%d/%d), Logo (%d/%d), Fanart (%d/%d)",
+ media_stats["with_poster"],
+ nfo_stats["total"],
+ media_stats["with_logo"],
+ nfo_stats["total"],
+ media_stats["with_fanart"],
+ nfo_stats["total"]
+ )
- def _load_data(self, anime_folder: str, data_path: str) -> None:
+ def _load_data(self, anime_folder: str, data_path: str) -> Optional[Serie]:
"""
Load a single series metadata file into the in-memory collection.
Args:
anime_folder: The folder name (for logging only)
data_path: Path to the metadata file
+
+ Returns:
+ Serie: The loaded Serie object, or None if loading failed
"""
try:
serie = Serie.load_from_file(data_path)
@@ -161,6 +246,7 @@ class SerieList:
anime_folder,
serie.key
)
+ return serie
except (OSError, JSONDecodeError, KeyError, ValueError) as error:
logging.error(
"Failed to load metadata for folder %s from %s: %s",
@@ -168,6 +254,7 @@ class SerieList:
data_path,
error,
)
+ return None
def GetMissingEpisode(self) -> List[Serie]:
"""Return all series that still contain missing episodes."""
diff --git a/src/core/entities/series.py b/src/core/entities/series.py
index c5ab7b4..068dc11 100644
--- a/src/core/entities/series.py
+++ b/src/core/entities/series.py
@@ -1,5 +1,8 @@
import json
+import os
import warnings
+from pathlib import Path
+from typing import Optional
from src.server.utils.filesystem import sanitize_folder_name
@@ -35,7 +38,8 @@ class Serie:
site: str,
folder: str,
episodeDict: dict[int, list[int]],
- year: int | None = None
+ year: int | None = None,
+ nfo_path: Optional[str] = None
):
if not key or not key.strip():
raise ValueError("Serie key cannot be None or empty")
@@ -46,6 +50,7 @@ class Serie:
self._folder = folder
self._episodeDict = episodeDict
self._year = year
+ self._nfo_path = nfo_path
def __str__(self):
"""String representation of Serie object"""
@@ -148,6 +153,93 @@ class Serie:
"""Set the release year of the series."""
self._year = value
+ @property
+ def nfo_path(self) -> Optional[str]:
+ """
+ Path to the tvshow.nfo metadata file.
+
+ Returns:
+ str or None: Path to the NFO file, or None if not set
+ """
+ return self._nfo_path
+
+ @nfo_path.setter
+ def nfo_path(self, value: Optional[str]):
+ """Set the path to the NFO file."""
+ self._nfo_path = value
+
+ def has_nfo(self, base_directory: Optional[str] = None) -> bool:
+ """
+ Check if tvshow.nfo file exists for this series.
+
+ Args:
+ base_directory: Base anime directory path. If provided, checks
+ relative to base_directory/folder/tvshow.nfo. If not provided,
+ uses nfo_path directly.
+
+ Returns:
+ bool: True if tvshow.nfo exists, False otherwise
+ """
+ if base_directory:
+ nfo_file = Path(base_directory) / self.folder / "tvshow.nfo"
+ elif self._nfo_path:
+ nfo_file = Path(self._nfo_path)
+ else:
+ return False
+
+ return nfo_file.exists() and nfo_file.is_file()
+
+ def has_poster(self, base_directory: Optional[str] = None) -> bool:
+ """
+ Check if poster.jpg file exists for this series.
+
+ Args:
+ base_directory: Base anime directory path. If provided, checks
+ relative to base_directory/folder/poster.jpg.
+
+ Returns:
+ bool: True if poster.jpg exists, False otherwise
+ """
+ if not base_directory:
+ return False
+
+ poster_file = Path(base_directory) / self.folder / "poster.jpg"
+ return poster_file.exists() and poster_file.is_file()
+
+ def has_logo(self, base_directory: Optional[str] = None) -> bool:
+ """
+ Check if logo.png file exists for this series.
+
+ Args:
+ base_directory: Base anime directory path. If provided, checks
+ relative to base_directory/folder/logo.png.
+
+ Returns:
+ bool: True if logo.png exists, False otherwise
+ """
+ if not base_directory:
+ return False
+
+ logo_file = Path(base_directory) / self.folder / "logo.png"
+ return logo_file.exists() and logo_file.is_file()
+
+ def has_fanart(self, base_directory: Optional[str] = None) -> bool:
+ """
+ Check if fanart.jpg file exists for this series.
+
+ Args:
+ base_directory: Base anime directory path. If provided, checks
+ relative to base_directory/folder/fanart.jpg.
+
+ Returns:
+ bool: True if fanart.jpg exists, False otherwise
+ """
+ if not base_directory:
+ return False
+
+ fanart_file = Path(base_directory) / self.folder / "fanart.jpg"
+ return fanart_file.exists() and fanart_file.is_file()
+
@property
def name_with_year(self) -> str:
"""
@@ -208,7 +300,8 @@ class Serie:
"episodeDict": {
str(k): list(v) for k, v in self.episodeDict.items()
},
- "year": self.year
+ "year": self.year,
+ "nfo_path": self.nfo_path
}
@staticmethod
@@ -224,7 +317,8 @@ class Serie:
data["site"],
data["folder"],
episode_dict,
- data.get("year") # Optional year field for backward compatibility
+ data.get("year"), # Optional year field for backward compatibility
+ data.get("nfo_path") # Optional nfo_path field
)
def save_to_file(self, filename: str):
diff --git a/tests/unit/test_serie_class.py b/tests/unit/test_serie_class.py
index 38a0ae0..41bf42e 100644
--- a/tests/unit/test_serie_class.py
+++ b/tests/unit/test_serie_class.py
@@ -413,3 +413,337 @@ class TestSerieSanitizedFolder:
# Note: semicolon is valid on Linux but we test common invalid chars
assert ":" not in result
assert "/" not in result
+
+
+class TestSerieNFOFeatures:
+ """Test Serie class NFO-related features."""
+
+ def test_serie_creation_with_nfo_path(self):
+ """Test creating Serie with NFO path."""
+ serie = Serie(
+ key="test-series",
+ name="Test Series",
+ site="https://example.com",
+ folder="Test Folder",
+ episodeDict={1: [1]},
+ nfo_path="/path/to/tvshow.nfo"
+ )
+
+ assert serie.nfo_path == "/path/to/tvshow.nfo"
+
+ def test_serie_creation_without_nfo_path(self):
+ """Test creating Serie without NFO path defaults to None."""
+ serie = Serie(
+ key="test-series",
+ name="Test Series",
+ site="https://example.com",
+ folder="Test Folder",
+ episodeDict={1: [1]}
+ )
+
+ assert serie.nfo_path is None
+
+ def test_serie_nfo_path_setter(self):
+ """Test setting NFO path property."""
+ serie = Serie(
+ key="test-series",
+ name="Test Series",
+ site="https://example.com",
+ folder="Test Folder",
+ episodeDict={1: [1]}
+ )
+
+ serie.nfo_path = "/new/path/tvshow.nfo"
+ assert serie.nfo_path == "/new/path/tvshow.nfo"
+
+ def test_has_nfo_with_existing_file(self, tmp_path):
+ """Test has_nfo returns True when NFO file exists."""
+ # Create a test directory structure
+ base_dir = tmp_path / "anime"
+ series_dir = base_dir / "Test Series"
+ series_dir.mkdir(parents=True)
+
+ nfo_file = series_dir / "tvshow.nfo"
+ nfo_file.write_text("test nfo content")
+
+ serie = Serie(
+ key="test-series",
+ name="Test Series",
+ site="https://example.com",
+ folder="Test Series",
+ episodeDict={1: [1]}
+ )
+
+ assert serie.has_nfo(str(base_dir)) is True
+
+ def test_has_nfo_with_missing_file(self, tmp_path):
+ """Test has_nfo returns False when NFO file doesn't exist."""
+ base_dir = tmp_path / "anime"
+ base_dir.mkdir(parents=True)
+
+ serie = Serie(
+ key="test-series",
+ name="Test Series",
+ site="https://example.com",
+ folder="Test Series",
+ episodeDict={1: [1]}
+ )
+
+ assert serie.has_nfo(str(base_dir)) is False
+
+ def test_has_nfo_with_nfo_path_set(self, tmp_path):
+ """Test has_nfo using nfo_path when base_directory not provided."""
+ nfo_file = tmp_path / "tvshow.nfo"
+ nfo_file.write_text("test nfo content")
+
+ serie = Serie(
+ key="test-series",
+ name="Test Series",
+ site="https://example.com",
+ folder="Test Series",
+ episodeDict={1: [1]},
+ nfo_path=str(nfo_file)
+ )
+
+ assert serie.has_nfo() is True
+
+ def test_has_nfo_without_base_directory_or_path(self):
+ """Test has_nfo returns False when no base_directory or nfo_path."""
+ serie = Serie(
+ key="test-series",
+ name="Test Series",
+ site="https://example.com",
+ folder="Test Series",
+ episodeDict={1: [1]}
+ )
+
+ assert serie.has_nfo() is False
+
+ def test_has_poster_with_existing_file(self, tmp_path):
+ """Test has_poster returns True when poster.jpg exists."""
+ base_dir = tmp_path / "anime"
+ series_dir = base_dir / "Test Series"
+ series_dir.mkdir(parents=True)
+
+ poster_file = series_dir / "poster.jpg"
+ poster_file.write_text("test image data")
+
+ serie = Serie(
+ key="test-series",
+ name="Test Series",
+ site="https://example.com",
+ folder="Test Series",
+ episodeDict={1: [1]}
+ )
+
+ assert serie.has_poster(str(base_dir)) is True
+
+ def test_has_poster_with_missing_file(self, tmp_path):
+ """Test has_poster returns False when poster.jpg doesn't exist."""
+ base_dir = tmp_path / "anime"
+ base_dir.mkdir(parents=True)
+
+ serie = Serie(
+ key="test-series",
+ name="Test Series",
+ site="https://example.com",
+ folder="Test Series",
+ episodeDict={1: [1]}
+ )
+
+ assert serie.has_poster(str(base_dir)) is False
+
+ def test_has_poster_without_base_directory(self):
+ """Test has_poster returns False when no base_directory provided."""
+ serie = Serie(
+ key="test-series",
+ name="Test Series",
+ site="https://example.com",
+ folder="Test Series",
+ episodeDict={1: [1]}
+ )
+
+ assert serie.has_poster() is False
+
+ def test_has_logo_with_existing_file(self, tmp_path):
+ """Test has_logo returns True when logo.png exists."""
+ base_dir = tmp_path / "anime"
+ series_dir = base_dir / "Test Series"
+ series_dir.mkdir(parents=True)
+
+ logo_file = series_dir / "logo.png"
+ logo_file.write_text("test logo data")
+
+ serie = Serie(
+ key="test-series",
+ name="Test Series",
+ site="https://example.com",
+ folder="Test Series",
+ episodeDict={1: [1]}
+ )
+
+ assert serie.has_logo(str(base_dir)) is True
+
+ def test_has_logo_with_missing_file(self, tmp_path):
+ """Test has_logo returns False when logo.png doesn't exist."""
+ base_dir = tmp_path / "anime"
+ base_dir.mkdir(parents=True)
+
+ serie = Serie(
+ key="test-series",
+ name="Test Series",
+ site="https://example.com",
+ folder="Test Series",
+ episodeDict={1: [1]}
+ )
+
+ assert serie.has_logo(str(base_dir)) is False
+
+ def test_has_logo_without_base_directory(self):
+ """Test has_logo returns False when no base_directory provided."""
+ serie = Serie(
+ key="test-series",
+ name="Test Series",
+ site="https://example.com",
+ folder="Test Series",
+ episodeDict={1: [1]}
+ )
+
+ assert serie.has_logo() is False
+
+ def test_has_fanart_with_existing_file(self, tmp_path):
+ """Test has_fanart returns True when fanart.jpg exists."""
+ base_dir = tmp_path / "anime"
+ series_dir = base_dir / "Test Series"
+ series_dir.mkdir(parents=True)
+
+ fanart_file = series_dir / "fanart.jpg"
+ fanart_file.write_text("test fanart data")
+
+ serie = Serie(
+ key="test-series",
+ name="Test Series",
+ site="https://example.com",
+ folder="Test Series",
+ episodeDict={1: [1]}
+ )
+
+ assert serie.has_fanart(str(base_dir)) is True
+
+ def test_has_fanart_with_missing_file(self, tmp_path):
+ """Test has_fanart returns False when fanart.jpg doesn't exist."""
+ base_dir = tmp_path / "anime"
+ base_dir.mkdir(parents=True)
+
+ serie = Serie(
+ key="test-series",
+ name="Test Series",
+ site="https://example.com",
+ folder="Test Series",
+ episodeDict={1: [1]}
+ )
+
+ assert serie.has_fanart(str(base_dir)) is False
+
+ def test_has_fanart_without_base_directory(self):
+ """Test has_fanart returns False when no base_directory provided."""
+ serie = Serie(
+ key="test-series",
+ name="Test Series",
+ site="https://example.com",
+ folder="Test Series",
+ episodeDict={1: [1]}
+ )
+
+ assert serie.has_fanart() is False
+
+ def test_to_dict_includes_nfo_path(self):
+ """Test that to_dict includes nfo_path field."""
+ serie = Serie(
+ key="test-series",
+ name="Test Series",
+ site="https://example.com",
+ folder="Test Folder",
+ episodeDict={1: [1, 2], 2: [1]},
+ year=2024,
+ nfo_path="/path/to/tvshow.nfo"
+ )
+
+ result = serie.to_dict()
+
+ assert result["nfo_path"] == "/path/to/tvshow.nfo"
+ assert result["key"] == "test-series"
+ assert result["name"] == "Test Series"
+ assert result["year"] == 2024
+
+ def test_to_dict_with_none_nfo_path(self):
+ """Test that to_dict handles None nfo_path."""
+ serie = Serie(
+ key="test-series",
+ name="Test Series",
+ site="https://example.com",
+ folder="Test Folder",
+ episodeDict={1: [1]}
+ )
+
+ result = serie.to_dict()
+
+ assert result["nfo_path"] is None
+
+ def test_from_dict_with_nfo_path(self):
+ """Test that from_dict correctly loads nfo_path."""
+ data = {
+ "key": "test-series",
+ "name": "Test Series",
+ "site": "https://example.com",
+ "folder": "Test Folder",
+ "episodeDict": {"1": [1, 2]},
+ "year": 2024,
+ "nfo_path": "/path/to/tvshow.nfo"
+ }
+
+ serie = Serie.from_dict(data)
+
+ assert serie.nfo_path == "/path/to/tvshow.nfo"
+ assert serie.key == "test-series"
+ assert serie.year == 2024
+
+ def test_from_dict_without_nfo_path(self):
+ """Test that from_dict handles missing nfo_path (backward compatibility)."""
+ data = {
+ "key": "test-series",
+ "name": "Test Series",
+ "site": "https://example.com",
+ "folder": "Test Folder",
+ "episodeDict": {"1": [1, 2]}
+ }
+
+ serie = Serie.from_dict(data)
+
+ assert serie.nfo_path is None
+ assert serie.key == "test-series"
+
+ def test_save_and_load_file_with_nfo_path(self, tmp_path):
+ """Test that save_to_file and load_from_file preserve nfo_path."""
+ serie = Serie(
+ key="test-series",
+ name="Test Series",
+ site="https://example.com",
+ folder="Test Folder",
+ episodeDict={1: [1, 2], 2: [1]},
+ year=2024,
+ nfo_path="/path/to/tvshow.nfo"
+ )
+
+ file_path = tmp_path / "data"
+
+ with pytest.warns(DeprecationWarning):
+ serie.save_to_file(str(file_path))
+
+ with pytest.warns(DeprecationWarning):
+ loaded_serie = Serie.load_from_file(str(file_path))
+
+ assert loaded_serie.nfo_path == "/path/to/tvshow.nfo"
+ assert loaded_serie.key == "test-series"
+ assert loaded_serie.year == 2024
+
diff --git a/tests/unit/test_serie_list.py b/tests/unit/test_serie_list.py
index 99d858a..b262138 100644
--- a/tests/unit/test_serie_list.py
+++ b/tests/unit/test_serie_list.py
@@ -294,3 +294,228 @@ class TestSerieListBackwardCompatibility:
assert serie_list.contains(sample_serie.key)
loaded = serie_list.get_by_key(sample_serie.key)
assert loaded.name == sample_serie.name
+
+
+class TestSerieListNFOFeatures:
+ """Test SerieList NFO detection and logging."""
+
+ def test_load_series_detects_nfo_file(self, temp_directory, caplog):
+ """Test load_series detects and sets nfo_path for series with NFO."""
+ import logging
+ caplog.set_level(logging.INFO)
+
+ # Create series folder with data file and NFO
+ folder_name = "Test Series"
+ folder_path = os.path.join(temp_directory, folder_name)
+ os.makedirs(folder_path)
+
+ serie = Serie(
+ key="test-series",
+ name="Test Series",
+ site="https://example.com",
+ folder=folder_name,
+ episodeDict={1: [1, 2]}
+ )
+
+ data_path = os.path.join(folder_path, "data")
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", DeprecationWarning)
+ serie.save_to_file(data_path)
+
+ # Create NFO file
+ nfo_path = os.path.join(folder_path, "tvshow.nfo")
+ with open(nfo_path, "w") as f:
+ f.write("")
+
+ # Load series
+ serie_list = SerieList(temp_directory)
+
+ # Verify NFO was detected
+ loaded = serie_list.get_by_key("test-series")
+ assert loaded is not None
+ assert loaded.nfo_path == nfo_path
+
+ # Verify logging
+ assert "1 with NFO" in caplog.text
+
+ def test_load_series_detects_missing_nfo(self, temp_directory, caplog):
+ """Test load_series logs when NFO is missing."""
+ import logging
+ caplog.set_level(logging.DEBUG)
+
+ # Create series folder with data file but NO NFO
+ folder_name = "Test Series"
+ folder_path = os.path.join(temp_directory, folder_name)
+ os.makedirs(folder_path)
+
+ serie = Serie(
+ key="test-series",
+ name="Test Series",
+ site="https://example.com",
+ folder=folder_name,
+ episodeDict={1: [1, 2]}
+ )
+
+ data_path = os.path.join(folder_path, "data")
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", DeprecationWarning)
+ serie.save_to_file(data_path)
+
+ # Load series
+ serie_list = SerieList(temp_directory)
+
+ # Verify NFO not set
+ loaded = serie_list.get_by_key("test-series")
+ assert loaded is not None
+ assert loaded.nfo_path is None
+
+ # Verify logging
+ assert "missing tvshow.nfo" in caplog.text
+
+ def test_load_series_detects_media_files(self, temp_directory, caplog):
+ """Test load_series detects poster, logo, and fanart files."""
+ import logging
+ caplog.set_level(logging.INFO)
+
+ # Create series folder with all media files
+ folder_name = "Test Series"
+ folder_path = os.path.join(temp_directory, folder_name)
+ os.makedirs(folder_path)
+
+ serie = Serie(
+ key="test-series",
+ name="Test Series",
+ site="https://example.com",
+ folder=folder_name,
+ episodeDict={1: [1, 2]}
+ )
+
+ data_path = os.path.join(folder_path, "data")
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", DeprecationWarning)
+ serie.save_to_file(data_path)
+
+ # Create media files
+ with open(os.path.join(folder_path, "poster.jpg"), "w") as f:
+ f.write("poster data")
+ with open(os.path.join(folder_path, "logo.png"), "w") as f:
+ f.write("logo data")
+ with open(os.path.join(folder_path, "fanart.jpg"), "w") as f:
+ f.write("fanart data")
+
+ # Load series
+ serie_list = SerieList(temp_directory)
+
+ # Verify logging shows all media found
+ assert "Poster (1/1)" in caplog.text
+ assert "Logo (1/1)" in caplog.text
+ assert "Fanart (1/1)" in caplog.text
+
+ def test_load_series_detects_missing_media_files(
+ self, temp_directory, caplog
+ ):
+ """Test load_series logs when media files are missing."""
+ import logging
+ caplog.set_level(logging.DEBUG)
+
+ # Create series folder with NO media files
+ folder_name = "Test Series"
+ folder_path = os.path.join(temp_directory, folder_name)
+ os.makedirs(folder_path)
+
+ serie = Serie(
+ key="test-series",
+ name="Test Series",
+ site="https://example.com",
+ folder=folder_name,
+ episodeDict={1: [1, 2]}
+ )
+
+ data_path = os.path.join(folder_path, "data")
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", DeprecationWarning)
+ serie.save_to_file(data_path)
+
+ # Load series
+ serie_list = SerieList(temp_directory)
+
+ # Verify logging shows missing media
+ assert "missing poster.jpg" in caplog.text
+ assert "missing logo.png" in caplog.text
+ assert "missing fanart.jpg" in caplog.text
+
+ def test_load_series_summary_statistics(self, temp_directory, caplog):
+ """Test load_series logs summary statistics for NFO and media."""
+ import logging
+ caplog.set_level(logging.INFO)
+
+ # Create multiple series with varying NFO/media status
+ for i in range(3):
+ folder_name = f"Series {i}"
+ folder_path = os.path.join(temp_directory, folder_name)
+ os.makedirs(folder_path)
+
+ serie = Serie(
+ key=f"series-{i}",
+ name=f"Series {i}",
+ site="https://example.com",
+ folder=folder_name,
+ episodeDict={1: [1]}
+ )
+
+ data_path = os.path.join(folder_path, "data")
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", DeprecationWarning)
+ serie.save_to_file(data_path)
+
+ # First series has everything
+ if i == 0:
+ with open(os.path.join(folder_path, "tvshow.nfo"), "w") as f:
+ f.write("")
+ with open(os.path.join(folder_path, "poster.jpg"), "w") as f:
+ f.write("poster")
+ with open(os.path.join(folder_path, "logo.png"), "w") as f:
+ f.write("logo")
+ with open(os.path.join(folder_path, "fanart.jpg"), "w") as f:
+ f.write("fanart")
+ # Second series has NFO and poster only
+ elif i == 1:
+ with open(os.path.join(folder_path, "tvshow.nfo"), "w") as f:
+ f.write("")
+ with open(os.path.join(folder_path, "poster.jpg"), "w") as f:
+ f.write("poster")
+ # Third series has nothing
+
+ # Load series
+ serie_list = SerieList(temp_directory)
+
+ # Verify summary statistics
+ assert "3 series total" in caplog.text
+ assert "2 with NFO, 1 without NFO" in caplog.text
+ assert "Poster (2/3)" in caplog.text
+ assert "Logo (1/3)" in caplog.text
+ assert "Fanart (1/3)" in caplog.text
+
+ def test_load_series_handles_load_failure(self, temp_directory, caplog):
+ """Test load_series handles series that fail to load gracefully."""
+ import logging
+ caplog.set_level(logging.ERROR)
+
+ # Create folder with invalid data file
+ folder_name = "Invalid Series"
+ folder_path = os.path.join(temp_directory, folder_name)
+ os.makedirs(folder_path)
+
+ data_path = os.path.join(folder_path, "data")
+ with open(data_path, "w") as f:
+ f.write("invalid json {{{")
+
+ # Load series - should not crash
+ serie_list = SerieList(temp_directory)
+
+ # Verify error logged
+ assert "Failed to load metadata" in caplog.text
+
+ # Should not be in keyDict
+ assert len(serie_list.keyDict) == 0
+