Files
Aniworld/tests/unit/test_base_provider.py

219 lines
7.6 KiB
Python

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