Expand test coverage: ~188 new tests across 6 critical files

- Fix failing test_authenticated_request_succeeds (dependency override)
- Expand test_anime_service.py (+35 tests: status events, DB, broadcasts)
- Create test_queue_repository.py (27 tests: CRUD, model conversion)
- Expand test_enhanced_provider.py (+24 tests: fetch, download, redirect)
- Expand test_serie_scanner.py (+25 tests: events, year extract, mp4 scan)
- Create test_database_connection.py (38 tests: sessions, transactions)
- Expand test_anime_endpoints.py (+39 tests: status, search, loading)
- Clean up docs/instructions.md TODO list
This commit is contained in:
2026-02-15 17:44:27 +01:00
parent d7ab689fe1
commit e84a220f55
8 changed files with 3254 additions and 115 deletions

View File

@@ -442,3 +442,479 @@ class TestEnhancedProviderFromHTML:
result = enhanced_loader._get_provider_from_html(1, 1, "test")
assert result == {}
# ══════════════════════════════════════════════════════════════════════════════
# New coverage tests fetch, download flow, redirect, season counts
# ══════════════════════════════════════════════════════════════════════════════
class TestFetchAnimeListWithRecovery:
"""Test _fetch_anime_list_with_recovery."""
def test_successful_fetch(self, enhanced_loader):
"""Should fetch and parse a JSON response."""
mock_response = MagicMock()
mock_response.ok = True
mock_response.text = json.dumps([{"title": "Naruto"}])
with patch(
"src.core.providers.enhanced_provider.recovery_strategies"
) as mock_rs:
mock_rs.handle_network_failure.return_value = mock_response
result = enhanced_loader._fetch_anime_list_with_recovery(
"https://example.com/search"
)
assert len(result) == 1
assert result[0]["title"] == "Naruto"
def test_404_raises_non_retryable(self, enhanced_loader):
"""404 should raise NonRetryableError."""
mock_response = MagicMock()
mock_response.ok = False
mock_response.status_code = 404
with patch(
"src.core.providers.enhanced_provider.recovery_strategies"
) as mock_rs:
mock_rs.handle_network_failure.return_value = mock_response
with pytest.raises(NonRetryableError, match="not found"):
enhanced_loader._fetch_anime_list_with_recovery(
"https://example.com/search"
)
def test_403_raises_non_retryable(self, enhanced_loader):
"""403 should raise NonRetryableError."""
mock_response = MagicMock()
mock_response.ok = False
mock_response.status_code = 403
with patch(
"src.core.providers.enhanced_provider.recovery_strategies"
) as mock_rs:
mock_rs.handle_network_failure.return_value = mock_response
with pytest.raises(NonRetryableError, match="forbidden"):
enhanced_loader._fetch_anime_list_with_recovery(
"https://example.com/search"
)
def test_500_raises_retryable(self, enhanced_loader):
"""500 should raise RetryableError."""
mock_response = MagicMock()
mock_response.ok = False
mock_response.status_code = 500
with patch(
"src.core.providers.enhanced_provider.recovery_strategies"
) as mock_rs:
mock_rs.handle_network_failure.return_value = mock_response
with pytest.raises(RetryableError, match="Server error"):
enhanced_loader._fetch_anime_list_with_recovery(
"https://example.com/search"
)
def test_network_error_raises_network_error(self, enhanced_loader):
"""requests.RequestException should raise NetworkError."""
import requests as req
with patch(
"src.core.providers.enhanced_provider.recovery_strategies"
) as mock_rs:
mock_rs.handle_network_failure.side_effect = (
req.RequestException("timeout")
)
with pytest.raises(NetworkError, match="Network error"):
enhanced_loader._fetch_anime_list_with_recovery(
"https://example.com/search"
)
class TestGetKeyHTML:
"""Test _GetKeyHTML fetching and caching."""
def test_cached_html_returned(self, enhanced_loader):
"""Already-cached key should skip HTTP call."""
mock_resp = MagicMock()
enhanced_loader._KeyHTMLDict["cached-key"] = mock_resp
result = enhanced_loader._GetKeyHTML("cached-key")
assert result is mock_resp
enhanced_loader.session.get.assert_not_called()
def test_fetches_and_caches(self, enhanced_loader):
"""Missing key should be fetched and cached."""
mock_response = MagicMock()
mock_response.ok = True
with patch(
"src.core.providers.enhanced_provider.recovery_strategies"
) as mock_rs:
mock_rs.handle_network_failure.return_value = mock_response
result = enhanced_loader._GetKeyHTML("new-key")
assert result is mock_response
assert enhanced_loader._KeyHTMLDict["new-key"] is mock_response
def test_404_raises_non_retryable(self, enhanced_loader):
"""404 from server should raise NonRetryableError."""
mock_response = MagicMock()
mock_response.ok = False
mock_response.status_code = 404
with patch(
"src.core.providers.enhanced_provider.recovery_strategies"
) as mock_rs:
mock_rs.handle_network_failure.return_value = mock_response
with pytest.raises(NonRetryableError, match="not found"):
enhanced_loader._GetKeyHTML("missing-key")
class TestGetRedirectLink:
"""Test _get_redirect_link method."""
def test_returns_link_and_provider(self, enhanced_loader):
"""Should return (link, provider_name) tuple."""
with patch.object(
enhanced_loader, "IsLanguage", return_value=True
), patch.object(
enhanced_loader,
"_get_provider_from_html",
return_value={
"VOE": {1: "https://aniworld.to/redirect/100"}
},
):
link, provider = enhanced_loader._get_redirect_link(
1, 1, "test", "German Dub"
)
assert link == "https://aniworld.to/redirect/100"
assert provider == "VOE"
def test_language_unavailable_raises(self, enhanced_loader):
"""Should raise NonRetryableError if language not available."""
with patch.object(
enhanced_loader, "IsLanguage", return_value=False
):
with pytest.raises(NonRetryableError, match="not available"):
enhanced_loader._get_redirect_link(
1, 1, "test", "German Dub"
)
def test_no_provider_found_raises(self, enhanced_loader):
"""Should raise when no provider has the language."""
with patch.object(
enhanced_loader, "IsLanguage", return_value=True
), patch.object(
enhanced_loader,
"_get_provider_from_html",
return_value={"VOE": {2: "link"}}, # English Sub only
):
with pytest.raises(NonRetryableError, match="No provider"):
enhanced_loader._get_redirect_link(
1, 1, "test", "German Dub"
)
class TestGetEmbeddedLink:
"""Test _get_embeded_link method."""
def test_returns_final_url(self, enhanced_loader):
"""Should follow redirect and return final URL."""
mock_response = MagicMock()
mock_response.url = "https://voe.sx/e/abc123"
with patch.object(
enhanced_loader,
"_get_redirect_link",
return_value=("https://aniworld.to/redirect/100", "VOE"),
), patch(
"src.core.providers.enhanced_provider.recovery_strategies"
) as mock_rs:
mock_rs.handle_network_failure.return_value = mock_response
result = enhanced_loader._get_embeded_link(
1, 1, "test", "German Dub"
)
assert result == "https://voe.sx/e/abc123"
def test_redirect_failure_raises(self, enhanced_loader):
"""Should propagate error from _get_redirect_link."""
with patch.object(
enhanced_loader,
"_get_redirect_link",
side_effect=NonRetryableError("no link"),
):
with pytest.raises(NonRetryableError):
enhanced_loader._get_embeded_link(
1, 1, "test", "German Dub"
)
class TestGetDirectLinkFromProvider:
"""Test _get_direct_link_from_provider method."""
def test_returns_link_from_voe(self, enhanced_loader):
"""Should use VOE provider to extract direct link."""
mock_provider = MagicMock()
mock_provider.get_link.return_value = (
"https://direct.example.com/video.mp4",
[],
)
enhanced_loader.Providers = MagicMock()
enhanced_loader.Providers.GetProvider.return_value = mock_provider
with patch.object(
enhanced_loader,
"_get_embeded_link",
return_value="https://voe.sx/e/abc123",
):
result = enhanced_loader._get_direct_link_from_provider(
1, 1, "test", "German Dub"
)
assert result == ("https://direct.example.com/video.mp4", [])
def test_no_embedded_link_raises(self, enhanced_loader):
"""Should raise if embedded link is None."""
with patch.object(
enhanced_loader,
"_get_embeded_link",
return_value=None,
):
with pytest.raises(NonRetryableError, match="No embedded link"):
enhanced_loader._get_direct_link_from_provider(
1, 1, "test", "German Dub"
)
def test_no_provider_raises(self, enhanced_loader):
"""Should raise if VOE provider unavailable."""
enhanced_loader.Providers = MagicMock()
enhanced_loader.Providers.GetProvider.return_value = None
with patch.object(
enhanced_loader,
"_get_embeded_link",
return_value="https://voe.sx/e/abc",
):
with pytest.raises(NonRetryableError, match="VOE provider"):
enhanced_loader._get_direct_link_from_provider(
1, 1, "test", "German Dub"
)
class TestDownloadWithRecovery:
"""Test _download_with_recovery method."""
def test_successful_download(self, enhanced_loader, tmp_path):
"""Should download, verify, and move file."""
temp_path = str(tmp_path / "temp.mp4")
output_path = str(tmp_path / "output.mp4")
# Create a fake temp file after "download"
def fake_download(*args, **kwargs):
with open(temp_path, "wb") as f:
f.write(b"fake-video-data")
return True
with patch(
"src.core.providers.enhanced_provider.recovery_strategies"
) as mock_rs, patch(
"src.core.providers.enhanced_provider.file_corruption_detector"
) as mock_fcd, patch(
"src.core.providers.enhanced_provider.get_integrity_manager"
) as mock_im:
mock_rs.handle_network_failure.return_value = (
"https://direct.example.com/v.mp4",
[],
)
mock_rs.handle_download_failure.side_effect = fake_download
mock_fcd.is_valid_video_file.return_value = True
mock_im.return_value.store_checksum.return_value = "abc123"
result = enhanced_loader._download_with_recovery(
1, 1, "test", "German Dub",
temp_path, output_path, None,
)
assert result is True
assert os.path.exists(output_path)
def test_all_providers_fail_returns_false(self, enhanced_loader, tmp_path):
"""Should return False when all providers fail."""
temp_path = str(tmp_path / "temp.mp4")
output_path = str(tmp_path / "output.mp4")
with patch(
"src.core.providers.enhanced_provider.recovery_strategies"
) as mock_rs:
mock_rs.handle_network_failure.side_effect = Exception("fail")
result = enhanced_loader._download_with_recovery(
1, 1, "test", "German Dub",
temp_path, output_path, None,
)
assert result is False
def test_corrupted_download_removed(self, enhanced_loader, tmp_path):
"""Corrupted downloads should be removed and next provider tried."""
temp_path = str(tmp_path / "temp.mp4")
output_path = str(tmp_path / "output.mp4")
# Create a fake temp file after "download"
def fake_download(*args, **kwargs):
with open(temp_path, "wb") as f:
f.write(b"corrupt")
return True
with patch(
"src.core.providers.enhanced_provider.recovery_strategies"
) as mock_rs, patch(
"src.core.providers.enhanced_provider.file_corruption_detector"
) as mock_fcd:
mock_rs.handle_network_failure.return_value = (
"https://direct.example.com/v.mp4",
[],
)
mock_rs.handle_download_failure.side_effect = fake_download
mock_fcd.is_valid_video_file.return_value = False
result = enhanced_loader._download_with_recovery(
1, 1, "test", "German Dub",
temp_path, output_path, None,
)
assert result is False
class TestGetSeasonEpisodeCount:
"""Test get_season_episode_count method."""
def test_returns_episode_counts(self, enhanced_loader):
"""Should return dict of season -> episode count."""
base_html = (
b'<html><meta itemprop="numberOfSeasons" content="2">'
b"</html>"
)
s1_html = (
b'<html><body>'
b'<a href="/anime/stream/test/staffel-1/episode-1">E1</a>'
b'<a href="/anime/stream/test/staffel-1/episode-2">E2</a>'
b'</body></html>'
)
s2_html = (
b'<html><body>'
b'<a href="/anime/stream/test/staffel-2/episode-1">E1</a>'
b'</body></html>'
)
responses = [
MagicMock(content=base_html),
MagicMock(content=s1_html),
MagicMock(content=s2_html),
]
with patch(
"src.core.providers.enhanced_provider.recovery_strategies"
) as mock_rs:
mock_rs.handle_network_failure.side_effect = responses
result = enhanced_loader.get_season_episode_count("test")
assert result == {1: 2, 2: 1}
def test_no_seasons_meta_returns_empty(self, enhanced_loader):
"""Missing numberOfSeasons meta should return empty dict."""
base_html = b"<html><body>No seasons</body></html>"
with patch(
"src.core.providers.enhanced_provider.recovery_strategies"
) as mock_rs:
mock_rs.handle_network_failure.return_value = MagicMock(
content=base_html
)
result = enhanced_loader.get_season_episode_count("test")
assert result == {}
class TestPerformYtdlDownload:
"""Test _perform_ytdl_download method."""
def test_success(self, enhanced_loader):
"""Should return True on successful download."""
with patch(
"src.core.providers.enhanced_provider.YoutubeDL"
) as MockYDL:
mock_ydl = MagicMock()
MockYDL.return_value.__enter__ = MagicMock(return_value=mock_ydl)
MockYDL.return_value.__exit__ = MagicMock(return_value=False)
result = enhanced_loader._perform_ytdl_download(
{}, "https://example.com/video"
)
assert result is True
def test_failure_raises_download_error(self, enhanced_loader):
"""yt-dlp failure should raise DownloadError."""
with patch(
"src.core.providers.enhanced_provider.YoutubeDL"
) as MockYDL:
mock_ydl = MagicMock()
mock_ydl.download.side_effect = Exception("yt-dlp crash")
MockYDL.return_value.__enter__ = MagicMock(return_value=mock_ydl)
MockYDL.return_value.__exit__ = MagicMock(return_value=False)
with pytest.raises(DownloadError, match="Download failed"):
enhanced_loader._perform_ytdl_download(
{}, "https://example.com/video"
)
class TestDownloadFlow:
"""Test full Download method flow."""
@patch("src.core.providers.enhanced_provider.get_integrity_manager")
def test_existing_valid_file_returns_true(
self, mock_integrity, enhanced_loader, tmp_path
):
"""Should return True if file already exists and is valid."""
# Create fake existing file
folder = tmp_path / "Folder" / "Season 1"
folder.mkdir(parents=True)
video = folder / "Test - S01E001 - (German Dub).mp4"
video.write_bytes(b"valid-video")
enhanced_loader._KeyHTMLDict["key"] = MagicMock(
content=b"<html><div class='series-title'><h1><span>Test</span></h1></div></html>"
)
with patch(
"src.core.providers.enhanced_provider.file_corruption_detector"
) as mock_fcd:
mock_fcd.is_valid_video_file.return_value = True
mock_integrity.return_value.has_checksum.return_value = False
result = enhanced_loader.Download(
str(tmp_path), "Folder", 1, 1, "key"
)
assert result is True
assert enhanced_loader.download_stats["successful_downloads"] == 1
@patch("src.core.providers.enhanced_provider.get_integrity_manager")
def test_missing_key_raises_value_error(
self, mock_integrity, enhanced_loader, tmp_path
):
"""Download with empty key should raise."""
with pytest.raises((ValueError, DownloadError)):
enhanced_loader.Download(str(tmp_path), "folder", 1, 1, "")
class TestAniworldLoaderCompat:
"""Test backward compatibility wrapper."""
def test_inherits_from_enhanced(self):
"""AniworldLoader should extend EnhancedAniWorldLoader."""
from src.core.providers.enhanced_provider import AniworldLoader
assert issubclass(AniworldLoader, EnhancedAniWorldLoader)