better db model
This commit is contained in:
@@ -65,7 +65,6 @@ class TestAnimeSeriesResponse:
|
||||
title="Attack on Titan",
|
||||
folder="Attack on Titan (2013)",
|
||||
episodes=[ep],
|
||||
total_episodes=12,
|
||||
)
|
||||
|
||||
assert series.key == "attack-on-titan"
|
||||
|
||||
@@ -304,10 +304,18 @@ class TestDataMigrationServiceMigrateSingle:
|
||||
"""Test migrating series that already exists with same data."""
|
||||
service = DataMigrationService()
|
||||
|
||||
# Create mock existing series with same episode_dict
|
||||
# Create mock existing series with same episodes
|
||||
existing = MagicMock()
|
||||
existing.id = 1
|
||||
existing.episode_dict = {"1": [1, 2, 3], "2": [1, 2]}
|
||||
|
||||
# Mock episodes matching sample_serie.episodeDict = {1: [1, 2, 3], 2: [1, 2]}
|
||||
mock_episodes = []
|
||||
for season, eps in {1: [1, 2, 3], 2: [1, 2]}.items():
|
||||
for ep_num in eps:
|
||||
mock_ep = MagicMock()
|
||||
mock_ep.season = season
|
||||
mock_ep.episode_number = ep_num
|
||||
mock_episodes.append(mock_ep)
|
||||
|
||||
with patch.object(
|
||||
service,
|
||||
@@ -317,19 +325,25 @@ class TestDataMigrationServiceMigrateSingle:
|
||||
with patch(
|
||||
'src.server.services.data_migration_service.AnimeSeriesService'
|
||||
) as MockService:
|
||||
MockService.get_by_key = AsyncMock(return_value=existing)
|
||||
|
||||
result = await service.migrate_data_file(
|
||||
Path("/fake/data"),
|
||||
mock_db
|
||||
)
|
||||
|
||||
assert result is False
|
||||
MockService.create.assert_not_called()
|
||||
with patch(
|
||||
'src.server.services.data_migration_service.EpisodeService'
|
||||
) as MockEpisodeService:
|
||||
MockService.get_by_key = AsyncMock(return_value=existing)
|
||||
MockEpisodeService.get_by_series = AsyncMock(
|
||||
return_value=mock_episodes
|
||||
)
|
||||
|
||||
result = await service.migrate_data_file(
|
||||
Path("/fake/data"),
|
||||
mock_db
|
||||
)
|
||||
|
||||
assert result is False
|
||||
MockService.create.assert_not_called()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_migrate_existing_series_different_data(self, mock_db):
|
||||
"""Test migrating series that exists with different episode_dict."""
|
||||
"""Test migrating series that exists with different episodes."""
|
||||
service = DataMigrationService()
|
||||
|
||||
# Serie with new episodes
|
||||
@@ -344,7 +358,14 @@ class TestDataMigrationServiceMigrateSingle:
|
||||
# Existing series has fewer episodes
|
||||
existing = MagicMock()
|
||||
existing.id = 1
|
||||
existing.episode_dict = {"1": [1, 2, 3]}
|
||||
|
||||
# Mock episodes for existing (only 3 episodes)
|
||||
mock_episodes = []
|
||||
for ep_num in [1, 2, 3]:
|
||||
mock_ep = MagicMock()
|
||||
mock_ep.season = 1
|
||||
mock_ep.episode_number = ep_num
|
||||
mock_episodes.append(mock_ep)
|
||||
|
||||
with patch.object(
|
||||
service,
|
||||
@@ -354,16 +375,23 @@ class TestDataMigrationServiceMigrateSingle:
|
||||
with patch(
|
||||
'src.server.services.data_migration_service.AnimeSeriesService'
|
||||
) as MockService:
|
||||
MockService.get_by_key = AsyncMock(return_value=existing)
|
||||
MockService.update = AsyncMock()
|
||||
|
||||
result = await service.migrate_data_file(
|
||||
Path("/fake/data"),
|
||||
mock_db
|
||||
)
|
||||
|
||||
assert result is True
|
||||
MockService.update.assert_called_once()
|
||||
with patch(
|
||||
'src.server.services.data_migration_service.EpisodeService'
|
||||
) as MockEpisodeService:
|
||||
MockService.get_by_key = AsyncMock(return_value=existing)
|
||||
MockEpisodeService.get_by_series = AsyncMock(
|
||||
return_value=mock_episodes
|
||||
)
|
||||
MockEpisodeService.create = AsyncMock()
|
||||
|
||||
result = await service.migrate_data_file(
|
||||
Path("/fake/data"),
|
||||
mock_db
|
||||
)
|
||||
|
||||
assert result is True
|
||||
# Should create 2 new episodes (4 and 5)
|
||||
assert MockEpisodeService.create.call_count == 2
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_migrate_read_error(self, mock_db):
|
||||
@@ -493,21 +521,26 @@ class TestDataMigrationServiceMigrateAll:
|
||||
# Mock: first series doesn't exist, second already exists
|
||||
existing = MagicMock()
|
||||
existing.id = 2
|
||||
existing.episode_dict = {}
|
||||
|
||||
with patch(
|
||||
'src.server.services.data_migration_service.AnimeSeriesService'
|
||||
) as MockService:
|
||||
MockService.get_by_key = AsyncMock(
|
||||
side_effect=[None, existing]
|
||||
)
|
||||
MockService.create = AsyncMock()
|
||||
|
||||
result = await service.migrate_all(tmp_dir, mock_db)
|
||||
|
||||
assert result.total_found == 2
|
||||
assert result.migrated == 1
|
||||
assert result.skipped == 1
|
||||
with patch(
|
||||
'src.server.services.data_migration_service.EpisodeService'
|
||||
) as MockEpisodeService:
|
||||
MockService.get_by_key = AsyncMock(
|
||||
side_effect=[None, existing]
|
||||
)
|
||||
MockService.create = AsyncMock(
|
||||
return_value=MagicMock(id=1)
|
||||
)
|
||||
MockEpisodeService.get_by_series = AsyncMock(return_value=[])
|
||||
|
||||
result = await service.migrate_all(tmp_dir, mock_db)
|
||||
|
||||
assert result.total_found == 2
|
||||
assert result.migrated == 1
|
||||
assert result.skipped == 1
|
||||
|
||||
|
||||
class TestDataMigrationServiceIsMigrationNeeded:
|
||||
|
||||
@@ -14,9 +14,7 @@ from sqlalchemy.orm import Session, sessionmaker
|
||||
from src.server.database.base import Base, SoftDeleteMixin, TimestampMixin
|
||||
from src.server.database.models import (
|
||||
AnimeSeries,
|
||||
DownloadPriority,
|
||||
DownloadQueueItem,
|
||||
DownloadStatus,
|
||||
Episode,
|
||||
UserSession,
|
||||
)
|
||||
@@ -49,11 +47,6 @@ class TestAnimeSeries:
|
||||
name="Attack on Titan",
|
||||
site="https://aniworld.to",
|
||||
folder="/anime/attack-on-titan",
|
||||
description="Epic anime about titans",
|
||||
status="completed",
|
||||
total_episodes=75,
|
||||
cover_url="https://example.com/cover.jpg",
|
||||
episode_dict={1: [1, 2, 3], 2: [1, 2, 3, 4]},
|
||||
)
|
||||
|
||||
db_session.add(series)
|
||||
@@ -172,9 +165,7 @@ class TestEpisode:
|
||||
episode_number=5,
|
||||
title="The Fifth Episode",
|
||||
file_path="/anime/test/S01E05.mp4",
|
||||
file_size=524288000, # 500 MB
|
||||
is_downloaded=True,
|
||||
download_date=datetime.now(timezone.utc),
|
||||
)
|
||||
|
||||
db_session.add(episode)
|
||||
@@ -225,17 +216,17 @@ class TestDownloadQueueItem:
|
||||
db_session.add(series)
|
||||
db_session.commit()
|
||||
|
||||
item = DownloadQueueItem(
|
||||
episode = Episode(
|
||||
series_id=series.id,
|
||||
season=1,
|
||||
episode_number=3,
|
||||
status=DownloadStatus.DOWNLOADING,
|
||||
priority=DownloadPriority.HIGH,
|
||||
progress_percent=45.5,
|
||||
downloaded_bytes=250000000,
|
||||
total_bytes=550000000,
|
||||
download_speed=2500000.0,
|
||||
retry_count=0,
|
||||
)
|
||||
db_session.add(episode)
|
||||
db_session.commit()
|
||||
|
||||
item = DownloadQueueItem(
|
||||
series_id=series.id,
|
||||
episode_id=episode.id,
|
||||
download_url="https://example.com/download/ep3",
|
||||
file_destination="/anime/download/S01E03.mp4",
|
||||
)
|
||||
@@ -245,37 +236,38 @@ class TestDownloadQueueItem:
|
||||
|
||||
# Verify saved
|
||||
assert item.id is not None
|
||||
assert item.status == DownloadStatus.DOWNLOADING
|
||||
assert item.priority == DownloadPriority.HIGH
|
||||
assert item.progress_percent == 45.5
|
||||
assert item.retry_count == 0
|
||||
assert item.episode_id == episode.id
|
||||
assert item.series_id == series.id
|
||||
|
||||
def test_download_item_status_enum(self, db_session: Session):
|
||||
"""Test download status enum values."""
|
||||
def test_download_item_episode_relationship(self, db_session: Session):
|
||||
"""Test download item episode relationship."""
|
||||
series = AnimeSeries(
|
||||
key="status-test",
|
||||
name="Status Test",
|
||||
key="relationship-test",
|
||||
name="Relationship Test",
|
||||
site="https://example.com",
|
||||
folder="/anime/status",
|
||||
folder="/anime/relationship",
|
||||
)
|
||||
db_session.add(series)
|
||||
db_session.commit()
|
||||
|
||||
item = DownloadQueueItem(
|
||||
episode = Episode(
|
||||
series_id=series.id,
|
||||
season=1,
|
||||
episode_number=1,
|
||||
status=DownloadStatus.PENDING,
|
||||
)
|
||||
db_session.add(episode)
|
||||
db_session.commit()
|
||||
|
||||
item = DownloadQueueItem(
|
||||
series_id=series.id,
|
||||
episode_id=episode.id,
|
||||
)
|
||||
db_session.add(item)
|
||||
db_session.commit()
|
||||
|
||||
# Update status
|
||||
item.status = DownloadStatus.COMPLETED
|
||||
db_session.commit()
|
||||
|
||||
# Verify status change
|
||||
assert item.status == DownloadStatus.COMPLETED
|
||||
# Verify relationship
|
||||
assert item.episode.id == episode.id
|
||||
assert item.series.id == series.id
|
||||
|
||||
def test_download_item_error_handling(self, db_session: Session):
|
||||
"""Test download item with error information."""
|
||||
@@ -288,21 +280,24 @@ class TestDownloadQueueItem:
|
||||
db_session.add(series)
|
||||
db_session.commit()
|
||||
|
||||
item = DownloadQueueItem(
|
||||
episode = Episode(
|
||||
series_id=series.id,
|
||||
season=1,
|
||||
episode_number=1,
|
||||
status=DownloadStatus.FAILED,
|
||||
)
|
||||
db_session.add(episode)
|
||||
db_session.commit()
|
||||
|
||||
item = DownloadQueueItem(
|
||||
series_id=series.id,
|
||||
episode_id=episode.id,
|
||||
error_message="Network timeout after 30 seconds",
|
||||
retry_count=2,
|
||||
)
|
||||
db_session.add(item)
|
||||
db_session.commit()
|
||||
|
||||
# Verify error info
|
||||
assert item.status == DownloadStatus.FAILED
|
||||
assert item.error_message == "Network timeout after 30 seconds"
|
||||
assert item.retry_count == 2
|
||||
|
||||
|
||||
class TestUserSession:
|
||||
@@ -502,32 +497,31 @@ class TestDatabaseQueries:
|
||||
db_session.add(series)
|
||||
db_session.commit()
|
||||
|
||||
# Create items with different statuses
|
||||
for i, status in enumerate([
|
||||
DownloadStatus.PENDING,
|
||||
DownloadStatus.DOWNLOADING,
|
||||
DownloadStatus.COMPLETED,
|
||||
]):
|
||||
item = DownloadQueueItem(
|
||||
# Create episodes and items
|
||||
for i in range(3):
|
||||
episode = Episode(
|
||||
series_id=series.id,
|
||||
season=1,
|
||||
episode_number=i + 1,
|
||||
status=status,
|
||||
)
|
||||
db_session.add(episode)
|
||||
db_session.commit()
|
||||
|
||||
item = DownloadQueueItem(
|
||||
series_id=series.id,
|
||||
episode_id=episode.id,
|
||||
)
|
||||
db_session.add(item)
|
||||
db_session.commit()
|
||||
|
||||
# Query pending items
|
||||
# Query all items
|
||||
result = db_session.execute(
|
||||
select(DownloadQueueItem).where(
|
||||
DownloadQueueItem.status == DownloadStatus.PENDING
|
||||
)
|
||||
select(DownloadQueueItem)
|
||||
)
|
||||
pending = result.scalars().all()
|
||||
items = result.scalars().all()
|
||||
|
||||
# Verify query
|
||||
assert len(pending) == 1
|
||||
assert pending[0].episode_number == 1
|
||||
assert len(items) == 3
|
||||
|
||||
def test_query_active_sessions(self, db_session: Session):
|
||||
"""Test querying active user sessions."""
|
||||
|
||||
@@ -10,7 +10,6 @@ from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from src.server.database.base import Base
|
||||
from src.server.database.models import DownloadPriority, DownloadStatus
|
||||
from src.server.database.service import (
|
||||
AnimeSeriesService,
|
||||
DownloadQueueService,
|
||||
@@ -65,17 +64,11 @@ async def test_create_anime_series(db_session):
|
||||
name="Test Anime",
|
||||
site="https://example.com",
|
||||
folder="/path/to/anime",
|
||||
description="A test anime",
|
||||
status="ongoing",
|
||||
total_episodes=12,
|
||||
cover_url="https://example.com/cover.jpg",
|
||||
)
|
||||
|
||||
assert series.id is not None
|
||||
assert series.key == "test-anime-1"
|
||||
assert series.name == "Test Anime"
|
||||
assert series.description == "A test anime"
|
||||
assert series.total_episodes == 12
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -160,13 +153,11 @@ async def test_update_anime_series(db_session):
|
||||
db_session,
|
||||
series.id,
|
||||
name="Updated Name",
|
||||
total_episodes=24,
|
||||
)
|
||||
await db_session.commit()
|
||||
|
||||
assert updated is not None
|
||||
assert updated.name == "Updated Name"
|
||||
assert updated.total_episodes == 24
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -308,14 +299,12 @@ async def test_mark_episode_downloaded(db_session):
|
||||
db_session,
|
||||
episode.id,
|
||||
file_path="/path/to/file.mp4",
|
||||
file_size=1024000,
|
||||
)
|
||||
await db_session.commit()
|
||||
|
||||
assert updated is not None
|
||||
assert updated.is_downloaded is True
|
||||
assert updated.file_path == "/path/to/file.mp4"
|
||||
assert updated.download_date is not None
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -336,23 +325,30 @@ async def test_create_download_queue_item(db_session):
|
||||
)
|
||||
await db_session.commit()
|
||||
|
||||
# Add to queue
|
||||
item = await DownloadQueueService.create(
|
||||
# Create episode
|
||||
episode = await EpisodeService.create(
|
||||
db_session,
|
||||
series_id=series.id,
|
||||
season=1,
|
||||
episode_number=1,
|
||||
priority=DownloadPriority.HIGH,
|
||||
)
|
||||
await db_session.commit()
|
||||
|
||||
# Add to queue
|
||||
item = await DownloadQueueService.create(
|
||||
db_session,
|
||||
series_id=series.id,
|
||||
episode_id=episode.id,
|
||||
)
|
||||
|
||||
assert item.id is not None
|
||||
assert item.status == DownloadStatus.PENDING
|
||||
assert item.priority == DownloadPriority.HIGH
|
||||
assert item.episode_id == episode.id
|
||||
assert item.series_id == series.id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_pending_downloads(db_session):
|
||||
"""Test retrieving pending downloads."""
|
||||
async def test_get_download_queue_item_by_episode(db_session):
|
||||
"""Test retrieving download queue item by episode."""
|
||||
# Create series
|
||||
series = await AnimeSeriesService.create(
|
||||
db_session,
|
||||
@@ -362,29 +358,32 @@ async def test_get_pending_downloads(db_session):
|
||||
folder="/path/test5",
|
||||
)
|
||||
|
||||
# Add pending items
|
||||
await DownloadQueueService.create(
|
||||
# Create episode
|
||||
episode = await EpisodeService.create(
|
||||
db_session,
|
||||
series_id=series.id,
|
||||
season=1,
|
||||
episode_number=1,
|
||||
)
|
||||
await db_session.commit()
|
||||
|
||||
# Add to queue
|
||||
await DownloadQueueService.create(
|
||||
db_session,
|
||||
series_id=series.id,
|
||||
season=1,
|
||||
episode_number=2,
|
||||
episode_id=episode.id,
|
||||
)
|
||||
await db_session.commit()
|
||||
|
||||
# Retrieve pending
|
||||
pending = await DownloadQueueService.get_pending(db_session)
|
||||
assert len(pending) == 2
|
||||
# Retrieve by episode
|
||||
item = await DownloadQueueService.get_by_episode(db_session, episode.id)
|
||||
assert item is not None
|
||||
assert item.episode_id == episode.id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_download_status(db_session):
|
||||
"""Test updating download status."""
|
||||
async def test_set_download_error(db_session):
|
||||
"""Test setting error on download queue item."""
|
||||
# Create series and queue item
|
||||
series = await AnimeSeriesService.create(
|
||||
db_session,
|
||||
@@ -393,30 +392,34 @@ async def test_update_download_status(db_session):
|
||||
site="https://example.com",
|
||||
folder="/path/test6",
|
||||
)
|
||||
item = await DownloadQueueService.create(
|
||||
episode = await EpisodeService.create(
|
||||
db_session,
|
||||
series_id=series.id,
|
||||
season=1,
|
||||
episode_number=1,
|
||||
)
|
||||
item = await DownloadQueueService.create(
|
||||
db_session,
|
||||
series_id=series.id,
|
||||
episode_id=episode.id,
|
||||
)
|
||||
await db_session.commit()
|
||||
|
||||
# Update status
|
||||
updated = await DownloadQueueService.update_status(
|
||||
# Set error
|
||||
updated = await DownloadQueueService.set_error(
|
||||
db_session,
|
||||
item.id,
|
||||
DownloadStatus.DOWNLOADING,
|
||||
"Network error",
|
||||
)
|
||||
await db_session.commit()
|
||||
|
||||
assert updated is not None
|
||||
assert updated.status == DownloadStatus.DOWNLOADING
|
||||
assert updated.started_at is not None
|
||||
assert updated.error_message == "Network error"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_download_progress(db_session):
|
||||
"""Test updating download progress."""
|
||||
async def test_delete_download_queue_item_by_episode(db_session):
|
||||
"""Test deleting download queue item by episode."""
|
||||
# Create series and queue item
|
||||
series = await AnimeSeriesService.create(
|
||||
db_session,
|
||||
@@ -425,109 +428,31 @@ async def test_update_download_progress(db_session):
|
||||
site="https://example.com",
|
||||
folder="/path/test7",
|
||||
)
|
||||
item = await DownloadQueueService.create(
|
||||
episode = await EpisodeService.create(
|
||||
db_session,
|
||||
series_id=series.id,
|
||||
season=1,
|
||||
episode_number=1,
|
||||
)
|
||||
await db_session.commit()
|
||||
|
||||
# Update progress
|
||||
updated = await DownloadQueueService.update_progress(
|
||||
db_session,
|
||||
item.id,
|
||||
progress_percent=50.0,
|
||||
downloaded_bytes=500000,
|
||||
total_bytes=1000000,
|
||||
download_speed=50000.0,
|
||||
)
|
||||
await db_session.commit()
|
||||
|
||||
assert updated is not None
|
||||
assert updated.progress_percent == 50.0
|
||||
assert updated.downloaded_bytes == 500000
|
||||
assert updated.total_bytes == 1000000
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_clear_completed_downloads(db_session):
|
||||
"""Test clearing completed downloads."""
|
||||
# Create series and completed items
|
||||
series = await AnimeSeriesService.create(
|
||||
db_session,
|
||||
key="test-series-8",
|
||||
name="Test Series 8",
|
||||
site="https://example.com",
|
||||
folder="/path/test8",
|
||||
)
|
||||
item1 = await DownloadQueueService.create(
|
||||
await DownloadQueueService.create(
|
||||
db_session,
|
||||
series_id=series.id,
|
||||
season=1,
|
||||
episode_number=1,
|
||||
)
|
||||
item2 = await DownloadQueueService.create(
|
||||
db_session,
|
||||
series_id=series.id,
|
||||
season=1,
|
||||
episode_number=2,
|
||||
)
|
||||
|
||||
# Mark items as completed
|
||||
await DownloadQueueService.update_status(
|
||||
db_session,
|
||||
item1.id,
|
||||
DownloadStatus.COMPLETED,
|
||||
)
|
||||
await DownloadQueueService.update_status(
|
||||
db_session,
|
||||
item2.id,
|
||||
DownloadStatus.COMPLETED,
|
||||
episode_id=episode.id,
|
||||
)
|
||||
await db_session.commit()
|
||||
|
||||
# Clear completed
|
||||
count = await DownloadQueueService.clear_completed(db_session)
|
||||
await db_session.commit()
|
||||
|
||||
assert count == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_retry_failed_downloads(db_session):
|
||||
"""Test retrying failed downloads."""
|
||||
# Create series and failed item
|
||||
series = await AnimeSeriesService.create(
|
||||
# Delete by episode
|
||||
deleted = await DownloadQueueService.delete_by_episode(
|
||||
db_session,
|
||||
key="test-series-9",
|
||||
name="Test Series 9",
|
||||
site="https://example.com",
|
||||
folder="/path/test9",
|
||||
)
|
||||
item = await DownloadQueueService.create(
|
||||
db_session,
|
||||
series_id=series.id,
|
||||
season=1,
|
||||
episode_number=1,
|
||||
)
|
||||
|
||||
# Mark as failed
|
||||
await DownloadQueueService.update_status(
|
||||
db_session,
|
||||
item.id,
|
||||
DownloadStatus.FAILED,
|
||||
error_message="Network error",
|
||||
episode.id,
|
||||
)
|
||||
await db_session.commit()
|
||||
|
||||
# Retry
|
||||
retried = await DownloadQueueService.retry_failed(db_session)
|
||||
await db_session.commit()
|
||||
assert deleted is True
|
||||
|
||||
assert len(retried) == 1
|
||||
assert retried[0].status == DownloadStatus.PENDING
|
||||
assert retried[0].error_message is None
|
||||
# Verify deleted
|
||||
item = await DownloadQueueService.get_by_episode(db_session, episode.id)
|
||||
assert item is None
|
||||
|
||||
|
||||
# ============================================================================
|
||||
|
||||
@@ -45,7 +45,23 @@ def mock_anime_series():
|
||||
anime_series.name = "Test Series"
|
||||
anime_series.site = "https://aniworld.to/anime/stream/test-series"
|
||||
anime_series.folder = "Test Series (2020)"
|
||||
anime_series.episode_dict = {"1": [1, 2, 3], "2": [1, 2]}
|
||||
# Mock episodes relationship
|
||||
mock_ep1 = MagicMock()
|
||||
mock_ep1.season = 1
|
||||
mock_ep1.episode_number = 1
|
||||
mock_ep2 = MagicMock()
|
||||
mock_ep2.season = 1
|
||||
mock_ep2.episode_number = 2
|
||||
mock_ep3 = MagicMock()
|
||||
mock_ep3.season = 1
|
||||
mock_ep3.episode_number = 3
|
||||
mock_ep4 = MagicMock()
|
||||
mock_ep4.season = 2
|
||||
mock_ep4.episode_number = 1
|
||||
mock_ep5 = MagicMock()
|
||||
mock_ep5.season = 2
|
||||
mock_ep5.episode_number = 2
|
||||
anime_series.episodes = [mock_ep1, mock_ep2, mock_ep3, mock_ep4, mock_ep5]
|
||||
return anime_series
|
||||
|
||||
|
||||
@@ -288,37 +304,27 @@ class TestSerieListDatabaseMode:
|
||||
assert serie.name == mock_anime_series.name
|
||||
assert serie.site == mock_anime_series.site
|
||||
assert serie.folder == mock_anime_series.folder
|
||||
# Season keys should be converted from string to int
|
||||
# Season keys should be built from episodes relationship
|
||||
assert 1 in serie.episodeDict
|
||||
assert 2 in serie.episodeDict
|
||||
assert serie.episodeDict[1] == [1, 2, 3]
|
||||
assert serie.episodeDict[2] == [1, 2]
|
||||
|
||||
def test_convert_from_db_empty_episode_dict(self, mock_anime_series):
|
||||
"""Test _convert_from_db handles empty episode_dict."""
|
||||
mock_anime_series.episode_dict = None
|
||||
def test_convert_from_db_empty_episodes(self, mock_anime_series):
|
||||
"""Test _convert_from_db handles empty episodes."""
|
||||
mock_anime_series.episodes = []
|
||||
|
||||
serie = SerieList._convert_from_db(mock_anime_series)
|
||||
|
||||
assert serie.episodeDict == {}
|
||||
|
||||
def test_convert_from_db_handles_invalid_season_keys(
|
||||
self, mock_anime_series
|
||||
):
|
||||
"""Test _convert_from_db handles invalid season keys gracefully."""
|
||||
mock_anime_series.episode_dict = {
|
||||
"1": [1, 2],
|
||||
"invalid": [3, 4], # Invalid key - not an integer
|
||||
"2": [5, 6]
|
||||
}
|
||||
def test_convert_from_db_none_episodes(self, mock_anime_series):
|
||||
"""Test _convert_from_db handles None episodes."""
|
||||
mock_anime_series.episodes = None
|
||||
|
||||
serie = SerieList._convert_from_db(mock_anime_series)
|
||||
|
||||
# Valid keys should be converted
|
||||
assert 1 in serie.episodeDict
|
||||
assert 2 in serie.episodeDict
|
||||
# Invalid key should be skipped
|
||||
assert "invalid" not in serie.episodeDict
|
||||
assert serie.episodeDict == {}
|
||||
|
||||
def test_convert_to_db_dict(self, sample_serie):
|
||||
"""Test _convert_to_db_dict creates correct dictionary."""
|
||||
@@ -328,9 +334,8 @@ class TestSerieListDatabaseMode:
|
||||
assert result["name"] == sample_serie.name
|
||||
assert result["site"] == sample_serie.site
|
||||
assert result["folder"] == sample_serie.folder
|
||||
# Keys should be converted to strings for JSON
|
||||
assert "1" in result["episode_dict"]
|
||||
assert result["episode_dict"]["1"] == [1, 2, 3]
|
||||
# episode_dict should not be in result anymore
|
||||
assert "episode_dict" not in result
|
||||
|
||||
def test_convert_to_db_dict_empty_episode_dict(self):
|
||||
"""Test _convert_to_db_dict handles empty episode_dict."""
|
||||
@@ -344,7 +349,8 @@ class TestSerieListDatabaseMode:
|
||||
|
||||
result = SerieList._convert_to_db_dict(serie)
|
||||
|
||||
assert result["episode_dict"] is None
|
||||
# episode_dict should not be in result anymore
|
||||
assert "episode_dict" not in result
|
||||
|
||||
|
||||
class TestSerieListDatabaseAsync:
|
||||
|
||||
@@ -174,10 +174,16 @@ class TestSerieScannerAsyncScan:
|
||||
"""Test scan_async updates existing series in database."""
|
||||
scanner = SerieScanner(temp_directory, mock_loader)
|
||||
|
||||
# Mock existing series in database
|
||||
# Mock existing series in database with different episodes
|
||||
existing = MagicMock()
|
||||
existing.id = 1
|
||||
existing.episode_dict = {1: [5, 6]} # Different from sample_serie
|
||||
existing.folder = sample_serie.folder
|
||||
|
||||
# Mock episodes (different from sample_serie)
|
||||
mock_existing_episodes = [
|
||||
MagicMock(season=1, episode_number=5),
|
||||
MagicMock(season=1, episode_number=6),
|
||||
]
|
||||
|
||||
with patch.object(scanner, 'get_total_to_scan', return_value=1):
|
||||
with patch.object(
|
||||
@@ -200,17 +206,24 @@ class TestSerieScannerAsyncScan:
|
||||
with patch(
|
||||
'src.server.database.service.AnimeSeriesService'
|
||||
) as mock_service:
|
||||
mock_service.get_by_key = AsyncMock(
|
||||
return_value=existing
|
||||
)
|
||||
mock_service.update = AsyncMock(
|
||||
return_value=existing
|
||||
)
|
||||
|
||||
await scanner.scan_async(mock_db_session)
|
||||
|
||||
# Verify database update was called
|
||||
mock_service.update.assert_called_once()
|
||||
with patch(
|
||||
'src.server.database.service.EpisodeService'
|
||||
) as mock_ep_service:
|
||||
mock_service.get_by_key = AsyncMock(
|
||||
return_value=existing
|
||||
)
|
||||
mock_service.update = AsyncMock(
|
||||
return_value=existing
|
||||
)
|
||||
mock_ep_service.get_by_series = AsyncMock(
|
||||
return_value=mock_existing_episodes
|
||||
)
|
||||
mock_ep_service.create = AsyncMock()
|
||||
|
||||
await scanner.scan_async(mock_db_session)
|
||||
|
||||
# Verify episodes were created
|
||||
assert mock_ep_service.create.called
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_scan_async_handles_errors_gracefully(
|
||||
@@ -249,17 +262,21 @@ class TestSerieScannerDatabaseHelpers:
|
||||
with patch(
|
||||
'src.server.database.service.AnimeSeriesService'
|
||||
) as mock_service:
|
||||
mock_service.get_by_key = AsyncMock(return_value=None)
|
||||
mock_created = MagicMock()
|
||||
mock_created.id = 1
|
||||
mock_service.create = AsyncMock(return_value=mock_created)
|
||||
|
||||
result = await scanner._save_serie_to_db(
|
||||
sample_serie, mock_db_session
|
||||
)
|
||||
|
||||
assert result is mock_created
|
||||
mock_service.create.assert_called_once()
|
||||
with patch(
|
||||
'src.server.database.service.EpisodeService'
|
||||
) as mock_ep_service:
|
||||
mock_service.get_by_key = AsyncMock(return_value=None)
|
||||
mock_created = MagicMock()
|
||||
mock_created.id = 1
|
||||
mock_service.create = AsyncMock(return_value=mock_created)
|
||||
mock_ep_service.create = AsyncMock()
|
||||
|
||||
result = await scanner._save_serie_to_db(
|
||||
sample_serie, mock_db_session
|
||||
)
|
||||
|
||||
assert result is mock_created
|
||||
mock_service.create.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_serie_to_db_updates_existing(
|
||||
@@ -270,20 +287,34 @@ class TestSerieScannerDatabaseHelpers:
|
||||
|
||||
existing = MagicMock()
|
||||
existing.id = 1
|
||||
existing.episode_dict = {1: [5, 6]} # Different episodes
|
||||
existing.folder = sample_serie.folder
|
||||
|
||||
# Mock existing episodes (different from sample_serie)
|
||||
mock_existing_episodes = [
|
||||
MagicMock(season=1, episode_number=5),
|
||||
MagicMock(season=1, episode_number=6),
|
||||
]
|
||||
|
||||
with patch(
|
||||
'src.server.database.service.AnimeSeriesService'
|
||||
) as mock_service:
|
||||
mock_service.get_by_key = AsyncMock(return_value=existing)
|
||||
mock_service.update = AsyncMock(return_value=existing)
|
||||
|
||||
result = await scanner._save_serie_to_db(
|
||||
sample_serie, mock_db_session
|
||||
)
|
||||
|
||||
assert result is existing
|
||||
mock_service.update.assert_called_once()
|
||||
with patch(
|
||||
'src.server.database.service.EpisodeService'
|
||||
) as mock_ep_service:
|
||||
mock_service.get_by_key = AsyncMock(return_value=existing)
|
||||
mock_service.update = AsyncMock(return_value=existing)
|
||||
mock_ep_service.get_by_series = AsyncMock(
|
||||
return_value=mock_existing_episodes
|
||||
)
|
||||
mock_ep_service.create = AsyncMock()
|
||||
|
||||
result = await scanner._save_serie_to_db(
|
||||
sample_serie, mock_db_session
|
||||
)
|
||||
|
||||
assert result is existing
|
||||
# Should have created new episodes
|
||||
assert mock_ep_service.create.called
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_serie_to_db_skips_unchanged(
|
||||
@@ -294,19 +325,33 @@ class TestSerieScannerDatabaseHelpers:
|
||||
|
||||
existing = MagicMock()
|
||||
existing.id = 1
|
||||
existing.episode_dict = sample_serie.episodeDict # Same episodes
|
||||
existing.folder = sample_serie.folder
|
||||
|
||||
# Mock episodes matching sample_serie.episodeDict
|
||||
mock_existing_episodes = []
|
||||
for season, ep_nums in sample_serie.episodeDict.items():
|
||||
for ep_num in ep_nums:
|
||||
mock_existing_episodes.append(
|
||||
MagicMock(season=season, episode_number=ep_num)
|
||||
)
|
||||
|
||||
with patch(
|
||||
'src.server.database.service.AnimeSeriesService'
|
||||
) as mock_service:
|
||||
mock_service.get_by_key = AsyncMock(return_value=existing)
|
||||
|
||||
result = await scanner._save_serie_to_db(
|
||||
sample_serie, mock_db_session
|
||||
)
|
||||
|
||||
assert result is None
|
||||
mock_service.update.assert_not_called()
|
||||
with patch(
|
||||
'src.server.database.service.EpisodeService'
|
||||
) as mock_ep_service:
|
||||
mock_service.get_by_key = AsyncMock(return_value=existing)
|
||||
mock_ep_service.get_by_series = AsyncMock(
|
||||
return_value=mock_existing_episodes
|
||||
)
|
||||
|
||||
result = await scanner._save_serie_to_db(
|
||||
sample_serie, mock_db_session
|
||||
)
|
||||
|
||||
assert result is None
|
||||
mock_service.update.assert_not_called()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_serie_in_db_updates_existing(
|
||||
@@ -321,15 +366,20 @@ class TestSerieScannerDatabaseHelpers:
|
||||
with patch(
|
||||
'src.server.database.service.AnimeSeriesService'
|
||||
) as mock_service:
|
||||
mock_service.get_by_key = AsyncMock(return_value=existing)
|
||||
mock_service.update = AsyncMock(return_value=existing)
|
||||
|
||||
result = await scanner._update_serie_in_db(
|
||||
sample_serie, mock_db_session
|
||||
)
|
||||
|
||||
assert result is existing
|
||||
mock_service.update.assert_called_once()
|
||||
with patch(
|
||||
'src.server.database.service.EpisodeService'
|
||||
) as mock_ep_service:
|
||||
mock_service.get_by_key = AsyncMock(return_value=existing)
|
||||
mock_service.update = AsyncMock(return_value=existing)
|
||||
mock_ep_service.get_by_series = AsyncMock(return_value=[])
|
||||
mock_ep_service.create = AsyncMock()
|
||||
|
||||
result = await scanner._update_serie_in_db(
|
||||
sample_serie, mock_db_session
|
||||
)
|
||||
|
||||
assert result is existing
|
||||
mock_service.update.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_serie_in_db_returns_none_if_not_found(
|
||||
|
||||
Reference in New Issue
Block a user