- 176 unit tests covering ConfigManager, FileClassifier, EpisodeRenamer, TVDBClient, MockTVDBClient, TVDBCache, and episode_matcher CLI - Tests organized in tests/ with conftest.py shared fixtures - pytest-cov configured with 90% coverage threshold in pyproject.toml - CI workflow updated with COVERAGE_CORE=sysmon for Python 3.13 compat - .gitignore updated for coverage artifacts and venv
225 lines
7.9 KiB
Python
225 lines
7.9 KiB
Python
"""Tests for src/TVDBProvider/tvdb_client.py."""
|
|
import sys
|
|
from pathlib import Path
|
|
from unittest.mock import patch, MagicMock, PropertyMock
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
|
import pytest
|
|
from src.TVDBProvider.tvdb_client import TVDBClient, MockTVDBClient
|
|
|
|
|
|
class TestTVDBClientInit:
|
|
def test_init(self):
|
|
client = TVDBClient()
|
|
assert client.base_url == "https://api4.thetvdb.com/v4"
|
|
assert client.token is None
|
|
assert "Content-Type" in client.headers
|
|
|
|
def test_init_has_cache(self):
|
|
client = TVDBClient()
|
|
assert client.cache is not None
|
|
|
|
|
|
class TestTVDBClientAuthenticate:
|
|
@patch("src.TVDBProvider.tvdb_client.requests.post")
|
|
def test_auth_success(self, mock_post):
|
|
mock_post.return_value.status_code = 200
|
|
mock_post.return_value.json.return_value = {"data": {"token": "abc123"}}
|
|
client = TVDBClient()
|
|
assert client.authenticate("my_api_key") is True
|
|
assert client.token == "abc123"
|
|
assert "Bearer abc123" in client.headers["Authorization"]
|
|
|
|
@patch("src.TVDBProvider.tvdb_client.requests.post")
|
|
def test_auth_failure_status(self, mock_post):
|
|
mock_post.return_value.status_code = 401
|
|
client = TVDBClient()
|
|
assert client.authenticate("bad_key") is False
|
|
assert client.token is None
|
|
|
|
@patch("src.TVDBProvider.tvdb_client.requests.post")
|
|
def test_auth_exception(self, mock_post):
|
|
mock_post.side_effect = Exception("network error")
|
|
client = TVDBClient()
|
|
assert client.authenticate("key") is False
|
|
|
|
|
|
class TestTVDBClientSearch:
|
|
@patch("src.TVDBProvider.tvdb_client.requests.get")
|
|
def test_search_success(self, mock_get):
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {
|
|
"data": [{"tvdb_id": 123, "name": "Test Show"}]
|
|
}
|
|
client = TVDBClient()
|
|
client.token = "tok"
|
|
result = client.search_series("Test Show")
|
|
assert result["tvdb_id"] == 123
|
|
|
|
@patch("src.TVDBProvider.tvdb_client.requests.get")
|
|
def test_search_no_results(self, mock_get):
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {"data": []}
|
|
client = TVDBClient()
|
|
client.token = "tok"
|
|
result = client.search_series("NoMatch")
|
|
assert result is None
|
|
|
|
def test_search_not_authenticated(self):
|
|
client = TVDBClient()
|
|
result = client.search_series("Show")
|
|
assert result is None
|
|
|
|
@patch("src.TVDBProvider.tvdb_client.requests.get")
|
|
def test_search_error(self, mock_get):
|
|
mock_get.side_effect = Exception("timeout")
|
|
client = TVDBClient()
|
|
client.token = "tok"
|
|
result = client.search_series("Show")
|
|
assert result is None
|
|
|
|
|
|
class TestTVDBClientSeasonEpisodes:
|
|
@patch("src.TVDBProvider.tvdb_client.requests.get")
|
|
def test_get_season_episodes(self, mock_get):
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {
|
|
"data": {"episodes": [{"number": 1, "name": "E1"}]}
|
|
}
|
|
client = TVDBClient()
|
|
client.token = "tok"
|
|
result = client.get_season_episodes(123, 1)
|
|
assert len(result) == 1
|
|
|
|
def test_episodes_not_authenticated(self):
|
|
client = TVDBClient()
|
|
result = client.get_season_episodes(123, 1)
|
|
assert result is None
|
|
|
|
|
|
class TestTVDBClientSeriesInfo:
|
|
@patch.object(TVDBClient, "search_series")
|
|
def test_get_series_info(self, mock_search):
|
|
mock_search.return_value = {"tvdb_id": 123, "name": "Test", "slug": "test", "year": 2020}
|
|
client = TVDBClient()
|
|
client.token = "tok"
|
|
result = client.get_series_info("Test")
|
|
assert result["id"] == 123
|
|
assert result["name"] == "Test"
|
|
|
|
@patch.object(TVDBClient, "search_series")
|
|
def test_get_series_info_no_match(self, mock_search):
|
|
mock_search.return_value = None
|
|
client = TVDBClient()
|
|
client.token = "tok"
|
|
result = client.get_series_info("NoMatch")
|
|
assert result is None
|
|
|
|
|
|
class TestTVDBClientEpisodeDurations:
|
|
def test_get_episode_durations(self, tmp_path):
|
|
with patch("src.TVDBProvider.tvdb_client.TVDBClient.__init__") as mock_init:
|
|
mock_init.return_value = None
|
|
client = TVDBClient()
|
|
client.base_url = "https://api4.thetvdb.com/v4"
|
|
client.token = "tok"
|
|
client.headers = {"Content-Type": "application/json"}
|
|
|
|
mock_cache = MagicMock()
|
|
mock_cache.get_cached_episodes.return_value = None
|
|
client.cache = mock_cache
|
|
|
|
client.get_series_info = MagicMock(return_value={"id": 123})
|
|
client.get_season_episodes = MagicMock(return_value=[
|
|
{"number": 1, "name": "E1", "runtime": 45, "aired": "2020-01-01"},
|
|
{"number": 2, "name": "E2", "runtime": 50, "aired": "2020-01-08"},
|
|
])
|
|
|
|
result = client.get_episode_durations("Test", 1)
|
|
assert len(result) == 2
|
|
assert result[0]["episode_number"] == 1
|
|
assert result[0]["runtime"] == 45
|
|
mock_cache.cache_episodes.assert_called_once()
|
|
|
|
def test_get_episode_durations_cached(self, tmp_path):
|
|
with patch("src.TVDBProvider.tvdb_client.TVDBClient.__init__") as mock_init:
|
|
mock_init.return_value = None
|
|
client = TVDBClient()
|
|
client.token = "tok"
|
|
|
|
mock_cache = MagicMock()
|
|
mock_cache.get_cached_episodes.return_value = [
|
|
{"episode_number": 1, "runtime": 45}
|
|
]
|
|
client.cache = mock_cache
|
|
|
|
result = client.get_episode_durations("Test", 1)
|
|
assert len(result) == 1
|
|
|
|
def test_get_episode_durations_no_series(self, tmp_path):
|
|
with patch("src.TVDBProvider.tvdb_client.TVDBClient.__init__") as mock_init:
|
|
mock_init.return_value = None
|
|
client = TVDBClient()
|
|
client.token = "tok"
|
|
|
|
mock_cache = MagicMock()
|
|
mock_cache.get_cached_episodes.return_value = None
|
|
client.cache = mock_cache
|
|
|
|
client.get_series_info = MagicMock(return_value=None)
|
|
|
|
result = client.get_episode_durations("NoShow", 1)
|
|
assert result is None
|
|
|
|
|
|
class TestMockTVDBClient:
|
|
def test_mock_auth(self):
|
|
client = MockTVDBClient()
|
|
assert client.authenticate() is True
|
|
assert client.authenticated is True
|
|
|
|
def test_mock_auth_with_key(self):
|
|
client = MockTVDBClient()
|
|
assert client.authenticate("some_key") is True
|
|
|
|
def test_mock_episodes_unauthenticated(self):
|
|
client = MockTVDBClient()
|
|
result = client.get_episode_durations("Show", 1)
|
|
assert result is None
|
|
|
|
def test_mock_episodes_drama(self):
|
|
client = MockTVDBClient()
|
|
client.authenticate()
|
|
result = client.get_episode_durations("Game of Thrones", 1)
|
|
assert len(result) == 10
|
|
|
|
def test_mock_episodes_sitcom(self):
|
|
client = MockTVDBClient()
|
|
client.authenticate()
|
|
result = client.get_episode_durations("Friends", 1)
|
|
assert len(result) == 24
|
|
|
|
def test_mock_episodes_default(self):
|
|
client = MockTVDBClient()
|
|
client.authenticate()
|
|
result = client.get_episode_durations("Unknown Show", 1)
|
|
assert len(result) == 22
|
|
|
|
def test_mock_episode_structure(self):
|
|
client = MockTVDBClient()
|
|
client.authenticate()
|
|
result = client.get_episode_durations("Test", 1)
|
|
ep = result[0]
|
|
assert ep["episode_number"] == 1
|
|
assert ep["runtime"] == 45
|
|
assert "name" in ep
|
|
assert "aired" in ep
|
|
|
|
def test_mock_cached_episodes(self):
|
|
client = MockTVDBClient()
|
|
client.authenticate()
|
|
r1 = client.get_episode_durations("Test", 1)
|
|
r2 = client.get_episode_durations("Test", 1)
|
|
assert r1 is not None and r2 is not None
|