"""Unit tests for enhanced_provider.py - Caching, recovery, download logic.""" import json import os from typing import Any, Dict, List from unittest.mock import MagicMock, Mock, PropertyMock, patch import pytest from src.core.error_handler import ( DownloadError, NetworkError, NonRetryableError, RetryableError, ) from src.core.providers.base_provider import Loader # Import the class but we need a concrete subclass to test it from src.core.providers.enhanced_provider import EnhancedAniWorldLoader class ConcreteEnhancedLoader(EnhancedAniWorldLoader): """Concrete subclass that bridges PascalCase methods to abstract interface.""" def subscribe_download_progress(self, handler): pass def unsubscribe_download_progress(self, handler): pass def search(self, word: str) -> List[Dict[str, Any]]: return self.Search(word) def is_language(self, season, episode, key, language="German Dub"): return self.IsLanguage(season, episode, key, language) def download(self, base_directory, serie_folder, season, episode, key, language="German Dub", **kwargs): return self.Download(base_directory, serie_folder, season, episode, key, language) def get_site_key(self) -> str: return self.GetSiteKey() def get_title(self, key: str) -> str: return self.GetTitle(key) @pytest.fixture def enhanced_loader(): """Create ConcreteEnhancedLoader with mocked externals.""" with patch( "src.core.providers.enhanced_provider.UserAgent" ) as mock_ua, patch( "src.core.providers.enhanced_provider.get_integrity_manager" ): mock_ua.return_value.random = "MockAgent/1.0" loader = ConcreteEnhancedLoader() loader.session = MagicMock() return loader class TestEnhancedLoaderInit: """Test EnhancedAniWorldLoader initialization.""" def test_initializes_with_download_stats(self, enhanced_loader): """Should initialize download statistics at zero.""" stats = enhanced_loader.download_stats assert stats["total_downloads"] == 0 assert stats["successful_downloads"] == 0 assert stats["failed_downloads"] == 0 assert stats["retried_downloads"] == 0 def test_initializes_with_caches(self, enhanced_loader): """Should initialize empty caches.""" assert enhanced_loader._KeyHTMLDict == {} assert enhanced_loader._EpisodeHTMLDict == {} def test_site_key(self, enhanced_loader): """GetSiteKey should return 'aniworld.to'.""" assert enhanced_loader.GetSiteKey() == "aniworld.to" def test_has_supported_providers(self, enhanced_loader): """Should have a list of supported providers.""" assert isinstance(enhanced_loader.SUPPORTED_PROVIDERS, list) assert len(enhanced_loader.SUPPORTED_PROVIDERS) > 0 assert "VOE" in enhanced_loader.SUPPORTED_PROVIDERS class TestEnhancedSearch: """Test enhanced search with error recovery.""" def test_search_empty_term_raises(self, enhanced_loader): """Search with empty term should raise ValueError.""" with pytest.raises(ValueError, match="empty"): enhanced_loader.Search("") def test_search_whitespace_only_raises(self, enhanced_loader): """Search with whitespace-only term should raise ValueError.""" with pytest.raises(ValueError, match="empty"): enhanced_loader.Search(" ") def test_search_successful(self, enhanced_loader): """Successful search should return parsed list.""" mock_response = MagicMock() mock_response.ok = True mock_response.text = json.dumps([ {"title": "Naruto", "link": "/anime/stream/naruto"} ]) enhanced_loader.session.get.return_value = mock_response result = enhanced_loader.Search("Naruto") assert len(result) == 1 assert result[0]["title"] == "Naruto" class TestParseAnimeResponse: """Test JSON parsing strategies.""" def test_parse_valid_json_list(self, enhanced_loader): """Should parse valid JSON list.""" text = '[{"title": "Naruto"}]' result = enhanced_loader._parse_anime_response(text) assert len(result) == 1 def test_parse_html_escaped_json(self, enhanced_loader): """Should handle HTML-escaped JSON.""" text = '[{"title": "Naruto & Boruto"}]' result = enhanced_loader._parse_anime_response(text) assert result[0]["title"] == "Naruto & Boruto" def test_parse_empty_response_raises(self, enhanced_loader): """Empty response should raise ValueError.""" with pytest.raises(ValueError, match="Empty response"): enhanced_loader._parse_anime_response("") def test_parse_whitespace_only_raises(self, enhanced_loader): """Whitespace-only response should raise ValueError.""" with pytest.raises(ValueError, match="Empty response"): enhanced_loader._parse_anime_response(" ") def test_parse_html_response_raises(self, enhanced_loader): """HTML response instead of JSON should raise ValueError.""" with pytest.raises(ValueError): enhanced_loader._parse_anime_response( "" ) def test_parse_bom_json(self, enhanced_loader): """Should handle BOM-prefixed JSON.""" text = '\ufeff[{"title": "Test"}]' result = enhanced_loader._parse_anime_response(text) assert len(result) == 1 def test_parse_control_characters(self, enhanced_loader): """Should strip control characters and parse.""" text = '[{"title": "Na\x00ruto"}]' result = enhanced_loader._parse_anime_response(text) assert len(result) == 1 def test_parse_non_list_result_raises(self, enhanced_loader): """Non-list JSON should raise ValueError.""" with pytest.raises(ValueError): enhanced_loader._parse_anime_response('{"key": "value"}') class TestLanguageKey: """Test language code mapping.""" def test_german_dub(self, enhanced_loader): """German Dub should map to 1.""" assert enhanced_loader._GetLanguageKey("German Dub") == 1 def test_english_sub(self, enhanced_loader): """English Sub should map to 2.""" assert enhanced_loader._GetLanguageKey("English Sub") == 2 def test_german_sub(self, enhanced_loader): """German Sub should map to 3.""" assert enhanced_loader._GetLanguageKey("German Sub") == 3 def test_unknown_language(self, enhanced_loader): """Unknown language should map to 0.""" assert enhanced_loader._GetLanguageKey("French Dub") == 0 class TestEnhancedIsLanguage: """Test language availability checking with recovery.""" def test_is_language_available(self, enhanced_loader): """Should return True when language is available.""" html = """
""" mock_response = MagicMock() mock_response.content = html.encode("utf-8") enhanced_loader._EpisodeHTMLDict[( "naruto", 1, 1 )] = mock_response result = enhanced_loader.IsLanguage(1, 1, "naruto", "German Dub") assert result is True def test_is_language_not_available(self, enhanced_loader): """Should return False when language is not available.""" html = """
""" mock_response = MagicMock() mock_response.content = html.encode("utf-8") enhanced_loader._EpisodeHTMLDict[( "naruto", 1, 1 )] = mock_response result = enhanced_loader.IsLanguage(1, 1, "naruto", "German Sub") assert result is False def test_is_language_no_language_box(self, enhanced_loader): """Should return False when no language box in HTML.""" html = "" mock_response = MagicMock() mock_response.content = html.encode("utf-8") enhanced_loader._EpisodeHTMLDict[( "naruto", 1, 1 )] = mock_response result = enhanced_loader.IsLanguage(1, 1, "naruto", "German Dub") assert result is False class TestEnhancedGetTitle: """Test title extraction with error recovery.""" def test_get_title_successful(self, enhanced_loader): """Should extract title from HTML.""" html = """

Attack on Titan

""" mock_response = MagicMock() mock_response.content = html.encode("utf-8") enhanced_loader._KeyHTMLDict["aot"] = mock_response result = enhanced_loader.GetTitle("aot") assert result == "Attack on Titan" def test_get_title_missing_returns_fallback(self, enhanced_loader): """Should return fallback title when not found in HTML.""" html = "" mock_response = MagicMock() mock_response.content = html.encode("utf-8") enhanced_loader._KeyHTMLDict["unknown"] = mock_response result = enhanced_loader.GetTitle("unknown") assert "Unknown_Title" in result class TestEnhancedCache: """Test cache operations.""" def test_clear_cache(self, enhanced_loader): """ClearCache should empty all caches.""" enhanced_loader._KeyHTMLDict["key"] = "data" enhanced_loader._EpisodeHTMLDict[("key", 1, 1)] = "data" enhanced_loader.ClearCache() assert len(enhanced_loader._KeyHTMLDict) == 0 assert len(enhanced_loader._EpisodeHTMLDict) == 0 def test_remove_from_cache(self, enhanced_loader): """RemoveFromCache should only clear episode cache.""" enhanced_loader._KeyHTMLDict["key"] = "data" enhanced_loader._EpisodeHTMLDict[("key", 1, 1)] = "data" enhanced_loader.RemoveFromCache() assert len(enhanced_loader._KeyHTMLDict) == 1 assert len(enhanced_loader._EpisodeHTMLDict) == 0 class TestEnhancedGetEpisodeHTML: """Test episode HTML fetching with validation.""" def test_empty_key_raises(self, enhanced_loader): """Empty key should raise ValueError.""" with pytest.raises(ValueError, match="empty"): enhanced_loader._GetEpisodeHTML(1, 1, "") def test_whitespace_key_raises(self, enhanced_loader): """Whitespace key should raise ValueError.""" with pytest.raises(ValueError, match="empty"): enhanced_loader._GetEpisodeHTML(1, 1, " ") def test_invalid_season_zero_raises(self, enhanced_loader): """Season 0 should raise ValueError.""" with pytest.raises(ValueError, match="Invalid season"): enhanced_loader._GetEpisodeHTML(0, 1, "naruto") def test_invalid_season_negative_raises(self, enhanced_loader): """Negative season should raise ValueError.""" with pytest.raises(ValueError, match="Invalid season"): enhanced_loader._GetEpisodeHTML(-1, 1, "naruto") def test_invalid_episode_zero_raises(self, enhanced_loader): """Episode 0 should raise ValueError.""" with pytest.raises(ValueError, match="Invalid episode"): enhanced_loader._GetEpisodeHTML(1, 0, "naruto") def test_cached_episode_returned(self, enhanced_loader): """Should return cached response without HTTP call.""" mock_response = MagicMock() enhanced_loader._EpisodeHTMLDict[("naruto", 1, 1)] = mock_response result = enhanced_loader._GetEpisodeHTML(1, 1, "naruto") assert result is mock_response enhanced_loader.session.get.assert_not_called() class TestDownloadStatistics: """Test download statistics tracking.""" def test_get_download_statistics(self, enhanced_loader): """Should return stats with calculated success rate.""" enhanced_loader.download_stats["total_downloads"] = 10 enhanced_loader.download_stats["successful_downloads"] = 8 enhanced_loader.download_stats["failed_downloads"] = 2 stats = enhanced_loader.get_download_statistics() assert stats["success_rate"] == 80.0 def test_statistics_zero_downloads(self, enhanced_loader): """Success rate should be 0 with no downloads.""" stats = enhanced_loader.get_download_statistics() assert stats["success_rate"] == 0 def test_reset_statistics(self, enhanced_loader): """reset_statistics should zero all counters.""" enhanced_loader.download_stats["total_downloads"] = 10 enhanced_loader.download_stats["successful_downloads"] = 8 enhanced_loader.reset_statistics() assert enhanced_loader.download_stats["total_downloads"] == 0 assert enhanced_loader.download_stats["successful_downloads"] == 0 class TestEnhancedDownloadValidation: """Test download input validation.""" @patch("src.core.providers.enhanced_provider.get_integrity_manager") def test_download_missing_base_directory_raises( self, mock_integrity, enhanced_loader ): """Download with empty base_directory should raise.""" with pytest.raises((ValueError, DownloadError)): enhanced_loader.Download("", "folder", 1, 1, "key") @patch("src.core.providers.enhanced_provider.get_integrity_manager") def test_download_missing_serie_folder_raises( self, mock_integrity, enhanced_loader ): """Download with empty serie_folder should raise.""" with pytest.raises((ValueError, DownloadError)): enhanced_loader.Download("/base", "", 1, 1, "key") @patch("src.core.providers.enhanced_provider.get_integrity_manager") def test_download_negative_season_raises( self, mock_integrity, enhanced_loader ): """Download with negative season should raise.""" with pytest.raises((ValueError, DownloadError)): enhanced_loader.Download("/base", "folder", -1, 1, "key") @patch("src.core.providers.enhanced_provider.get_integrity_manager") def test_download_negative_episode_raises( self, mock_integrity, enhanced_loader ): """Download with negative episode should raise.""" with pytest.raises((ValueError, DownloadError)): enhanced_loader.Download("/base", "folder", 1, -1, "key") @patch("src.core.providers.enhanced_provider.get_integrity_manager") def test_download_increments_total_count( self, mock_integrity, enhanced_loader ): """Download should increment total_downloads counter.""" # Make it fail fast so we don't need to mock everything enhanced_loader._KeyHTMLDict["key"] = MagicMock( content=b"

Test

" ) try: enhanced_loader.Download("/base", "folder", 1, 1, "key") except Exception: pass assert enhanced_loader.download_stats["total_downloads"] >= 1 class TestEnhancedProviderFromHTML: """Test provider extraction from episode HTML.""" def test_extract_providers(self, enhanced_loader): """Should extract providers with language keys from HTML.""" html = """
  • VOE

  • Vidmoly

  • """ mock_response = MagicMock() mock_response.content = html.encode("utf-8") enhanced_loader._EpisodeHTMLDict[("test", 1, 1)] = mock_response result = enhanced_loader._get_provider_from_html(1, 1, "test") assert "VOE" in result assert "Vidmoly" in result def test_extract_providers_empty_page(self, enhanced_loader): """Should return empty dict when no providers found.""" html = "" mock_response = MagicMock() mock_response.content = html.encode("utf-8") enhanced_loader._EpisodeHTMLDict[("test", 1, 1)] = mock_response 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'' b"" ) s1_html = ( b'' b'E1' b'E2' b'' ) s2_html = ( b'' b'E1' b'' ) 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"No seasons" 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"

    Test

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