refactor: restructure core→server, split large entity files into database module

- Move src/core/ → src/server/
- Split SerieList.py (531 lines) and series.py (414 lines) into src/server/database/
- Add database/models.py for SQLAlchemy models
- Update all test imports to reflect new structure
- Remove deprecated test files (test_serie_class.py, test_serie_folder_with_year.py)
This commit is contained in:
2026-06-04 21:11:53 +02:00
parent 09d454d4c0
commit 5526ab884a
76 changed files with 1186 additions and 3574 deletions

View File

@@ -7,16 +7,16 @@ from unittest.mock import MagicMock, Mock, PropertyMock, patch
import pytest
from src.core.error_handler import (
from src.server.error_handler import (
DownloadError,
NetworkError,
NonRetryableError,
RetryableError,
)
from src.core.providers.base_provider import Loader
from src.server.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
from src.server.providers.enhanced_provider import EnhancedAniWorldLoader
class ConcreteEnhancedLoader(EnhancedAniWorldLoader):
@@ -50,9 +50,9 @@ class ConcreteEnhancedLoader(EnhancedAniWorldLoader):
def enhanced_loader():
"""Create ConcreteEnhancedLoader with mocked externals."""
with patch(
"src.core.providers.enhanced_provider.UserAgent"
"src.server.providers.enhanced_provider.UserAgent"
) as mock_ua, patch(
"src.core.providers.enhanced_provider.get_integrity_manager"
"src.server.providers.enhanced_provider.get_integrity_manager"
):
mock_ua.return_value.random = "MockAgent/1.0"
loader = ConcreteEnhancedLoader()
@@ -360,7 +360,7 @@ class TestDownloadStatistics:
class TestEnhancedDownloadValidation:
"""Test download input validation."""
@patch("src.core.providers.enhanced_provider.get_integrity_manager")
@patch("src.server.providers.enhanced_provider.get_integrity_manager")
def test_download_missing_base_directory_raises(
self, mock_integrity, enhanced_loader
):
@@ -368,7 +368,7 @@ class TestEnhancedDownloadValidation:
with pytest.raises((ValueError, DownloadError)):
enhanced_loader.Download("", "folder", 1, 1, "key")
@patch("src.core.providers.enhanced_provider.get_integrity_manager")
@patch("src.server.providers.enhanced_provider.get_integrity_manager")
def test_download_missing_serie_folder_raises(
self, mock_integrity, enhanced_loader
):
@@ -376,7 +376,7 @@ class TestEnhancedDownloadValidation:
with pytest.raises((ValueError, DownloadError)):
enhanced_loader.Download("/base", "", 1, 1, "key")
@patch("src.core.providers.enhanced_provider.get_integrity_manager")
@patch("src.server.providers.enhanced_provider.get_integrity_manager")
def test_download_negative_season_raises(
self, mock_integrity, enhanced_loader
):
@@ -384,7 +384,7 @@ class TestEnhancedDownloadValidation:
with pytest.raises((ValueError, DownloadError)):
enhanced_loader.Download("/base", "folder", -1, 1, "key")
@patch("src.core.providers.enhanced_provider.get_integrity_manager")
@patch("src.server.providers.enhanced_provider.get_integrity_manager")
def test_download_negative_episode_raises(
self, mock_integrity, enhanced_loader
):
@@ -392,7 +392,7 @@ class TestEnhancedDownloadValidation:
with pytest.raises((ValueError, DownloadError)):
enhanced_loader.Download("/base", "folder", 1, -1, "key")
@patch("src.core.providers.enhanced_provider.get_integrity_manager")
@patch("src.server.providers.enhanced_provider.get_integrity_manager")
def test_download_increments_total_count(
self, mock_integrity, enhanced_loader
):
@@ -459,7 +459,7 @@ class TestFetchAnimeListWithRecovery:
mock_response.text = json.dumps([{"title": "Naruto"}])
with patch(
"src.core.providers.enhanced_provider.recovery_strategies"
"src.server.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(
@@ -476,7 +476,7 @@ class TestFetchAnimeListWithRecovery:
mock_response.status_code = 404
with patch(
"src.core.providers.enhanced_provider.recovery_strategies"
"src.server.providers.enhanced_provider.recovery_strategies"
) as mock_rs:
mock_rs.handle_network_failure.return_value = mock_response
with pytest.raises(NonRetryableError, match="not found"):
@@ -491,7 +491,7 @@ class TestFetchAnimeListWithRecovery:
mock_response.status_code = 403
with patch(
"src.core.providers.enhanced_provider.recovery_strategies"
"src.server.providers.enhanced_provider.recovery_strategies"
) as mock_rs:
mock_rs.handle_network_failure.return_value = mock_response
with pytest.raises(NonRetryableError, match="forbidden"):
@@ -506,7 +506,7 @@ class TestFetchAnimeListWithRecovery:
mock_response.status_code = 500
with patch(
"src.core.providers.enhanced_provider.recovery_strategies"
"src.server.providers.enhanced_provider.recovery_strategies"
) as mock_rs:
mock_rs.handle_network_failure.return_value = mock_response
with pytest.raises(RetryableError, match="Server error"):
@@ -519,7 +519,7 @@ class TestFetchAnimeListWithRecovery:
import requests as req
with patch(
"src.core.providers.enhanced_provider.recovery_strategies"
"src.server.providers.enhanced_provider.recovery_strategies"
) as mock_rs:
mock_rs.handle_network_failure.side_effect = (
req.RequestException("timeout")
@@ -548,7 +548,7 @@ class TestGetKeyHTML:
mock_response.ok = True
with patch(
"src.core.providers.enhanced_provider.recovery_strategies"
"src.server.providers.enhanced_provider.recovery_strategies"
) as mock_rs:
mock_rs.handle_network_failure.return_value = mock_response
result = enhanced_loader._GetKeyHTML("new-key")
@@ -563,7 +563,7 @@ class TestGetKeyHTML:
mock_response.status_code = 404
with patch(
"src.core.providers.enhanced_provider.recovery_strategies"
"src.server.providers.enhanced_provider.recovery_strategies"
) as mock_rs:
mock_rs.handle_network_failure.return_value = mock_response
with pytest.raises(NonRetryableError, match="not found"):
@@ -628,7 +628,7 @@ class TestGetEmbeddedLink:
"_get_redirect_link",
return_value=("https://aniworld.to/redirect/100", "VOE"),
), patch(
"src.core.providers.enhanced_provider.recovery_strategies"
"src.server.providers.enhanced_provider.recovery_strategies"
) as mock_rs:
mock_rs.handle_network_failure.return_value = mock_response
result = enhanced_loader._get_embeded_link(
@@ -718,11 +718,11 @@ class TestDownloadWithRecovery:
return True
with patch(
"src.core.providers.enhanced_provider.recovery_strategies"
"src.server.providers.enhanced_provider.recovery_strategies"
) as mock_rs, patch(
"src.core.providers.enhanced_provider.file_corruption_detector"
"src.server.providers.enhanced_provider.file_corruption_detector"
) as mock_fcd, patch(
"src.core.providers.enhanced_provider.get_integrity_manager"
"src.server.providers.enhanced_provider.get_integrity_manager"
) as mock_im:
mock_rs.handle_network_failure.return_value = (
"https://direct.example.com/v.mp4",
@@ -746,7 +746,7 @@ class TestDownloadWithRecovery:
output_path = str(tmp_path / "output.mp4")
with patch(
"src.core.providers.enhanced_provider.recovery_strategies"
"src.server.providers.enhanced_provider.recovery_strategies"
) as mock_rs:
mock_rs.handle_network_failure.side_effect = Exception("fail")
@@ -769,9 +769,9 @@ class TestDownloadWithRecovery:
return True
with patch(
"src.core.providers.enhanced_provider.recovery_strategies"
"src.server.providers.enhanced_provider.recovery_strategies"
) as mock_rs, patch(
"src.core.providers.enhanced_provider.file_corruption_detector"
"src.server.providers.enhanced_provider.file_corruption_detector"
) as mock_fcd:
mock_rs.handle_network_failure.return_value = (
"https://direct.example.com/v.mp4",
@@ -816,7 +816,7 @@ class TestGetSeasonEpisodeCount:
]
with patch(
"src.core.providers.enhanced_provider.recovery_strategies"
"src.server.providers.enhanced_provider.recovery_strategies"
) as mock_rs:
mock_rs.handle_network_failure.side_effect = responses
result = enhanced_loader.get_season_episode_count("test")
@@ -828,7 +828,7 @@ class TestGetSeasonEpisodeCount:
base_html = b"<html><body>No seasons</body></html>"
with patch(
"src.core.providers.enhanced_provider.recovery_strategies"
"src.server.providers.enhanced_provider.recovery_strategies"
) as mock_rs:
mock_rs.handle_network_failure.return_value = MagicMock(
content=base_html
@@ -844,7 +844,7 @@ class TestPerformYtdlDownload:
def test_success(self, enhanced_loader):
"""Should return True on successful download."""
with patch(
"src.core.providers.enhanced_provider.YoutubeDL"
"src.server.providers.enhanced_provider.YoutubeDL"
) as MockYDL:
mock_ydl = MagicMock()
MockYDL.return_value.__enter__ = MagicMock(return_value=mock_ydl)
@@ -858,7 +858,7 @@ class TestPerformYtdlDownload:
def test_failure_raises_download_error(self, enhanced_loader):
"""yt-dlp failure should raise DownloadError."""
with patch(
"src.core.providers.enhanced_provider.YoutubeDL"
"src.server.providers.enhanced_provider.YoutubeDL"
) as MockYDL:
mock_ydl = MagicMock()
mock_ydl.download.side_effect = Exception("yt-dlp crash")
@@ -873,7 +873,7 @@ class TestPerformYtdlDownload:
class TestDownloadFlow:
"""Test full Download method flow."""
@patch("src.core.providers.enhanced_provider.get_integrity_manager")
@patch("src.server.providers.enhanced_provider.get_integrity_manager")
def test_existing_valid_file_returns_true(
self, mock_integrity, enhanced_loader, tmp_path
):
@@ -889,7 +889,7 @@ class TestDownloadFlow:
)
with patch(
"src.core.providers.enhanced_provider.file_corruption_detector"
"src.server.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
@@ -901,7 +901,7 @@ class TestDownloadFlow:
assert result is True
assert enhanced_loader.download_stats["successful_downloads"] == 1
@patch("src.core.providers.enhanced_provider.get_integrity_manager")
@patch("src.server.providers.enhanced_provider.get_integrity_manager")
def test_missing_key_raises_value_error(
self, mock_integrity, enhanced_loader, tmp_path
):
@@ -915,7 +915,7 @@ class TestAniworldLoaderCompat:
def test_inherits_from_enhanced(self):
"""AniworldLoader should extend EnhancedAniWorldLoader."""
from src.core.providers.enhanced_provider import AniworldLoader
from src.server.providers.enhanced_provider import AniworldLoader
assert issubclass(AniworldLoader, EnhancedAniWorldLoader)
@@ -936,11 +936,11 @@ class TestFfmpegHlsOptions:
return True
with patch(
"src.core.providers.enhanced_provider.recovery_strategies"
"src.server.providers.enhanced_provider.recovery_strategies"
) as mock_rs, patch(
"src.core.providers.enhanced_provider.file_corruption_detector"
"src.server.providers.enhanced_provider.file_corruption_detector"
) as mock_fcd, patch(
"src.core.providers.enhanced_provider.get_integrity_manager"
"src.server.providers.enhanced_provider.get_integrity_manager"
) as mock_im:
mock_rs.handle_network_failure.return_value = (
"https://direct.example.com/v.mp4",
@@ -969,7 +969,7 @@ class TestHlsUrlDetection:
def test_voe_hls_pattern_extracts_hls_url(self):
"""HLS_PATTERN should extract HLS URL from VOE embedded player HTML."""
import re
from src.core.providers.streaming.voe import HLS_PATTERN
from src.server.providers.streaming.voe import HLS_PATTERN
html_with_hls = """
var playerConfig = {
@@ -984,7 +984,7 @@ class TestHlsUrlDetection:
def test_voe_hls_pattern_returns_none_when_no_hls(self):
"""HLS_PATTERN should return None when no HLS URL in HTML."""
import re
from src.core.providers.streaming.voe import HLS_PATTERN
from src.server.providers.streaming.voe import HLS_PATTERN
html_no_hls = """
var playerConfig = {
@@ -997,7 +997,7 @@ class TestHlsUrlDetection:
def test_hls_url_detection_in_provider_flow(self, enhanced_loader, tmp_path):
"""Provider should detect and handle HLS URLs from VOE extractor."""
import re
from src.core.providers.streaming.voe import HLS_PATTERN
from src.server.providers.streaming.voe import HLS_PATTERN
# Simulate VOE returning an HLS URL (base64 encoded .m3u8)
encoded_hls = "aHR0cHM6Ly9leGFtcGxlLmNvbS92aWRlby5tM3U4"