Task 4: Add Services & Utilities tests (66 tests)
- test_media_utils.py: 29 tests for check_media_files, get_media_file_paths, has_all_images, count_video_files, has_video_files, constants - test_nfo_factory.py: 11 tests for NFOServiceFactory.create, create_optional, get_nfo_factory singleton, create_nfo_service convenience - test_series_manager_service.py: 15 tests for init, from_settings, process_nfo_for_series, scan_and_process_nfo, close - test_templates_utils.py: 4 tests for TEMPLATES_DIR path resolution - test_error_controller.py: 7 tests for 404/500 handlers (API vs HTML)
This commit is contained in:
189
tests/unit/test_nfo_factory.py
Normal file
189
tests/unit/test_nfo_factory.py
Normal file
@@ -0,0 +1,189 @@
|
||||
"""Unit tests for NFO service factory module.
|
||||
|
||||
Tests factory instantiation, configuration precedence, singleton pattern,
|
||||
and convenience functions for creating NFOService instances.
|
||||
"""
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from src.core.services.nfo_factory import (
|
||||
NFOServiceFactory,
|
||||
create_nfo_service,
|
||||
get_nfo_factory,
|
||||
)
|
||||
|
||||
|
||||
class TestNFOServiceFactoryCreate:
|
||||
"""Tests for NFOServiceFactory.create method."""
|
||||
|
||||
@patch("src.core.services.nfo_factory.NFOService")
|
||||
@patch("src.core.services.nfo_factory.settings")
|
||||
def test_create_with_explicit_api_key(self, mock_settings, mock_nfo_cls):
|
||||
"""Explicit API key takes priority over settings."""
|
||||
mock_settings.tmdb_api_key = "settings_key"
|
||||
mock_settings.anime_directory = "/anime"
|
||||
mock_settings.nfo_image_size = "original"
|
||||
mock_settings.nfo_auto_create = False
|
||||
|
||||
factory = NFOServiceFactory()
|
||||
factory.create(tmdb_api_key="explicit_key")
|
||||
mock_nfo_cls.assert_called_once_with(
|
||||
tmdb_api_key="explicit_key",
|
||||
anime_directory="/anime",
|
||||
image_size="original",
|
||||
auto_create=False,
|
||||
)
|
||||
|
||||
@patch("src.core.services.nfo_factory.NFOService")
|
||||
@patch("src.core.services.nfo_factory.settings")
|
||||
def test_create_falls_back_to_settings(self, mock_settings, mock_nfo_cls):
|
||||
"""Falls back to settings when no explicit key provided."""
|
||||
mock_settings.tmdb_api_key = "settings_key"
|
||||
mock_settings.anime_directory = "/anime"
|
||||
mock_settings.nfo_image_size = "w500"
|
||||
mock_settings.nfo_auto_create = True
|
||||
|
||||
factory = NFOServiceFactory()
|
||||
factory.create()
|
||||
mock_nfo_cls.assert_called_once_with(
|
||||
tmdb_api_key="settings_key",
|
||||
anime_directory="/anime",
|
||||
image_size="w500",
|
||||
auto_create=True,
|
||||
)
|
||||
|
||||
@patch("src.core.services.nfo_factory.settings")
|
||||
def test_create_raises_without_api_key(self, mock_settings):
|
||||
"""Raises ValueError when no API key available from any source."""
|
||||
mock_settings.tmdb_api_key = None
|
||||
factory = NFOServiceFactory()
|
||||
factory._get_api_key_from_config = MagicMock(return_value=None)
|
||||
with pytest.raises(ValueError, match="TMDB API key not configured"):
|
||||
factory.create()
|
||||
|
||||
@patch("src.core.services.nfo_factory.NFOService")
|
||||
@patch("src.core.services.nfo_factory.settings")
|
||||
def test_create_with_all_custom_params(self, mock_settings, mock_nfo_cls):
|
||||
"""All parameters can be overridden."""
|
||||
mock_settings.tmdb_api_key = "default"
|
||||
mock_settings.anime_directory = "/default"
|
||||
mock_settings.nfo_image_size = "original"
|
||||
mock_settings.nfo_auto_create = False
|
||||
|
||||
factory = NFOServiceFactory()
|
||||
factory.create(
|
||||
tmdb_api_key="custom",
|
||||
anime_directory="/custom",
|
||||
image_size="w300",
|
||||
auto_create=True,
|
||||
)
|
||||
mock_nfo_cls.assert_called_once_with(
|
||||
tmdb_api_key="custom",
|
||||
anime_directory="/custom",
|
||||
image_size="w300",
|
||||
auto_create=True,
|
||||
)
|
||||
|
||||
@patch("src.core.services.nfo_factory.NFOService")
|
||||
@patch("src.core.services.nfo_factory.settings")
|
||||
def test_create_uses_config_json_fallback(self, mock_settings, mock_nfo_cls):
|
||||
"""Falls back to config.json when settings has no key."""
|
||||
mock_settings.tmdb_api_key = None
|
||||
mock_settings.anime_directory = "/anime"
|
||||
mock_settings.nfo_image_size = "original"
|
||||
mock_settings.nfo_auto_create = False
|
||||
|
||||
factory = NFOServiceFactory()
|
||||
factory._get_api_key_from_config = MagicMock(return_value="config_key")
|
||||
factory.create()
|
||||
mock_nfo_cls.assert_called_once()
|
||||
call_kwargs = mock_nfo_cls.call_args[1]
|
||||
assert call_kwargs["tmdb_api_key"] == "config_key"
|
||||
|
||||
|
||||
class TestNFOServiceFactoryCreateOptional:
|
||||
"""Tests for NFOServiceFactory.create_optional method."""
|
||||
|
||||
@patch("src.core.services.nfo_factory.settings")
|
||||
def test_returns_none_without_api_key(self, mock_settings):
|
||||
"""Returns None instead of raising when no API key."""
|
||||
mock_settings.tmdb_api_key = None
|
||||
factory = NFOServiceFactory()
|
||||
factory._get_api_key_from_config = MagicMock(return_value=None)
|
||||
result = factory.create_optional()
|
||||
assert result is None
|
||||
|
||||
@patch("src.core.services.nfo_factory.NFOService")
|
||||
@patch("src.core.services.nfo_factory.settings")
|
||||
def test_returns_service_when_configured(self, mock_settings, mock_nfo_cls):
|
||||
"""Returns NFOService when configuration is available."""
|
||||
mock_settings.tmdb_api_key = "key123"
|
||||
mock_settings.anime_directory = "/anime"
|
||||
mock_settings.nfo_image_size = "original"
|
||||
mock_settings.nfo_auto_create = False
|
||||
|
||||
factory = NFOServiceFactory()
|
||||
result = factory.create_optional()
|
||||
assert result is not None
|
||||
|
||||
|
||||
class TestGetNfoFactory:
|
||||
"""Tests for get_nfo_factory singleton function."""
|
||||
|
||||
def test_returns_factory_instance(self):
|
||||
"""Returns an NFOServiceFactory instance."""
|
||||
import src.core.services.nfo_factory as mod
|
||||
old = mod._factory_instance
|
||||
try:
|
||||
mod._factory_instance = None
|
||||
factory = get_nfo_factory()
|
||||
assert isinstance(factory, NFOServiceFactory)
|
||||
finally:
|
||||
mod._factory_instance = old
|
||||
|
||||
def test_returns_same_instance(self):
|
||||
"""Repeated calls return the same singleton."""
|
||||
import src.core.services.nfo_factory as mod
|
||||
old = mod._factory_instance
|
||||
try:
|
||||
mod._factory_instance = None
|
||||
f1 = get_nfo_factory()
|
||||
f2 = get_nfo_factory()
|
||||
assert f1 is f2
|
||||
finally:
|
||||
mod._factory_instance = old
|
||||
|
||||
|
||||
class TestCreateNfoService:
|
||||
"""Tests for create_nfo_service convenience function."""
|
||||
|
||||
@patch("src.core.services.nfo_factory.NFOService")
|
||||
@patch("src.core.services.nfo_factory.settings")
|
||||
def test_convenience_function_creates_service(
|
||||
self, mock_settings, mock_nfo_cls
|
||||
):
|
||||
"""Convenience function delegates to factory.create()."""
|
||||
mock_settings.tmdb_api_key = "key"
|
||||
mock_settings.anime_directory = "/anime"
|
||||
mock_settings.nfo_image_size = "original"
|
||||
mock_settings.nfo_auto_create = False
|
||||
|
||||
result = create_nfo_service()
|
||||
mock_nfo_cls.assert_called_once()
|
||||
|
||||
@patch("src.core.services.nfo_factory.settings")
|
||||
def test_convenience_function_raises_without_key(self, mock_settings):
|
||||
"""Convenience function raises ValueError without key."""
|
||||
mock_settings.tmdb_api_key = None
|
||||
import src.core.services.nfo_factory as mod
|
||||
old = mod._factory_instance
|
||||
try:
|
||||
mod._factory_instance = None
|
||||
factory = get_nfo_factory()
|
||||
factory._get_api_key_from_config = MagicMock(return_value=None)
|
||||
with pytest.raises(ValueError):
|
||||
factory.create()
|
||||
finally:
|
||||
mod._factory_instance = old
|
||||
Reference in New Issue
Block a user