Add database loading methods to SerieList

- Add load_all_from_db() for bulk loading series from DB
- Add _load_single_series_from_db() for loading single series by folder
- Add invalidate_cache() to clear in-memory cache
- Add tests for all new methods

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-05-26 18:26:25 +02:00
parent 102d83e947
commit 53d6da5dac
2 changed files with 818 additions and 392 deletions

View File

@@ -0,0 +1,291 @@
"""Tests for SerieList database loading functionality."""
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from src.core.entities.series import Serie
@pytest.fixture
def mock_session_factory():
"""Create a mock async session factory."""
mock_session = AsyncMock()
mock_session_factory = MagicMock(return_value=mock_session)
return mock_session_factory, mock_session
@pytest.fixture
def sample_anime_series():
"""Create a sample AnimeSeries DB model for testing."""
mock = MagicMock()
mock.key = "attack-on-titan"
mock.name = "Attack on Titan"
mock.site = "aniworld.to"
mock.folder = "Attack on Titan (2013)"
mock.year = 2013
mock.episodes = [
MagicMock(season=1, episode_number=1),
MagicMock(season=1, episode_number=2),
MagicMock(season=1, episode_number=3),
MagicMock(season=2, episode_number=1),
MagicMock(season=2, episode_number=2),
]
return mock
@pytest.fixture
def sample_serie():
"""Create a sample Serie for testing."""
return Serie(
key="attack-on-titan",
name="Attack on Titan",
site="aniworld.to",
folder="Attack on Titan (2013)",
episodeDict={1: [1, 2, 3], 2: [1, 2]},
year=2013
)
class TestLoadAllFromDb:
"""Test load_all_from_db method."""
@pytest.mark.asyncio
async def test_load_all_from_db(self, mock_session_factory, sample_anime_series):
"""Verify SerieList loads all series from DB."""
mock_factory, mock_session = mock_session_factory
with patch(
"src.server.database.connection.get_async_session_factory",
return_value=mock_factory
):
with patch(
"src.server.database.service.AnimeSeriesService.get_all",
return_value=[sample_anime_series]
):
from src.core.entities.SerieList import SerieList
serie_list = SerieList("/tmp", skip_load=True)
count = await serie_list.load_all_from_db()
assert count == 1
assert "attack-on-titan" in serie_list.keyDict
serie = serie_list.keyDict["attack-on-titan"]
assert serie.name == "Attack on Titan"
assert serie.key == "attack-on-titan"
assert serie.year == 2013
@pytest.mark.asyncio
async def test_load_all_from_db_multiple_series(
self, mock_session_factory, sample_anime_series
):
"""Verify SerieList loads multiple series from DB."""
mock_factory, mock_session = mock_session_factory
mock_series2 = MagicMock()
mock_series2.key = "one-piece"
mock_series2.name = "One Piece"
mock_series2.site = "aniworld.to"
mock_series2.folder = "One Piece"
mock_series2.year = 1999
mock_series2.episodes = []
with patch(
"src.server.database.connection.get_async_session_factory",
return_value=mock_factory
):
with patch(
"src.server.database.service.AnimeSeriesService.get_all",
return_value=[sample_anime_series, mock_series2]
):
from src.core.entities.SerieList import SerieList
serie_list = SerieList("/tmp", skip_load=True)
count = await serie_list.load_all_from_db()
assert count == 2
assert "attack-on-titan" in serie_list.keyDict
assert "one-piece" in serie_list.keyDict
@pytest.mark.asyncio
async def test_load_all_from_db_rebuilds_episode_dict(
self, mock_session_factory, sample_anime_series
):
"""Verify episode_dict is correctly built from Episode records."""
mock_factory, mock_session = mock_session_factory
with patch(
"src.server.database.connection.get_async_session_factory",
return_value=mock_factory
):
with patch(
"src.server.database.service.AnimeSeriesService.get_all",
return_value=[sample_anime_series]
):
from src.core.entities.SerieList import SerieList
serie_list = SerieList("/tmp", skip_load=True)
await serie_list.load_all_from_db()
serie = serie_list.keyDict["attack-on-titan"]
assert 1 in serie.episodeDict
assert 2 in serie.episodeDict
assert sorted(serie.episodeDict[1]) == [1, 2, 3]
assert sorted(serie.episodeDict[2]) == [1, 2]
@pytest.mark.asyncio
async def test_load_all_from_db_no_series(self, mock_session_factory):
"""Verify SerieList handles empty DB."""
mock_factory, mock_session = mock_session_factory
with patch(
"src.server.database.connection.get_async_session_factory",
return_value=mock_factory
):
with patch(
"src.server.database.service.AnimeSeriesService.get_all",
return_value=[]
):
from src.core.entities.SerieList import SerieList
serie_list = SerieList("/tmp", skip_load=True)
count = await serie_list.load_all_from_db()
assert count == 0
assert len(serie_list.keyDict) == 0
@pytest.mark.asyncio
async def test_load_all_from_db_db_not_initialized(self, mock_session_factory):
"""Verify SerieList handles uninitialized DB gracefully."""
mock_factory, mock_session = mock_session_factory
with patch(
"src.server.database.connection.get_async_session_factory",
return_value=mock_factory
):
with patch(
"src.server.database.service.AnimeSeriesService.get_all",
side_effect=RuntimeError("Database not initialized")
):
from src.core.entities.SerieList import SerieList
serie_list = SerieList("/tmp", skip_load=True)
count = await serie_list.load_all_from_db()
assert count == 0
assert len(serie_list.keyDict) == 0
class TestLoadSingleSeriesFromDb:
"""Test _load_single_series_from_db method."""
@pytest.mark.asyncio
async def test_load_single_series_from_db(
self, mock_session_factory, sample_anime_series
):
"""Verify SerieList loads a single series from DB by folder."""
mock_factory, mock_session = mock_session_factory
with patch(
"src.server.database.connection.get_async_session_factory",
return_value=mock_factory
):
with patch(
"src.server.database.service.AnimeSeriesService.get_by_folder",
return_value=sample_anime_series
):
from src.core.entities.SerieList import SerieList
serie_list = SerieList("/tmp", skip_load=True)
serie = await serie_list._load_single_series_from_db("Attack on Titan (2013)")
assert serie is not None
assert serie.key == "attack-on-titan"
assert "attack-on-titan" in serie_list.keyDict
@pytest.mark.asyncio
async def test_load_single_series_from_db_not_found(
self, mock_session_factory
):
"""Verify SerieList handles series not found in DB."""
mock_factory, mock_session = mock_session_factory
with patch(
"src.server.database.connection.get_async_session_factory",
return_value=mock_factory
):
with patch(
"src.server.database.service.AnimeSeriesService.get_by_folder",
return_value=None
):
from src.core.entities.SerieList import SerieList
serie_list = SerieList("/tmp", skip_load=True)
serie = await serie_list._load_single_series_from_db("Unknown Series")
assert serie is None
assert len(serie_list.keyDict) == 0
@pytest.mark.asyncio
async def test_load_single_series_from_db_db_not_initialized(
self, mock_session_factory
):
"""Verify SerieList handles uninitialized DB gracefully."""
mock_factory, mock_session = mock_session_factory
with patch(
"src.server.database.connection.get_async_session_factory",
return_value=mock_factory
):
with patch(
"src.server.database.service.AnimeSeriesService.get_by_folder",
side_effect=RuntimeError("Database not initialized")
):
from src.core.entities.SerieList import SerieList
serie_list = SerieList("/tmp", skip_load=True)
serie = await serie_list._load_single_series_from_db("Some Folder")
assert serie is None
class TestInvalidateCache:
"""Test invalidate_cache method."""
def test_invalidate_cache_clears_keydict(self, sample_serie):
"""Verify invalidate_cache clears the in-memory cache."""
from src.core.entities.SerieList import SerieList
serie_list = SerieList("/tmp", skip_load=True)
serie_list.keyDict["attack-on-titan"] = sample_serie
assert len(serie_list.keyDict) == 1
serie_list.invalidate_cache()
assert len(serie_list.keyDict) == 0
def test_invalidate_cache_allows_reload(self, mock_session_factory, sample_anime_series):
"""Verify cache can be reloaded after invalidation."""
mock_factory, mock_session = mock_session_factory
with patch(
"src.server.database.connection.get_async_session_factory",
return_value=mock_factory
):
with patch(
"src.server.database.service.AnimeSeriesService.get_all",
return_value=[sample_anime_series]
):
from src.core.entities.SerieList import SerieList
serie_list = SerieList("/tmp", skip_load=True)
serie_list.keyDict["some-key"] = MagicMock()
serie_list.invalidate_cache()
assert len(serie_list.keyDict) == 0
# Reload
import asyncio
asyncio.get_event_loop().run_until_complete(serie_list.load_all_from_db())
assert len(serie_list.keyDict) == 1