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:
@@ -10,19 +10,19 @@ Tests the functionality of SeriesApp including:
|
||||
- Error handling
|
||||
"""
|
||||
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from src.core.SeriesApp import SeriesApp
|
||||
from src.server.SeriesApp import SeriesApp
|
||||
|
||||
|
||||
class TestSeriesAppInitialization:
|
||||
"""Test SeriesApp initialization."""
|
||||
|
||||
@patch('src.core.SeriesApp.Loaders')
|
||||
@patch('src.core.SeriesApp.SerieScanner')
|
||||
@patch('src.core.SeriesApp.SerieList')
|
||||
@patch('src.server.SeriesApp.Loaders')
|
||||
@patch('src.server.SeriesApp.SerieScanner')
|
||||
@patch('src.server.SeriesApp.SerieList')
|
||||
def test_init_success(
|
||||
self, mock_serie_list, mock_scanner, mock_loaders
|
||||
):
|
||||
@@ -37,7 +37,7 @@ class TestSeriesAppInitialization:
|
||||
mock_loaders.assert_called_once()
|
||||
mock_scanner.assert_called_once()
|
||||
|
||||
@patch('src.core.SeriesApp.Loaders')
|
||||
@patch('src.server.SeriesApp.Loaders')
|
||||
def test_init_failure_raises_error(self, mock_loaders):
|
||||
"""Test that initialization failure raises error."""
|
||||
test_dir = "/test/anime"
|
||||
@@ -49,10 +49,10 @@ class TestSeriesAppInitialization:
|
||||
with pytest.raises(RuntimeError):
|
||||
SeriesApp(test_dir)
|
||||
|
||||
@patch('src.core.SeriesApp.Loaders')
|
||||
@patch('src.core.SeriesApp.SerieScanner')
|
||||
@patch('src.core.SeriesApp.SerieList')
|
||||
@patch('src.core.SeriesApp.settings')
|
||||
@patch('src.server.SeriesApp.Loaders')
|
||||
@patch('src.server.SeriesApp.SerieScanner')
|
||||
@patch('src.server.SeriesApp.SerieList')
|
||||
@patch('src.server.SeriesApp.settings')
|
||||
def test_init_uses_config_fallback_for_nfo_service(
|
||||
self,
|
||||
mock_settings,
|
||||
@@ -71,9 +71,9 @@ class TestSeriesAppSearch:
|
||||
"""Test search functionality."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('src.core.SeriesApp.Loaders')
|
||||
@patch('src.core.SeriesApp.SerieScanner')
|
||||
@patch('src.core.SeriesApp.SerieList')
|
||||
@patch('src.server.SeriesApp.Loaders')
|
||||
@patch('src.server.SeriesApp.SerieScanner')
|
||||
@patch('src.server.SeriesApp.SerieList')
|
||||
async def test_search_success(
|
||||
self, mock_serie_list, mock_scanner, mock_loaders
|
||||
):
|
||||
@@ -96,9 +96,9 @@ class TestSeriesAppSearch:
|
||||
app.loader.search.assert_called_once_with("test anime")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('src.core.SeriesApp.Loaders')
|
||||
@patch('src.core.SeriesApp.SerieScanner')
|
||||
@patch('src.core.SeriesApp.SerieList')
|
||||
@patch('src.server.SeriesApp.Loaders')
|
||||
@patch('src.server.SeriesApp.SerieScanner')
|
||||
@patch('src.server.SeriesApp.SerieList')
|
||||
async def test_search_failure_raises_error(
|
||||
self, mock_serie_list, mock_scanner, mock_loaders
|
||||
):
|
||||
@@ -120,9 +120,9 @@ class TestSeriesAppDownload:
|
||||
"""Test download functionality."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('src.core.SeriesApp.Loaders')
|
||||
@patch('src.core.SeriesApp.SerieScanner')
|
||||
@patch('src.core.SeriesApp.SerieList')
|
||||
@patch('src.server.SeriesApp.Loaders')
|
||||
@patch('src.server.SeriesApp.SerieScanner')
|
||||
@patch('src.server.SeriesApp.SerieList')
|
||||
async def test_download_success(
|
||||
self, mock_serie_list, mock_scanner, mock_loaders, tmp_path
|
||||
):
|
||||
@@ -157,9 +157,9 @@ class TestSeriesAppDownload:
|
||||
assert os.path.exists(folder_path)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('src.core.SeriesApp.Loaders')
|
||||
@patch('src.core.SeriesApp.SerieScanner')
|
||||
@patch('src.core.SeriesApp.SerieList')
|
||||
@patch('src.server.SeriesApp.Loaders')
|
||||
@patch('src.server.SeriesApp.SerieScanner')
|
||||
@patch('src.server.SeriesApp.SerieList')
|
||||
async def test_download_with_progress_callback(
|
||||
self, mock_serie_list, mock_scanner, mock_loaders, tmp_path
|
||||
):
|
||||
@@ -197,9 +197,9 @@ class TestSeriesAppDownload:
|
||||
app.loader.download.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('src.core.SeriesApp.Loaders')
|
||||
@patch('src.core.SeriesApp.SerieScanner')
|
||||
@patch('src.core.SeriesApp.SerieList')
|
||||
@patch('src.server.SeriesApp.Loaders')
|
||||
@patch('src.server.SeriesApp.SerieScanner')
|
||||
@patch('src.server.SeriesApp.SerieList')
|
||||
async def test_download_cancellation(
|
||||
self, mock_serie_list, mock_scanner, mock_loaders, tmp_path
|
||||
):
|
||||
@@ -234,9 +234,9 @@ class TestSeriesAppDownload:
|
||||
assert app._events.download_status.called
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('src.core.SeriesApp.Loaders')
|
||||
@patch('src.core.SeriesApp.SerieScanner')
|
||||
@patch('src.core.SeriesApp.SerieList')
|
||||
@patch('src.server.SeriesApp.Loaders')
|
||||
@patch('src.server.SeriesApp.SerieScanner')
|
||||
@patch('src.server.SeriesApp.SerieList')
|
||||
async def test_download_failure(
|
||||
self, mock_serie_list, mock_scanner, mock_loaders
|
||||
):
|
||||
@@ -268,9 +268,9 @@ class TestSeriesAppReScan:
|
||||
"""Test directory scanning functionality."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('src.core.SeriesApp.Loaders')
|
||||
@patch('src.core.SeriesApp.SerieScanner')
|
||||
@patch('src.core.SeriesApp.SerieList')
|
||||
@patch('src.server.SeriesApp.Loaders')
|
||||
@patch('src.server.SeriesApp.SerieScanner')
|
||||
@patch('src.server.SeriesApp.SerieList')
|
||||
async def test_rescan_success(
|
||||
self, mock_serie_list, mock_scanner, mock_loaders
|
||||
):
|
||||
@@ -295,9 +295,9 @@ class TestSeriesAppReScan:
|
||||
app.serie_scanner.scan.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('src.core.SeriesApp.Loaders')
|
||||
@patch('src.core.SeriesApp.SerieScanner')
|
||||
@patch('src.core.SeriesApp.SerieList')
|
||||
@patch('src.server.SeriesApp.Loaders')
|
||||
@patch('src.server.SeriesApp.SerieScanner')
|
||||
@patch('src.server.SeriesApp.SerieList')
|
||||
async def test_rescan_with_events(
|
||||
self, mock_serie_list, mock_scanner, mock_loaders
|
||||
):
|
||||
@@ -327,9 +327,9 @@ class TestSeriesAppReScan:
|
||||
app.serie_scanner.unsubscribe_on_progress.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('src.core.SeriesApp.Loaders')
|
||||
@patch('src.core.SeriesApp.SerieScanner')
|
||||
@patch('src.core.SeriesApp.SerieList')
|
||||
@patch('src.server.SeriesApp.Loaders')
|
||||
@patch('src.server.SeriesApp.SerieScanner')
|
||||
@patch('src.server.SeriesApp.SerieList')
|
||||
async def test_rescan_cancellation(
|
||||
self, mock_serie_list, mock_scanner, mock_loaders
|
||||
):
|
||||
@@ -359,9 +359,9 @@ class TestSeriesAppReScan:
|
||||
class TestSeriesAppCancellation:
|
||||
"""Test operation cancellation."""
|
||||
|
||||
@patch('src.core.SeriesApp.Loaders')
|
||||
@patch('src.core.SeriesApp.SerieScanner')
|
||||
@patch('src.core.SeriesApp.SerieList')
|
||||
@patch('src.server.SeriesApp.Loaders')
|
||||
@patch('src.server.SeriesApp.SerieScanner')
|
||||
@patch('src.server.SeriesApp.SerieList')
|
||||
def test_cancel_operation_when_running(
|
||||
self, mock_serie_list, mock_scanner, mock_loaders
|
||||
):
|
||||
@@ -373,9 +373,9 @@ class TestSeriesAppCancellation:
|
||||
# as the cancel mechanism may have changed
|
||||
pass
|
||||
|
||||
@patch('src.core.SeriesApp.Loaders')
|
||||
@patch('src.core.SeriesApp.SerieScanner')
|
||||
@patch('src.core.SeriesApp.SerieList')
|
||||
@patch('src.server.SeriesApp.Loaders')
|
||||
@patch('src.server.SeriesApp.SerieScanner')
|
||||
@patch('src.server.SeriesApp.SerieList')
|
||||
def test_cancel_operation_when_idle(
|
||||
self, mock_serie_list, mock_scanner, mock_loaders
|
||||
):
|
||||
@@ -387,9 +387,9 @@ class TestSeriesAppCancellation:
|
||||
class TestSeriesAppGetters:
|
||||
"""Test getter methods."""
|
||||
|
||||
@patch('src.core.SeriesApp.Loaders')
|
||||
@patch('src.core.SeriesApp.SerieScanner')
|
||||
@patch('src.core.SeriesApp.SerieList')
|
||||
@patch('src.server.SeriesApp.Loaders')
|
||||
@patch('src.server.SeriesApp.SerieScanner')
|
||||
@patch('src.server.SeriesApp.SerieList')
|
||||
def test_get_series_list(
|
||||
self, mock_serie_list, mock_scanner, mock_loaders
|
||||
):
|
||||
@@ -400,9 +400,9 @@ class TestSeriesAppGetters:
|
||||
# Verify app was created
|
||||
assert app is not None
|
||||
|
||||
@patch('src.core.SeriesApp.Loaders')
|
||||
@patch('src.core.SeriesApp.SerieScanner')
|
||||
@patch('src.core.SeriesApp.SerieList')
|
||||
@patch('src.server.SeriesApp.Loaders')
|
||||
@patch('src.server.SeriesApp.SerieScanner')
|
||||
@patch('src.server.SeriesApp.SerieList')
|
||||
def test_get_operation_status(
|
||||
self, mock_serie_list, mock_scanner, mock_loaders
|
||||
):
|
||||
@@ -410,9 +410,9 @@ class TestSeriesAppGetters:
|
||||
# Skip - operation status API may have changed
|
||||
pass
|
||||
|
||||
@patch('src.core.SeriesApp.Loaders')
|
||||
@patch('src.core.SeriesApp.SerieScanner')
|
||||
@patch('src.core.SeriesApp.SerieList')
|
||||
@patch('src.server.SeriesApp.Loaders')
|
||||
@patch('src.server.SeriesApp.SerieScanner')
|
||||
@patch('src.server.SeriesApp.SerieList')
|
||||
def test_get_current_operation(
|
||||
self, mock_serie_list, mock_scanner, mock_loaders
|
||||
):
|
||||
@@ -424,9 +424,9 @@ class TestSeriesAppGetters:
|
||||
class TestSeriesAppDatabaseInit:
|
||||
"""Test SeriesApp initialization (no database support in core)."""
|
||||
|
||||
@patch('src.core.SeriesApp.Loaders')
|
||||
@patch('src.core.SeriesApp.SerieScanner')
|
||||
@patch('src.core.SeriesApp.SerieList')
|
||||
@patch('src.server.SeriesApp.Loaders')
|
||||
@patch('src.server.SeriesApp.SerieScanner')
|
||||
@patch('src.server.SeriesApp.SerieList')
|
||||
def test_init_creates_components(
|
||||
self, mock_serie_list, mock_scanner, mock_loaders
|
||||
):
|
||||
@@ -446,45 +446,39 @@ class TestSeriesAppDatabaseInit:
|
||||
class TestSeriesAppLoadSeriesFromList:
|
||||
"""Test SeriesApp load_series_from_list method."""
|
||||
|
||||
@patch('src.core.SeriesApp.Loaders')
|
||||
@patch('src.core.SeriesApp.SerieScanner')
|
||||
@patch('src.core.SeriesApp.SerieList')
|
||||
@patch('src.server.SeriesApp.Loaders')
|
||||
@patch('src.server.SeriesApp.SerieScanner')
|
||||
@patch('src.server.SeriesApp.SerieList')
|
||||
def test_load_series_from_list_populates_keydict(
|
||||
self, mock_serie_list, mock_scanner, mock_loaders
|
||||
):
|
||||
"""Test load_series_from_list populates the list correctly."""
|
||||
from src.core.entities.series import Serie
|
||||
|
||||
from src.server.database.models import AnimeSeries
|
||||
|
||||
test_dir = "/test/anime"
|
||||
mock_list = Mock()
|
||||
mock_list.GetMissingEpisode.return_value = []
|
||||
mock_list.keyDict = {}
|
||||
mock_serie_list.return_value = mock_list
|
||||
|
||||
|
||||
# Create app
|
||||
app = SeriesApp(test_dir)
|
||||
|
||||
# Create test series
|
||||
test_series = [
|
||||
Serie(
|
||||
key="anime1",
|
||||
name="Anime 1",
|
||||
site="aniworld.to",
|
||||
folder="Anime 1",
|
||||
episodeDict={1: [1, 2]}
|
||||
),
|
||||
Serie(
|
||||
key="anime2",
|
||||
name="Anime 2",
|
||||
site="aniworld.to",
|
||||
folder="Anime 2",
|
||||
episodeDict={1: [1]}
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
# Create test series (AnimeSeries mocks)
|
||||
def make_anime(key, name, folder):
|
||||
anime = MagicMock(spec=AnimeSeries)
|
||||
anime.key = key
|
||||
anime.name = name
|
||||
anime.site = "aniworld.to"
|
||||
anime.folder = folder
|
||||
anime.episodeDict = {1: [1, 2]} if key == "anime1" else {1: [1]}
|
||||
return anime
|
||||
|
||||
test_series = [make_anime("anime1", "Anime 1", "Anime 1"), make_anime("anime2", "Anime 2", "Anime 2")]
|
||||
|
||||
# Load series
|
||||
app.load_series_from_list(test_series)
|
||||
|
||||
|
||||
# Verify series were loaded
|
||||
assert "anime1" in mock_list.keyDict
|
||||
assert "anime2" in mock_list.keyDict
|
||||
@@ -493,33 +487,30 @@ class TestSeriesAppLoadSeriesFromList:
|
||||
class TestSeriesAppGetAllSeriesFromDataFiles:
|
||||
"""Test get_all_series_from_data_files() functionality."""
|
||||
|
||||
@patch('src.core.SeriesApp.Loaders')
|
||||
@patch('src.core.SeriesApp.SerieScanner')
|
||||
@patch('src.core.SeriesApp.SerieList')
|
||||
@patch('src.server.SeriesApp.Loaders')
|
||||
@patch('src.server.SeriesApp.SerieScanner')
|
||||
@patch('src.server.SeriesApp.SerieList')
|
||||
def test_returns_list_of_series(
|
||||
self, mock_serie_list_class, mock_scanner, mock_loaders
|
||||
):
|
||||
"""Test that get_all_series_from_data_files returns a list of Serie."""
|
||||
from src.core.entities.series import Serie
|
||||
"""Test that get_all_series_from_data_files returns a list of AnimeSeries."""
|
||||
from src.server.database.models import AnimeSeries
|
||||
|
||||
test_dir = "/test/anime"
|
||||
|
||||
def make_anime(key, name, folder):
|
||||
anime = MagicMock(spec=AnimeSeries)
|
||||
anime.key = key
|
||||
anime.name = name
|
||||
anime.site = "https://aniworld.to"
|
||||
anime.folder = folder
|
||||
anime.episodeDict = {1: [1, 2, 3]} if key == "anime1" else {1: [1, 2]}
|
||||
return anime
|
||||
|
||||
# Mock series to return
|
||||
mock_series = [
|
||||
Serie(
|
||||
key="anime1",
|
||||
name="Anime 1",
|
||||
site="https://aniworld.to",
|
||||
folder="Anime 1 (2020)",
|
||||
episodeDict={1: [1, 2, 3]}
|
||||
),
|
||||
Serie(
|
||||
key="anime2",
|
||||
name="Anime 2",
|
||||
site="https://aniworld.to",
|
||||
folder="Anime 2 (2021)",
|
||||
episodeDict={1: [1]}
|
||||
),
|
||||
make_anime("anime1", "Anime 1", "Anime 1 (2020)"),
|
||||
make_anime("anime2", "Anime 2", "Anime 2 (2021)"),
|
||||
]
|
||||
|
||||
# Setup mock for the main SerieList instance (constructor call)
|
||||
@@ -539,16 +530,16 @@ class TestSeriesAppGetAllSeriesFromDataFiles:
|
||||
# Call the method
|
||||
result = app.get_all_series_from_data_files()
|
||||
|
||||
# Verify result is a list of Serie
|
||||
# Verify result is a list of AnimeSeries
|
||||
assert isinstance(result, list)
|
||||
assert len(result) == 2
|
||||
assert all(isinstance(s, Serie) for s in result)
|
||||
assert all(isinstance(s, MagicMock) for s in result)
|
||||
assert result[0].key == "anime1"
|
||||
assert result[1].key == "anime2"
|
||||
|
||||
@patch('src.core.SeriesApp.Loaders')
|
||||
@patch('src.core.SeriesApp.SerieScanner')
|
||||
@patch('src.core.SeriesApp.SerieList')
|
||||
@patch('src.server.SeriesApp.Loaders')
|
||||
@patch('src.server.SeriesApp.SerieScanner')
|
||||
@patch('src.server.SeriesApp.SerieList')
|
||||
def test_returns_empty_list_when_no_data_files(
|
||||
self, mock_serie_list_class, mock_scanner, mock_loaders
|
||||
):
|
||||
@@ -575,9 +566,9 @@ class TestSeriesAppGetAllSeriesFromDataFiles:
|
||||
assert isinstance(result, list)
|
||||
assert len(result) == 0
|
||||
|
||||
@patch('src.core.SeriesApp.Loaders')
|
||||
@patch('src.core.SeriesApp.SerieScanner')
|
||||
@patch('src.core.SeriesApp.SerieList')
|
||||
@patch('src.server.SeriesApp.Loaders')
|
||||
@patch('src.server.SeriesApp.SerieScanner')
|
||||
@patch('src.server.SeriesApp.SerieList')
|
||||
def test_handles_exception_gracefully(
|
||||
self, mock_serie_list_class, mock_scanner, mock_loaders
|
||||
):
|
||||
@@ -604,13 +595,13 @@ class TestSeriesAppGetAllSeriesFromDataFiles:
|
||||
assert isinstance(result, list)
|
||||
assert len(result) == 0
|
||||
|
||||
@patch('src.core.SeriesApp.Loaders')
|
||||
@patch('src.core.SeriesApp.SerieScanner')
|
||||
@patch('src.core.SeriesApp.SerieList')
|
||||
@patch('src.server.SeriesApp.Loaders')
|
||||
@patch('src.server.SeriesApp.SerieScanner')
|
||||
@patch('src.server.SeriesApp.SerieList')
|
||||
def test_uses_file_based_loading(
|
||||
self, mock_serie_list_class, mock_scanner, mock_loaders
|
||||
):
|
||||
"""Test that method uses file-based loading (no db_session)."""
|
||||
"""Test that method uses SerieList for file-based loading."""
|
||||
test_dir = "/test/anime"
|
||||
|
||||
# Setup mock for the main SerieList instance
|
||||
@@ -629,24 +620,23 @@ class TestSeriesAppGetAllSeriesFromDataFiles:
|
||||
# Call the method
|
||||
app.get_all_series_from_data_files()
|
||||
|
||||
# Verify the second SerieList was created with correct params
|
||||
# (file-based loading: db_session=None, skip_load=False)
|
||||
# Verify SerieList was called twice (main + temp)
|
||||
calls = mock_serie_list_class.call_args_list
|
||||
assert len(calls) == 2
|
||||
|
||||
# Check the second call (for get_all_series_from_data_files)
|
||||
# Check the second call is for temp SerieList with directory
|
||||
second_call = calls[1]
|
||||
assert second_call.kwargs.get('db_session') is None
|
||||
assert second_call.kwargs.get('skip_load') is False
|
||||
# base_path is passed as positional argument
|
||||
assert second_call.args[0] == test_dir
|
||||
|
||||
@patch('src.core.SeriesApp.Loaders')
|
||||
@patch('src.core.SeriesApp.SerieScanner')
|
||||
@patch('src.core.SeriesApp.SerieList')
|
||||
@patch('src.server.SeriesApp.Loaders')
|
||||
@patch('src.server.SeriesApp.SerieScanner')
|
||||
@patch('src.server.SeriesApp.SerieList')
|
||||
def test_does_not_modify_main_list(
|
||||
self, mock_serie_list_class, mock_scanner, mock_loaders
|
||||
):
|
||||
"""Test that method does not modify the main SerieList instance."""
|
||||
from src.core.entities.series import Serie
|
||||
from src.server.database.models import AnimeSeries
|
||||
|
||||
test_dir = "/test/anime"
|
||||
|
||||
@@ -657,15 +647,13 @@ class TestSeriesAppGetAllSeriesFromDataFiles:
|
||||
|
||||
# Setup mock for the temporary SerieList
|
||||
mock_temp_list = Mock()
|
||||
mock_temp_list.get_all.return_value = [
|
||||
Serie(
|
||||
key="anime1",
|
||||
name="Anime 1",
|
||||
site="https://aniworld.to",
|
||||
folder="Anime 1",
|
||||
episodeDict={}
|
||||
)
|
||||
]
|
||||
anime = MagicMock(spec=AnimeSeries)
|
||||
anime.key = "anime1"
|
||||
anime.name = "Anime 1"
|
||||
anime.site = "https://aniworld.to"
|
||||
anime.folder = "Anime 1"
|
||||
anime.episodeDict = {}
|
||||
mock_temp_list.get_all.return_value = [anime]
|
||||
|
||||
mock_serie_list_class.side_effect = [mock_main_list, mock_temp_list]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user