- Created QueueRepository adapter in src/server/services/queue_repository.py - Refactored DownloadService to use repository pattern instead of JSON - Updated application startup to initialize download service from database - Updated all test fixtures to use MockQueueRepository - All 1104 tests passing
699 lines
24 KiB
Python
699 lines
24 KiB
Python
"""Unit tests for the download queue service.
|
|
|
|
Tests cover queue management, manual download control, database persistence,
|
|
and error scenarios for the simplified download service.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from datetime import datetime, timezone
|
|
from typing import Dict, List, Optional
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
|
|
import pytest
|
|
|
|
from src.server.models.download import (
|
|
DownloadItem,
|
|
DownloadPriority,
|
|
DownloadStatus,
|
|
EpisodeIdentifier,
|
|
)
|
|
from src.server.services.anime_service import AnimeService
|
|
from src.server.services.download_service import (
|
|
DownloadService,
|
|
DownloadServiceError,
|
|
)
|
|
|
|
|
|
class MockQueueRepository:
|
|
"""Mock implementation of QueueRepository for testing.
|
|
|
|
This provides an in-memory storage that mimics the database repository
|
|
behavior without requiring actual database connections.
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""Initialize mock repository with in-memory storage."""
|
|
self._items: Dict[str, DownloadItem] = {}
|
|
|
|
async def save_item(self, item: DownloadItem) -> DownloadItem:
|
|
"""Save item to in-memory storage."""
|
|
self._items[item.id] = item
|
|
return item
|
|
|
|
async def get_item(self, item_id: str) -> Optional[DownloadItem]:
|
|
"""Get item by ID from in-memory storage."""
|
|
return self._items.get(item_id)
|
|
|
|
async def get_pending_items(self) -> List[DownloadItem]:
|
|
"""Get all pending items."""
|
|
return [
|
|
item for item in self._items.values()
|
|
if item.status == DownloadStatus.PENDING
|
|
]
|
|
|
|
async def get_active_item(self) -> Optional[DownloadItem]:
|
|
"""Get the currently active item."""
|
|
for item in self._items.values():
|
|
if item.status == DownloadStatus.DOWNLOADING:
|
|
return item
|
|
return None
|
|
|
|
async def get_completed_items(
|
|
self, limit: int = 100
|
|
) -> List[DownloadItem]:
|
|
"""Get completed items."""
|
|
completed = [
|
|
item for item in self._items.values()
|
|
if item.status == DownloadStatus.COMPLETED
|
|
]
|
|
return completed[:limit]
|
|
|
|
async def get_failed_items(self, limit: int = 50) -> List[DownloadItem]:
|
|
"""Get failed items."""
|
|
failed = [
|
|
item for item in self._items.values()
|
|
if item.status == DownloadStatus.FAILED
|
|
]
|
|
return failed[:limit]
|
|
|
|
async def update_status(
|
|
self,
|
|
item_id: str,
|
|
status: DownloadStatus,
|
|
error: Optional[str] = None
|
|
) -> bool:
|
|
"""Update item status."""
|
|
if item_id not in self._items:
|
|
return False
|
|
self._items[item_id].status = status
|
|
if error:
|
|
self._items[item_id].error = error
|
|
if status == DownloadStatus.COMPLETED:
|
|
self._items[item_id].completed_at = datetime.now(timezone.utc)
|
|
elif status == DownloadStatus.DOWNLOADING:
|
|
self._items[item_id].started_at = datetime.now(timezone.utc)
|
|
return True
|
|
|
|
async def update_progress(
|
|
self,
|
|
item_id: str,
|
|
progress: float,
|
|
downloaded: int,
|
|
total: int,
|
|
speed: float
|
|
) -> bool:
|
|
"""Update download progress."""
|
|
if item_id not in self._items:
|
|
return False
|
|
item = self._items[item_id]
|
|
if item.progress is None:
|
|
from src.server.models.download import DownloadProgress
|
|
item.progress = DownloadProgress(
|
|
percent=progress,
|
|
downloaded_bytes=downloaded,
|
|
total_bytes=total,
|
|
speed_bps=speed
|
|
)
|
|
else:
|
|
item.progress.percent = progress
|
|
item.progress.downloaded_bytes = downloaded
|
|
item.progress.total_bytes = total
|
|
item.progress.speed_bps = speed
|
|
return True
|
|
|
|
async def delete_item(self, item_id: str) -> bool:
|
|
"""Delete item from storage."""
|
|
if item_id in self._items:
|
|
del self._items[item_id]
|
|
return True
|
|
return False
|
|
|
|
async def clear_completed(self) -> int:
|
|
"""Clear all completed items."""
|
|
completed_ids = [
|
|
item_id for item_id, item in self._items.items()
|
|
if item.status == DownloadStatus.COMPLETED
|
|
]
|
|
for item_id in completed_ids:
|
|
del self._items[item_id]
|
|
return len(completed_ids)
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_anime_service():
|
|
"""Create a mock AnimeService."""
|
|
service = MagicMock(spec=AnimeService)
|
|
service.download = AsyncMock(return_value=True)
|
|
return service
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_queue_repository():
|
|
"""Create a mock QueueRepository for testing."""
|
|
return MockQueueRepository()
|
|
|
|
|
|
@pytest.fixture
|
|
def download_service(mock_anime_service, mock_queue_repository):
|
|
"""Create a DownloadService instance for testing."""
|
|
return DownloadService(
|
|
anime_service=mock_anime_service,
|
|
queue_repository=mock_queue_repository,
|
|
max_retries=3,
|
|
)
|
|
|
|
|
|
class TestDownloadServiceInitialization:
|
|
"""Test download service initialization."""
|
|
|
|
def test_initialization_creates_queues(
|
|
self, mock_anime_service, mock_queue_repository
|
|
):
|
|
"""Test that initialization creates empty queues."""
|
|
service = DownloadService(
|
|
anime_service=mock_anime_service,
|
|
queue_repository=mock_queue_repository,
|
|
)
|
|
|
|
assert len(service._pending_queue) == 0
|
|
assert service._active_download is None
|
|
assert len(service._completed_items) == 0
|
|
assert len(service._failed_items) == 0
|
|
assert service._is_stopped is True
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_initialization_loads_persisted_queue(
|
|
self, mock_anime_service, mock_queue_repository
|
|
):
|
|
"""Test that initialization loads persisted queue from database."""
|
|
# Pre-populate the mock repository with a pending item
|
|
test_item = DownloadItem(
|
|
id="test-id-1",
|
|
serie_id="series-1",
|
|
serie_folder="test-series",
|
|
serie_name="Test Series",
|
|
episode=EpisodeIdentifier(season=1, episode=1),
|
|
status=DownloadStatus.PENDING,
|
|
priority=DownloadPriority.NORMAL,
|
|
added_at=datetime.now(timezone.utc),
|
|
)
|
|
await mock_queue_repository.save_item(test_item)
|
|
|
|
# Create service and initialize from database
|
|
service = DownloadService(
|
|
anime_service=mock_anime_service,
|
|
queue_repository=mock_queue_repository,
|
|
)
|
|
await service.initialize()
|
|
|
|
assert len(service._pending_queue) == 1
|
|
assert service._pending_queue[0].id == "test-id-1"
|
|
|
|
|
|
class TestQueueManagement:
|
|
"""Test queue management operations."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_add_to_queue_single_episode(self, download_service):
|
|
"""Test adding a single episode to queue."""
|
|
episodes = [EpisodeIdentifier(season=1, episode=1)]
|
|
|
|
item_ids = await download_service.add_to_queue(
|
|
serie_id="series-1",
|
|
serie_folder="series",
|
|
serie_name="Test Series",
|
|
episodes=episodes,
|
|
priority=DownloadPriority.NORMAL,
|
|
)
|
|
|
|
assert len(item_ids) == 1
|
|
assert len(download_service._pending_queue) == 1
|
|
assert download_service._pending_queue[0].serie_id == "series-1"
|
|
assert (
|
|
download_service._pending_queue[0].status
|
|
== DownloadStatus.PENDING
|
|
)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_add_to_queue_multiple_episodes(self, download_service):
|
|
"""Test adding multiple episodes to queue."""
|
|
episodes = [
|
|
EpisodeIdentifier(season=1, episode=1),
|
|
EpisodeIdentifier(season=1, episode=2),
|
|
EpisodeIdentifier(season=1, episode=3),
|
|
]
|
|
|
|
item_ids = await download_service.add_to_queue(
|
|
serie_id="series-1",
|
|
serie_folder="series",
|
|
serie_name="Test Series",
|
|
episodes=episodes,
|
|
priority=DownloadPriority.NORMAL,
|
|
)
|
|
|
|
assert len(item_ids) == 3
|
|
assert len(download_service._pending_queue) == 3
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_remove_from_pending_queue(self, download_service):
|
|
"""Test removing items from pending queue."""
|
|
item_ids = await download_service.add_to_queue(
|
|
serie_id="series-1",
|
|
serie_folder="series",
|
|
serie_name="Test Series",
|
|
episodes=[EpisodeIdentifier(season=1, episode=1)],
|
|
)
|
|
|
|
removed_ids = await download_service.remove_from_queue(item_ids)
|
|
|
|
assert len(removed_ids) == 1
|
|
assert removed_ids[0] == item_ids[0]
|
|
assert len(download_service._pending_queue) == 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_start_next_download(self, download_service):
|
|
"""Test starting the next download from queue."""
|
|
# Add items to queue
|
|
await download_service.add_to_queue(
|
|
serie_id="series-1",
|
|
serie_folder="series",
|
|
serie_name="Test Series",
|
|
episodes=[
|
|
EpisodeIdentifier(season=1, episode=1),
|
|
EpisodeIdentifier(season=1, episode=2),
|
|
],
|
|
)
|
|
|
|
# Start next download
|
|
started_id = await download_service.start_next_download()
|
|
|
|
assert started_id is not None
|
|
assert started_id == "queue_started" # Service returns this string
|
|
# Queue processing starts in background, wait a moment
|
|
await asyncio.sleep(0.2)
|
|
# First item should be processing or completed
|
|
assert len(download_service._pending_queue) <= 2
|
|
assert download_service._is_stopped is False
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_start_next_download_empty_queue(self, download_service):
|
|
"""Test starting download with empty queue returns None."""
|
|
result = await download_service.start_next_download()
|
|
assert result is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_start_next_download_already_active(
|
|
self, download_service, mock_anime_service
|
|
):
|
|
"""Test that starting download while one is active raises error."""
|
|
# Add items and start one
|
|
await download_service.add_to_queue(
|
|
serie_id="series-1",
|
|
serie_folder="series",
|
|
serie_name="Test Series",
|
|
episodes=[
|
|
EpisodeIdentifier(season=1, episode=1),
|
|
EpisodeIdentifier(season=1, episode=2),
|
|
],
|
|
)
|
|
|
|
# Make download slow so it stays active (fake - no real download)
|
|
async def fake_slow_download(**kwargs):
|
|
await asyncio.sleep(0.5) # Reduced from 10s to speed up test
|
|
return True # Fake success
|
|
|
|
mock_anime_service.download = AsyncMock(side_effect=fake_slow_download)
|
|
|
|
# Start first download (will block for 0.5s in background)
|
|
item_id = await download_service.start_next_download()
|
|
assert item_id is not None
|
|
await asyncio.sleep(0.1) # Let it start processing
|
|
|
|
# Try to start another - should fail because one is active
|
|
with pytest.raises(DownloadServiceError, match="already active"):
|
|
await download_service.start_next_download()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_stop_downloads(self, download_service):
|
|
"""Test stopping queue processing."""
|
|
await download_service.stop_downloads()
|
|
assert download_service._is_stopped is True
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_download_completion_moves_to_list(
|
|
self, download_service, mock_anime_service
|
|
):
|
|
"""Test successful download moves item to completed list."""
|
|
# Ensure mock returns success (fake download - no real download)
|
|
mock_anime_service.download = AsyncMock(return_value=True)
|
|
|
|
# Add item
|
|
await download_service.add_to_queue(
|
|
serie_id="series-1",
|
|
serie_folder="series",
|
|
serie_name="Test Series",
|
|
episodes=[EpisodeIdentifier(season=1, episode=1)],
|
|
)
|
|
|
|
# Start and wait for completion
|
|
await download_service.start_next_download()
|
|
await asyncio.sleep(0.2) # Wait for download to complete
|
|
|
|
assert len(download_service._completed_items) == 1
|
|
assert download_service._active_download is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_download_failure_moves_to_list(
|
|
self, download_service, mock_anime_service
|
|
):
|
|
"""Test failed download moves item to failed list."""
|
|
# Make download fail (fake download failure - no real download)
|
|
mock_anime_service.download = AsyncMock(return_value=False)
|
|
|
|
# Add item
|
|
await download_service.add_to_queue(
|
|
serie_id="series-1",
|
|
serie_folder="series",
|
|
serie_name="Test Series",
|
|
episodes=[EpisodeIdentifier(season=1, episode=1)],
|
|
)
|
|
|
|
# Start and wait for failure
|
|
await download_service.start_next_download()
|
|
await asyncio.sleep(0.2) # Wait for download to fail
|
|
|
|
assert len(download_service._failed_items) == 1
|
|
assert download_service._active_download is None
|
|
|
|
|
|
class TestQueueStatus:
|
|
"""Test queue status reporting."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_queue_status(self, download_service):
|
|
"""Test getting queue status."""
|
|
# Add items to queue
|
|
await download_service.add_to_queue(
|
|
serie_id="series-1",
|
|
serie_folder="series",
|
|
serie_name="Test Series",
|
|
episodes=[
|
|
EpisodeIdentifier(season=1, episode=1),
|
|
EpisodeIdentifier(season=1, episode=2),
|
|
],
|
|
)
|
|
|
|
status = await download_service.get_queue_status()
|
|
|
|
# Queue is stopped until start_next_download() is called
|
|
assert status.is_running is False
|
|
assert status.is_paused is False
|
|
assert len(status.pending_queue) == 2
|
|
assert len(status.active_downloads) == 0
|
|
assert len(status.completed_downloads) == 0
|
|
assert len(status.failed_downloads) == 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_queue_stats(self, download_service):
|
|
"""Test getting queue statistics."""
|
|
# Add items
|
|
await download_service.add_to_queue(
|
|
serie_id="series-1",
|
|
serie_folder="series",
|
|
serie_name="Test Series",
|
|
episodes=[
|
|
EpisodeIdentifier(season=1, episode=1),
|
|
EpisodeIdentifier(season=1, episode=2),
|
|
],
|
|
)
|
|
|
|
stats = await download_service.get_queue_stats()
|
|
|
|
assert stats.total_items == 2
|
|
assert stats.pending_count == 2
|
|
assert stats.active_count == 0
|
|
assert stats.completed_count == 0
|
|
assert stats.failed_count == 0
|
|
assert stats.total_downloaded_mb == 0.0
|
|
|
|
|
|
class TestQueueControl:
|
|
"""Test queue control operations."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_clear_completed(self, download_service):
|
|
"""Test clearing completed downloads."""
|
|
# Manually add completed item
|
|
completed_item = DownloadItem(
|
|
id="completed-1",
|
|
serie_id="series-1",
|
|
serie_folder="Test Series (2023)",
|
|
serie_name="Test Series",
|
|
episode=EpisodeIdentifier(season=1, episode=1),
|
|
status=DownloadStatus.COMPLETED,
|
|
)
|
|
download_service._completed_items.append(completed_item)
|
|
|
|
count = await download_service.clear_completed()
|
|
|
|
assert count == 1
|
|
assert len(download_service._completed_items) == 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_clear_pending(self, download_service):
|
|
"""Test clearing all pending downloads from the queue."""
|
|
# Add multiple items to the queue
|
|
await download_service.add_to_queue(
|
|
serie_id="series-1",
|
|
serie_folder="test-series-1",
|
|
serie_name="Test Series 1",
|
|
episodes=[EpisodeIdentifier(season=1, episode=1)],
|
|
)
|
|
await download_service.add_to_queue(
|
|
serie_id="series-2",
|
|
serie_folder="test-series-2",
|
|
serie_name="Test Series 2",
|
|
episodes=[
|
|
EpisodeIdentifier(season=1, episode=2),
|
|
EpisodeIdentifier(season=1, episode=3),
|
|
],
|
|
)
|
|
|
|
# Verify items were added
|
|
assert len(download_service._pending_queue) == 3
|
|
|
|
# Clear pending queue
|
|
count = await download_service.clear_pending()
|
|
|
|
# Verify all pending items were cleared
|
|
assert count == 3
|
|
assert len(download_service._pending_queue) == 0
|
|
assert len(download_service._pending_items_by_id) == 0
|
|
|
|
|
|
class TestPersistence:
|
|
"""Test queue persistence functionality with database backend."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_queue_persistence(
|
|
self, download_service, mock_queue_repository
|
|
):
|
|
"""Test that queue state is persisted to database."""
|
|
await download_service.add_to_queue(
|
|
serie_id="series-1",
|
|
serie_folder="series",
|
|
serie_name="Test Series",
|
|
episodes=[EpisodeIdentifier(season=1, episode=1)],
|
|
)
|
|
|
|
# Item should be saved in mock repository
|
|
pending_items = await mock_queue_repository.get_pending_items()
|
|
assert len(pending_items) == 1
|
|
assert pending_items[0].serie_id == "series-1"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_queue_recovery_after_restart(
|
|
self, mock_anime_service, mock_queue_repository
|
|
):
|
|
"""Test that queue is recovered after service restart."""
|
|
# Create and populate first service
|
|
service1 = DownloadService(
|
|
anime_service=mock_anime_service,
|
|
queue_repository=mock_queue_repository,
|
|
)
|
|
|
|
await service1.add_to_queue(
|
|
serie_id="series-1",
|
|
serie_folder="series",
|
|
serie_name="Test Series",
|
|
episodes=[
|
|
EpisodeIdentifier(season=1, episode=1),
|
|
EpisodeIdentifier(season=1, episode=2),
|
|
],
|
|
)
|
|
|
|
# Create new service with same repository (simulating restart)
|
|
service2 = DownloadService(
|
|
anime_service=mock_anime_service,
|
|
queue_repository=mock_queue_repository,
|
|
)
|
|
# Initialize to load from database to recover state
|
|
await service2.initialize()
|
|
|
|
# Should recover pending items
|
|
assert len(service2._pending_queue) == 2
|
|
|
|
|
|
class TestRetryLogic:
|
|
"""Test retry logic for failed downloads."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_retry_failed_items(self, download_service):
|
|
"""Test retrying failed downloads."""
|
|
# Manually add failed item
|
|
failed_item = DownloadItem(
|
|
id="failed-1",
|
|
serie_id="series-1",
|
|
serie_folder="Test Series (2023)",
|
|
serie_name="Test Series",
|
|
episode=EpisodeIdentifier(season=1, episode=1),
|
|
status=DownloadStatus.FAILED,
|
|
retry_count=0,
|
|
error="Test error",
|
|
)
|
|
download_service._failed_items.append(failed_item)
|
|
|
|
retried_ids = await download_service.retry_failed()
|
|
|
|
assert len(retried_ids) == 1
|
|
assert len(download_service._failed_items) == 0
|
|
assert len(download_service._pending_queue) == 1
|
|
assert download_service._pending_queue[0].retry_count == 1
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_max_retries_not_exceeded(self, download_service):
|
|
"""Test that items with max retries are not retried."""
|
|
# Create item with max retries
|
|
failed_item = DownloadItem(
|
|
id="failed-1",
|
|
serie_id="series-1",
|
|
serie_folder="Test Series (2023)",
|
|
serie_name="Test Series",
|
|
episode=EpisodeIdentifier(season=1, episode=1),
|
|
status=DownloadStatus.FAILED,
|
|
retry_count=3, # Max retries
|
|
error="Test error",
|
|
)
|
|
download_service._failed_items.append(failed_item)
|
|
|
|
retried_ids = await download_service.retry_failed()
|
|
|
|
assert len(retried_ids) == 0
|
|
assert len(download_service._failed_items) == 1
|
|
assert len(download_service._pending_queue) == 0
|
|
|
|
|
|
class TestBroadcastCallbacks:
|
|
"""Test WebSocket broadcast functionality."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_broadcast_on_queue_update(self, download_service):
|
|
"""Test that queue updates work correctly (no broadcast callbacks)."""
|
|
# Note: The service no longer has set_broadcast_callback method
|
|
# It uses the progress service internally for websocket updates
|
|
|
|
await download_service.add_to_queue(
|
|
serie_id="series-1",
|
|
serie_folder="series",
|
|
serie_name="Test Series",
|
|
episodes=[EpisodeIdentifier(season=1, episode=1)],
|
|
)
|
|
|
|
# Verify item was added successfully
|
|
assert len(download_service._pending_queue) == 1
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_progress_callback_format(self, download_service):
|
|
"""Test that download completes successfully with mocked service."""
|
|
# Note: Progress updates are handled by SeriesApp events and
|
|
# ProgressService, not via direct callbacks to the download service.
|
|
# This test verifies that downloads complete without errors.
|
|
|
|
# Mock successful download (fake download - no real download)
|
|
download_service._anime_service.download = AsyncMock(return_value=True)
|
|
|
|
# Add and process a download
|
|
await download_service.add_to_queue(
|
|
serie_id="series-1",
|
|
serie_folder="series",
|
|
serie_name="Test Series",
|
|
episodes=[EpisodeIdentifier(season=1, episode=1)],
|
|
)
|
|
|
|
# Start download and wait for completion
|
|
await download_service.start_next_download()
|
|
await asyncio.sleep(0.5) # Wait for processing
|
|
|
|
# Verify download completed successfully
|
|
assert len(download_service._completed_items) == 1
|
|
assert download_service._completed_items[0].status == (
|
|
DownloadStatus.COMPLETED
|
|
)
|
|
|
|
|
|
class TestServiceLifecycle:
|
|
"""Test service start and stop operations."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_start_service(self, download_service):
|
|
"""Test starting the service."""
|
|
# start() is now just for initialization/compatibility
|
|
await download_service.start()
|
|
# No _is_running attribute - simplified service doesn't track this
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_stop_service(self, download_service):
|
|
"""Test stopping the service."""
|
|
await download_service.start()
|
|
await download_service.stop()
|
|
# Verifies service can be stopped without errors
|
|
# No _is_running attribute in simplified service
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_start_already_running(self, download_service):
|
|
"""Test starting service when already running."""
|
|
await download_service.start()
|
|
await download_service.start() # Should not raise error
|
|
# No _is_running attribute in simplified service
|
|
|
|
|
|
class TestErrorHandling:
|
|
"""Test error handling in download service."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_download_failure_moves_to_failed(self, download_service):
|
|
"""Test that download failures are handled correctly."""
|
|
# Mock download to fail with exception (fake - no real download)
|
|
download_service._anime_service.download = AsyncMock(
|
|
side_effect=Exception("Fake download failed")
|
|
)
|
|
|
|
await download_service.add_to_queue(
|
|
serie_id="series-1",
|
|
serie_folder="series",
|
|
serie_name="Test Series",
|
|
episodes=[EpisodeIdentifier(season=1, episode=1)],
|
|
)
|
|
|
|
# Process the download
|
|
item = download_service._pending_queue.popleft()
|
|
await download_service._process_download(item)
|
|
|
|
# Item should be in failed queue
|
|
assert len(download_service._failed_items) == 1
|
|
assert (
|
|
download_service._failed_items[0].status == DownloadStatus.FAILED
|
|
)
|
|
assert download_service._failed_items[0].error is not None
|