"""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