"""Unit tests for base_provider.py - Abstract base class and interface contracts.""" from abc import ABC from typing import Any, Dict, List from unittest.mock import MagicMock import pytest from src.core.providers.base_provider import Loader class TestLoaderAbstractInterface: """Test that Loader defines the correct abstract interface.""" def test_loader_is_abstract_class(self): """Loader should be an abstract class and not directly instantiable.""" assert issubclass(Loader, ABC) with pytest.raises(TypeError): Loader() def test_loader_defines_search_method(self): """Loader must define abstract search method.""" assert hasattr(Loader, "search") assert getattr(Loader.search, "__isabstractmethod__", False) def test_loader_defines_is_language_method(self): """Loader must define abstract is_language method.""" assert hasattr(Loader, "is_language") assert getattr(Loader.is_language, "__isabstractmethod__", False) def test_loader_defines_download_method(self): """Loader must define abstract download method.""" assert hasattr(Loader, "download") assert getattr(Loader.download, "__isabstractmethod__", False) def test_loader_defines_get_site_key_method(self): """Loader must define abstract get_site_key method.""" assert hasattr(Loader, "get_site_key") assert getattr(Loader.get_site_key, "__isabstractmethod__", False) def test_loader_defines_get_title_method(self): """Loader must define abstract get_title method.""" assert hasattr(Loader, "get_title") assert getattr(Loader.get_title, "__isabstractmethod__", False) def test_loader_defines_get_season_episode_count_method(self): """Loader must define abstract get_season_episode_count method.""" assert hasattr(Loader, "get_season_episode_count") assert getattr( Loader.get_season_episode_count, "__isabstractmethod__", False ) def test_loader_defines_subscribe_download_progress(self): """Loader must define abstract subscribe_download_progress method.""" assert hasattr(Loader, "subscribe_download_progress") assert getattr( Loader.subscribe_download_progress, "__isabstractmethod__", False ) def test_loader_defines_unsubscribe_download_progress(self): """Loader must define abstract unsubscribe_download_progress method.""" assert hasattr(Loader, "unsubscribe_download_progress") assert getattr( Loader.unsubscribe_download_progress, "__isabstractmethod__", False ) class ConcreteLoader(Loader): """Minimal concrete implementation for testing inheritance.""" def subscribe_download_progress(self, handler): pass def unsubscribe_download_progress(self, handler): pass def search(self, word: str) -> List[Dict[str, Any]]: return [{"title": word}] def is_language( self, season: int, episode: int, key: str, language: str = "German Dub", ) -> bool: return True def download( self, base_directory: str, serie_folder: str, season: int, episode: int, key: str, language: str = "German Dub", ) -> bool: return True def get_site_key(self) -> str: return "test.provider" def get_title(self, key: str) -> str: return f"Title for {key}" def get_season_episode_count(self, slug: str) -> Dict[int, int]: return {1: 12, 2: 24} class TestLoaderInheritance: """Test concrete implementations of the Loader interface.""" def test_concrete_loader_can_be_instantiated(self): """A fully implemented subclass should instantiate without error.""" loader = ConcreteLoader() assert isinstance(loader, Loader) def test_concrete_loader_search(self): """Concrete search should return a list of dicts.""" loader = ConcreteLoader() result = loader.search("Naruto") assert isinstance(result, list) assert result[0]["title"] == "Naruto" def test_concrete_loader_is_language(self): """Concrete is_language should return bool.""" loader = ConcreteLoader() assert loader.is_language(1, 1, "naruto") is True def test_concrete_loader_is_language_default_param(self): """is_language should default to 'German Dub' language.""" loader = ConcreteLoader() result = loader.is_language(1, 1, "naruto") assert isinstance(result, bool) def test_concrete_loader_download(self): """Concrete download should return bool.""" loader = ConcreteLoader() result = loader.download("/base", "folder", 1, 1, "naruto") assert result is True def test_concrete_loader_get_site_key(self): """Concrete get_site_key should return a string.""" loader = ConcreteLoader() assert loader.get_site_key() == "test.provider" def test_concrete_loader_get_title(self): """Concrete get_title should return title string.""" loader = ConcreteLoader() assert loader.get_title("naruto") == "Title for naruto" def test_concrete_loader_get_season_episode_count(self): """Concrete get_season_episode_count should return dict.""" loader = ConcreteLoader() result = loader.get_season_episode_count("naruto") assert isinstance(result, dict) assert result[1] == 12 assert result[2] == 24 def test_concrete_loader_subscribe_download_progress(self): """subscribe_download_progress should accept handler without error.""" loader = ConcreteLoader() handler = MagicMock() loader.subscribe_download_progress(handler) def test_concrete_loader_unsubscribe_download_progress(self): """unsubscribe_download_progress should accept handler without error.""" loader = ConcreteLoader() handler = MagicMock() loader.unsubscribe_download_progress(handler) class IncompleteLoader(Loader): """Incomplete implementation missing some abstract methods.""" def search(self, word: str) -> List[Dict[str, Any]]: return [] def is_language(self, season, episode, key, language="German Dub"): return False # Deliberately omit download, get_site_key, etc. class TestIncompleteImplementation: """Test that incomplete implementations cannot be instantiated.""" def test_incomplete_loader_raises_type_error(self): """Loader subclass missing abstract methods cannot be instantiated.""" with pytest.raises(TypeError): IncompleteLoader() class TestLoaderMethodSignatures: """Test method signatures match the expected contract.""" def test_search_returns_list(self): """search() should return List[Dict[str, Any]].""" loader = ConcreteLoader() result = loader.search("test") assert isinstance(result, list) for item in result: assert isinstance(item, dict) def test_download_returns_bool(self): """download() should return bool.""" loader = ConcreteLoader() result = loader.download("/dir", "folder", 1, 1, "key") assert isinstance(result, bool) def test_get_season_episode_count_returns_dict_int_int(self): """get_season_episode_count() should return Dict[int, int].""" loader = ConcreteLoader() result = loader.get_season_episode_count("slug") assert isinstance(result, dict) for k, v in result.items(): assert isinstance(k, int) assert isinstance(v, int)