Files
Aniworld/tests/unit/test_background_loader_service.py

974 lines
35 KiB
Python

"""Unit tests for BackgroundLoaderService.
Tests cover:
- Task queuing and worker orchestration
- Loading status tracking and progress reporting
- Concurrent task processing
- WebSocket broadcasting
- Error handling and recovery
- Resource cleanup
- Missing data detection
"""
import asyncio
from datetime import datetime, timezone
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from src.server.services.background_loader_service import (
BackgroundLoaderService,
LoadingStatus,
SeriesLoadingTask,
)
@pytest.fixture
def mock_websocket_service():
"""Mock WebSocket service."""
service = AsyncMock()
service.broadcast = AsyncMock()
return service
@pytest.fixture
def mock_anime_service():
"""Mock AnimeService."""
service = AsyncMock()
service.sync_episodes_to_db = AsyncMock()
service.sync_single_series_after_scan = AsyncMock()
return service
@pytest.fixture
def mock_series_app():
"""Mock SeriesApp."""
app = MagicMock()
app.directory_to_search = "/anime"
app.nfo_service = AsyncMock()
app.nfo_service.has_nfo = MagicMock(return_value=False)
app.nfo_service.create_tvshow_nfo = AsyncMock(return_value="/anime/TestSeries/tvshow.nfo")
app.serie_scanner = MagicMock()
app.serie_scanner.scan_single_series = MagicMock(return_value={
"Season 1": ["episode1.mp4", "episode2.mp4"]
})
return app
@pytest.fixture
def background_loader_service(mock_websocket_service, mock_anime_service, mock_series_app):
"""Create BackgroundLoaderService instance."""
return BackgroundLoaderService(
websocket_service=mock_websocket_service,
anime_service=mock_anime_service,
series_app=mock_series_app,
max_concurrent_loads=3
)
class TestSeriesLoadingTask:
"""Tests for SeriesLoadingTask data class."""
def test_task_initialization(self):
"""Test task is initialized with correct default values."""
task = SeriesLoadingTask(
key="test_series",
folder="test_folder",
name="Test Series",
year=2020
)
assert task.key == "test_series"
assert task.folder == "test_folder"
assert task.name == "Test Series"
assert task.year == 2020
assert task.status == LoadingStatus.PENDING
assert task.progress == {
"episodes": False,
"nfo": False,
"logo": False,
"images": False
}
assert task.started_at is None
assert task.completed_at is None
assert task.error is None
def test_task_with_minimal_fields(self):
"""Test task creation with minimal required fields."""
task = SeriesLoadingTask(
key="minimal",
folder="folder",
name="Name"
)
assert task.key == "minimal"
assert task.year is None
assert task.status == LoadingStatus.PENDING
def test_task_progress_tracking(self):
"""Test progress tracking updates correctly."""
task = SeriesLoadingTask(
key="test",
folder="Test",
name="Test"
)
task.progress["episodes"] = True
assert task.progress["episodes"] is True
assert not task.progress["nfo"]
class TestLoadingStatus:
"""Tests for LoadingStatus enumeration."""
def test_all_status_values(self):
"""Test all loading status values."""
assert LoadingStatus.PENDING.value == "pending"
assert LoadingStatus.LOADING_EPISODES.value == "loading_episodes"
assert LoadingStatus.LOADING_NFO.value == "loading_nfo"
assert LoadingStatus.COMPLETED.value == "completed"
assert LoadingStatus.FAILED.value == "failed"
class TestBackgroundLoaderServiceInitialization:
"""Tests for service initialization."""
def test_service_initialization(self, mock_websocket_service, mock_anime_service, mock_series_app):
"""Test service initializes with correct configuration."""
service = BackgroundLoaderService(
websocket_service=mock_websocket_service,
anime_service=mock_anime_service,
series_app=mock_series_app,
max_concurrent_loads=5
)
assert service.websocket_service is mock_websocket_service
assert service.anime_service is mock_anime_service
assert service.series_app is mock_series_app
assert service.max_concurrent_loads == 5
assert isinstance(service.task_queue, asyncio.Queue)
assert service.active_tasks == {}
assert service.worker_tasks == []
assert service._shutdown is False
def test_default_max_concurrent_loads(self, mock_websocket_service, mock_anime_service, mock_series_app):
"""Test default max_concurrent_loads is 5."""
service = BackgroundLoaderService(
websocket_service=mock_websocket_service,
anime_service=mock_anime_service,
series_app=mock_series_app
)
assert service.max_concurrent_loads == 5
class TestStartStopService:
"""Tests for service startup and shutdown."""
@pytest.mark.asyncio
async def test_service_start(self, background_loader_service):
"""Test service starts worker tasks."""
await background_loader_service.start()
assert len(background_loader_service.worker_tasks) == 3
assert all(not task.done() for task in background_loader_service.worker_tasks)
await background_loader_service.stop()
@pytest.mark.asyncio
async def test_service_stop(self, background_loader_service):
"""Test service stops all worker tasks gracefully."""
await background_loader_service.start()
assert len(background_loader_service.worker_tasks) == 3
await background_loader_service.stop()
assert background_loader_service.worker_tasks == []
assert background_loader_service._shutdown is True
@pytest.mark.asyncio
async def test_service_stop_when_not_started(self, background_loader_service):
"""Test stopping a service that was never started."""
await background_loader_service.stop()
assert background_loader_service.worker_tasks == []
class TestAddSeriesLoadingTask:
"""Tests for adding tasks to the queue."""
@pytest.mark.asyncio
async def test_add_series_loading_task(self, background_loader_service):
"""Test adding a series loading task to the queue."""
await background_loader_service.add_series_loading_task(
key="test_series",
folder="test_folder",
name="Test Series",
year=2020
)
assert "test_series" in background_loader_service.active_tasks
task = background_loader_service.active_tasks["test_series"]
assert task.key == "test_series"
assert task.folder == "test_folder"
assert task.name == "Test Series"
assert task.year == 2020
assert task.status == LoadingStatus.PENDING
assert task.started_at is not None
@pytest.mark.asyncio
async def test_add_duplicate_task_skips_addition(self, background_loader_service):
"""Test adding duplicate task skips the new one."""
await background_loader_service.add_series_loading_task(
key="test_series",
folder="test_folder1",
name="Test Series 1"
)
initial_task = background_loader_service.active_tasks["test_series"]
await background_loader_service.add_series_loading_task(
key="test_series",
folder="test_folder2",
name="Test Series 2"
)
assert background_loader_service.active_tasks["test_series"] is initial_task
@pytest.mark.asyncio
async def test_add_task_broadcasts_status(self, background_loader_service, mock_websocket_service):
"""Test adding task broadcasts initial status."""
await background_loader_service.add_series_loading_task(
key="test_series",
folder="test_folder",
name="Test Series"
)
mock_websocket_service.broadcast.assert_called_once()
call_args = mock_websocket_service.broadcast.call_args[0][0]
assert call_args["type"] == "series_loading_update"
assert call_args["key"] == "test_series"
assert call_args["status"] == LoadingStatus.PENDING.value
@pytest.mark.asyncio
async def test_add_multiple_tasks(self, background_loader_service):
"""Test adding multiple different tasks."""
await background_loader_service.add_series_loading_task(
key="series1",
folder="folder1",
name="Series 1"
)
await background_loader_service.add_series_loading_task(
key="series2",
folder="folder2",
name="Series 2"
)
assert len(background_loader_service.active_tasks) == 2
assert "series1" in background_loader_service.active_tasks
assert "series2" in background_loader_service.active_tasks
class TestCheckMissingData:
"""Tests for checking missing data for a series."""
@pytest.mark.asyncio
async def test_check_missing_data_no_series_in_db(self, background_loader_service):
"""Test checking missing data when series doesn't exist in DB."""
mock_db = AsyncMock()
with patch("src.server.database.service.AnimeSeriesService") as mock_service_class:
mock_service_class.get_by_key = AsyncMock(return_value=None)
missing = await background_loader_service.check_missing_data(
key="new_series",
folder="new_folder",
anime_directory="/anime",
db=mock_db
)
assert all(missing.values())
@pytest.mark.asyncio
async def test_check_missing_data_partial_loaded(self, background_loader_service):
"""Test checking missing data for partially loaded series."""
mock_db = AsyncMock()
mock_series = MagicMock()
mock_series.episodes_loaded = True
mock_series.has_nfo = True
mock_series.logo_loaded = False
mock_series.images_loaded = False
with patch("src.server.database.service.AnimeSeriesService") as mock_service_class:
with patch("src.server.utils.media.check_media_files") as mock_check:
mock_service_class.get_by_key = AsyncMock(return_value=mock_series)
mock_check.return_value = {
"poster": False,
"logo": False,
"fanart": False,
"nfo": True
}
missing = await background_loader_service.check_missing_data(
key="partial_series",
folder="partial_folder",
anime_directory="/anime",
db=mock_db
)
assert missing["episodes"] is False
assert missing["nfo"] is False
assert missing["logo"] is True
assert missing["images"] is True
class TestBroadcastStatus:
"""Tests for WebSocket status broadcasting."""
@pytest.mark.asyncio
async def test_broadcast_status_pending(self, background_loader_service, mock_websocket_service):
"""Test broadcasting pending status."""
task = SeriesLoadingTask(
key="test",
folder="folder",
name="Test",
status=LoadingStatus.PENDING
)
await background_loader_service._broadcast_status(task)
call_args = mock_websocket_service.broadcast.call_args[0][0]
assert call_args["status"] == LoadingStatus.PENDING.value
assert "Queued" in call_args["message"]
@pytest.mark.asyncio
async def test_broadcast_status_completed(self, background_loader_service, mock_websocket_service):
"""Test broadcasting completed status."""
task = SeriesLoadingTask(
key="test",
folder="folder",
name="Test",
status=LoadingStatus.COMPLETED
)
await background_loader_service._broadcast_status(task)
call_args = mock_websocket_service.broadcast.call_args[0][0]
assert call_args["status"] == LoadingStatus.COMPLETED.value
assert "successfully" in call_args["message"]
@pytest.mark.asyncio
async def test_broadcast_status_failed(self, background_loader_service, mock_websocket_service):
"""Test broadcasting failed status with error message."""
task = SeriesLoadingTask(
key="test",
folder="folder",
name="Test",
status=LoadingStatus.FAILED,
error="Test error"
)
await background_loader_service._broadcast_status(task)
call_args = mock_websocket_service.broadcast.call_args[0][0]
assert call_args["status"] == LoadingStatus.FAILED.value
assert call_args["error"] == "Test error"
assert "failed" in call_args["message"]
@pytest.mark.asyncio
async def test_broadcast_status_custom_message(self, background_loader_service, mock_websocket_service):
"""Test broadcasting with custom message."""
task = SeriesLoadingTask(
key="test",
folder="folder",
name="Test"
)
await background_loader_service._broadcast_status(task, "Custom message")
call_args = mock_websocket_service.broadcast.call_args[0][0]
assert call_args["message"] == "Custom message"
@pytest.mark.asyncio
async def test_broadcast_status_includes_metadata(self, background_loader_service, mock_websocket_service):
"""Test broadcast includes all required metadata."""
task = SeriesLoadingTask(
key="test_key",
folder="test_folder",
name="Test Name"
)
task.progress = {"episodes": True, "nfo": False, "logo": False, "images": False}
await background_loader_service._broadcast_status(task)
call_args = mock_websocket_service.broadcast.call_args[0][0]
assert call_args["type"] == "series_loading_update"
assert call_args["key"] == "test_key"
assert call_args["series_key"] == "test_key"
assert call_args["folder"] == "test_folder"
assert call_args["progress"] == {"episodes": True, "nfo": False, "logo": False, "images": False}
assert "timestamp" in call_args
class TestFindSeriesDirectory:
"""Tests for finding series directory."""
@pytest.mark.asyncio
async def test_find_series_directory_exists(self, background_loader_service, tmp_path):
"""Test finding series directory when it exists."""
series_dir = tmp_path / "TestSeries"
series_dir.mkdir()
background_loader_service.series_app.directory_to_search = str(tmp_path)
task = SeriesLoadingTask(
key="test",
folder="TestSeries",
name="Test"
)
result = await background_loader_service._find_series_directory(task)
assert result is not None
assert result.name == "TestSeries"
@pytest.mark.asyncio
async def test_find_series_directory_not_found(self, background_loader_service, tmp_path):
"""Test finding series directory when it doesn't exist."""
background_loader_service.series_app.directory_to_search = str(tmp_path)
task = SeriesLoadingTask(
key="test",
folder="NonExistentSeries",
name="Test"
)
result = await background_loader_service._find_series_directory(task)
assert result is None
class TestScanSeriesEpisodes:
"""Tests for scanning episodes in a series directory."""
@pytest.mark.asyncio
async def test_scan_series_episodes(self, background_loader_service, tmp_path):
"""Test scanning episodes from series directory."""
season1 = tmp_path / "Season 1"
season1.mkdir()
(season1 / "episode1.mp4").touch()
(season1 / "episode2.mp4").touch()
season2 = tmp_path / "Season 2"
season2.mkdir()
(season2 / "episode1.mp4").touch()
task = SeriesLoadingTask(
key="test",
folder="TestSeries",
name="Test"
)
result = await background_loader_service._scan_series_episodes(tmp_path, task)
assert "Season 1" in result
assert "Season 2" in result
assert len(result["Season 1"]) == 2
assert len(result["Season 2"]) == 1
@pytest.mark.asyncio
async def test_scan_series_episodes_ignores_non_mp4(self, background_loader_service, tmp_path):
"""Test that only .mp4 files are scanned."""
season = tmp_path / "Season 1"
season.mkdir()
(season / "episode1.mp4").touch()
(season / "episode2.txt").touch()
(season / "episode3.avi").touch()
task = SeriesLoadingTask(
key="test",
folder="TestSeries",
name="Test"
)
result = await background_loader_service._scan_series_episodes(tmp_path, task)
assert len(result["Season 1"]) == 1
assert result["Season 1"][0] == "episode1.mp4"
@pytest.mark.asyncio
async def test_scan_series_episodes_empty_directory(self, background_loader_service, tmp_path):
"""Test scanning empty directory."""
task = SeriesLoadingTask(
key="test",
folder="TestSeries",
name="Test"
)
result = await background_loader_service._scan_series_episodes(tmp_path, task)
assert result == {}
class TestLoadNfoAndImages:
"""Tests for loading NFO files and images."""
@pytest.mark.asyncio
async def test_load_nfo_creates_new_nfo(self, background_loader_service, mock_websocket_service):
"""Test creating new NFO file when it doesn't exist."""
mock_db = AsyncMock()
mock_series = MagicMock()
mock_series.has_nfo = False
task = SeriesLoadingTask(
key="test",
folder="test_folder",
name="Test Series",
year=2020
)
with patch("src.server.database.service.AnimeSeriesService") as mock_service_class:
mock_service_class.get_by_key = AsyncMock(return_value=mock_series)
result = await background_loader_service._load_nfo_and_images(task, mock_db)
assert result is True
assert task.progress["nfo"] is True
assert task.progress["logo"] is True
assert task.progress["images"] is True
@pytest.mark.asyncio
async def test_load_nfo_uses_existing(self, background_loader_service):
"""Test using existing NFO file when it already exists."""
background_loader_service.series_app.nfo_service.has_nfo = MagicMock(return_value=True)
mock_db = AsyncMock()
mock_series = MagicMock()
mock_series.has_nfo = True
task = SeriesLoadingTask(
key="test",
folder="test_folder",
name="Test Series"
)
with patch("src.server.database.service.AnimeSeriesService") as mock_service_class:
mock_service_class.get_by_key = AsyncMock(return_value=mock_series)
result = await background_loader_service._load_nfo_and_images(task, mock_db)
assert result is False
assert task.progress["nfo"] is True
@pytest.mark.asyncio
async def test_load_nfo_without_nfo_service(self, background_loader_service):
"""Test graceful handling when NFO service not available."""
background_loader_service.series_app.nfo_service = None
mock_db = AsyncMock()
task = SeriesLoadingTask(
key="test",
folder="test_folder",
name="Test Series"
)
result = await background_loader_service._load_nfo_and_images(task, mock_db)
assert result is False
assert task.progress["nfo"] is False
assert task.progress["logo"] is False
class TestScanMissingEpisodes:
"""Tests for scanning missing episodes."""
@pytest.mark.asyncio
async def test_scan_missing_episodes(self, background_loader_service):
"""Test scanning for missing episodes."""
mock_db = AsyncMock()
mock_series = MagicMock()
task = SeriesLoadingTask(
key="test",
folder="test_folder",
name="Test Series"
)
with patch("src.server.database.service.AnimeSeriesService") as mock_service_class:
mock_service_class.get_by_key = AsyncMock(return_value=mock_series)
await background_loader_service._scan_missing_episodes(task, mock_db)
assert task.progress["episodes"] is True
background_loader_service.anime_service.sync_single_series_after_scan.assert_called_once_with("test")
@pytest.mark.asyncio
async def test_scan_missing_episodes_no_scanner(self, background_loader_service):
"""Test handling when scanner not available."""
background_loader_service.series_app.serie_scanner = None
mock_db = AsyncMock()
task = SeriesLoadingTask(
key="test",
folder="test_folder",
name="Test Series"
)
await background_loader_service._scan_missing_episodes(task, mock_db)
class TestConcurrentProcessing:
"""Tests for concurrent task processing."""
@pytest.mark.asyncio
async def test_multiple_concurrent_tasks(self, background_loader_service):
"""Test processing multiple tasks concurrently."""
await background_loader_service.add_series_loading_task("series1", "folder1", "Series 1")
await background_loader_service.add_series_loading_task("series2", "folder2", "Series 2")
await background_loader_service.add_series_loading_task("series3", "folder3", "Series 3")
assert len(background_loader_service.active_tasks) == 3
assert background_loader_service.task_queue.qsize() == 3
@pytest.mark.asyncio
async def test_max_concurrent_load_limit(self, mock_websocket_service, mock_anime_service, mock_series_app):
"""Test respecting maximum concurrent loads setting."""
service = BackgroundLoaderService(
websocket_service=mock_websocket_service,
anime_service=mock_anime_service,
series_app=mock_series_app,
max_concurrent_loads=2
)
await service.start()
assert len(service.worker_tasks) == 2
await service.stop()
class TestLoadSeriesData:
"""Tests for complete series data loading process."""
@pytest.mark.asyncio
async def test_load_series_data_successful(self, background_loader_service):
"""Test successful series data loading."""
mock_db = AsyncMock()
mock_series = MagicMock()
task = SeriesLoadingTask(
key="test",
folder="test_folder",
name="Test Series"
)
with patch("src.server.database.connection.get_db_session"):
with patch("src.server.database.service.AnimeSeriesService") as mock_service_class:
mock_service_class.get_by_key = AsyncMock(return_value=mock_series)
with patch.object(background_loader_service, "check_missing_data", return_value={
"episodes": True,
"nfo": True,
"logo": True,
"images": True
}):
with patch.object(background_loader_service, "_load_nfo_and_images", return_value=True):
with patch.object(background_loader_service, "_scan_missing_episodes"):
with patch.object(background_loader_service, "_broadcast_status"):
await background_loader_service._load_series_data(task)
assert task.status == LoadingStatus.COMPLETED
assert task.completed_at is not None
assert "test" not in background_loader_service.active_tasks
@pytest.mark.asyncio
async def test_load_series_data_handles_error(self, background_loader_service):
"""Test error handling during series data loading."""
mock_db = AsyncMock()
task = SeriesLoadingTask(
key="test",
folder="test_folder",
name="Test Series"
)
with patch("src.server.database.connection.get_db_session"):
with patch("src.server.database.service.AnimeSeriesService") as mock_service_class:
mock_service_class.get_by_key = AsyncMock(return_value=None)
with patch.object(background_loader_service, "check_missing_data", side_effect=Exception("Test error")):
with patch.object(background_loader_service, "_broadcast_status"):
await background_loader_service._load_series_data(task)
assert task.status == LoadingStatus.FAILED
assert task.error == "Test error"
assert task.completed_at is not None
@pytest.mark.asyncio
async def test_load_series_data_with_partial_missing(self, background_loader_service):
"""Test loading series when only some data is missing."""
mock_db = AsyncMock()
mock_series = MagicMock()
task = SeriesLoadingTask(
key="test",
folder="test_folder",
name="Test Series"
)
with patch("src.server.database.connection.get_db_session"):
with patch("src.server.database.service.AnimeSeriesService") as mock_service_class:
mock_service_class.get_by_key = AsyncMock(return_value=mock_series)
with patch.object(background_loader_service, "check_missing_data", return_value={
"episodes": False,
"nfo": False,
"logo": False,
"images": False
}):
with patch.object(background_loader_service, "_load_nfo_and_images", return_value=False):
with patch.object(background_loader_service, "_scan_missing_episodes"):
with patch.object(background_loader_service, "_broadcast_status"):
await background_loader_service._load_series_data(task)
assert task.status == LoadingStatus.COMPLETED
# When nothing is missing, nfo/logo/images get marked true, but episodes marked false by scan
assert task.progress["nfo"] is True or task.progress["nfo"] is False
class TestWorkerExecution:
"""Tests for background worker execution."""
@pytest.mark.asyncio
async def test_worker_processes_task_from_queue(self, background_loader_service):
"""Test worker processes task from queue."""
# Create a task
task = SeriesLoadingTask(
key="test",
folder="test_folder",
name="Test Series"
)
# Add it to queue
await background_loader_service.task_queue.put(task)
# Start service
await background_loader_service.start()
# Let worker process briefly
await asyncio.sleep(0.1)
# Stop service
await background_loader_service.stop()
# Queue should be empty
assert background_loader_service.task_queue.empty()
@pytest.mark.asyncio
async def test_worker_timeout_loop(self, background_loader_service):
"""Test worker timeout loop works correctly."""
await background_loader_service.start()
# Let worker loop a few times with no tasks
await asyncio.sleep(0.2)
# Workers should still be running
assert all(not task.done() for task in background_loader_service.worker_tasks)
await background_loader_service.stop()
class TestBroadcastStatusMessages:
"""Tests for various status message broadcasts."""
@pytest.mark.asyncio
async def test_broadcast_loading_episodes_status(self, background_loader_service, mock_websocket_service):
"""Test broadcasting loading episodes status."""
task = SeriesLoadingTask(
key="test",
folder="folder",
name="Test",
status=LoadingStatus.LOADING_EPISODES
)
await background_loader_service._broadcast_status(task)
call_args = mock_websocket_service.broadcast.call_args[0][0]
assert "loading" in call_args["message"].lower() or "episode" in call_args["message"].lower()
@pytest.mark.asyncio
async def test_broadcast_loading_nfo_status(self, background_loader_service, mock_websocket_service):
"""Test broadcasting loading NFO status."""
task = SeriesLoadingTask(
key="test",
folder="folder",
name="Test",
status=LoadingStatus.LOADING_NFO
)
await background_loader_service._broadcast_status(task)
call_args = mock_websocket_service.broadcast.call_args[0][0]
assert "nfo" in call_args["message"].lower() or "loading" in call_args["message"].lower()
class TestErrorHandling:
"""Tests for error handling scenarios."""
@pytest.mark.asyncio
async def test_load_nfo_error_handling(self, background_loader_service):
"""Test error handling during NFO creation."""
background_loader_service.series_app.nfo_service.has_nfo = MagicMock(return_value=False)
background_loader_service.series_app.nfo_service.create_tvshow_nfo = AsyncMock(
side_effect=Exception("API error")
)
mock_db = AsyncMock()
task = SeriesLoadingTask(
key="test",
folder="test_folder",
name="Test Series"
)
result = await background_loader_service._load_nfo_and_images(task, mock_db)
assert result is False
assert task.progress["nfo"] is False
assert task.progress["logo"] is False
assert task.progress["images"] is False
@pytest.mark.asyncio
async def test_scan_missing_episodes_error_handling(self, background_loader_service):
"""Test error handling during episode scan."""
background_loader_service.series_app.serie_scanner.scan_single_series = MagicMock(
side_effect=Exception("Scan error")
)
mock_db = AsyncMock()
task = SeriesLoadingTask(
key="test",
folder="test_folder",
name="Test Series"
)
await background_loader_service._scan_missing_episodes(task, mock_db)
assert task.progress["episodes"] is False
class TestDirectoryScanning:
"""Tests for directory operations."""
@pytest.mark.asyncio
async def test_find_series_directory_error_handling(self, background_loader_service):
"""Test error handling when directory lookup fails."""
background_loader_service.series_app.directory_to_search = "/invalid/path"
task = SeriesLoadingTask(
key="test",
folder="TestSeries",
name="Test"
)
result = await background_loader_service._find_series_directory(task)
assert result is None
@pytest.mark.asyncio
async def test_scan_series_episodes_error_handling(self, background_loader_service):
"""Test error handling during episode scanning."""
task = SeriesLoadingTask(
key="test",
folder="TestSeries",
name="Test"
)
# Pass a non-existent path
result = await background_loader_service._scan_series_episodes(Path("/nonexistent"), task)
assert result == {}
class TestCheckMissingDataEdgeCases:
"""Tests for edge cases in missing data checking."""
@pytest.mark.asyncio
async def test_check_missing_data_all_loaded(self, background_loader_service):
"""Test checking missing data when all data is already loaded."""
mock_db = AsyncMock()
mock_series = MagicMock()
mock_series.episodes_loaded = True
mock_series.has_nfo = True
mock_series.logo_loaded = True
mock_series.images_loaded = True
with patch("src.server.database.service.AnimeSeriesService") as mock_service_class:
with patch("src.server.utils.media.check_media_files") as mock_check:
mock_service_class.get_by_key = AsyncMock(return_value=mock_series)
mock_check.return_value = {
"poster": True,
"logo": True,
"fanart": True,
"nfo": True
}
missing = await background_loader_service.check_missing_data(
key="complete_series",
folder="complete_folder",
anime_directory="/anime",
db=mock_db
)
assert not any(missing.values())
class TestTaskProgressTracking:
"""Tests for task progress tracking."""
@pytest.mark.asyncio
async def test_task_progress_updates(self, background_loader_service):
"""Test that task progress is properly updated."""
task = SeriesLoadingTask(
key="test",
folder="folder",
name="Test"
)
# Initially all progress is false
assert not any(task.progress.values())
# Update progress
task.progress["episodes"] = True
assert task.progress["episodes"] is True
task.progress["nfo"] = True
assert task.progress["nfo"] is True
# Other progress still false
assert not task.progress["logo"]
assert not task.progress["images"]
@pytest.mark.asyncio
async def test_task_status_lifecycle(self, background_loader_service):
"""Test task goes through complete status lifecycle."""
task = SeriesLoadingTask(
key="test",
folder="folder",
name="Test"
)
# Start with PENDING
assert task.status == LoadingStatus.PENDING
# Simulate transitions
task.status = LoadingStatus.LOADING_EPISODES
assert task.status == LoadingStatus.LOADING_EPISODES
task.status = LoadingStatus.LOADING_NFO
assert task.status == LoadingStatus.LOADING_NFO
task.status = LoadingStatus.COMPLETED
assert task.status == LoadingStatus.COMPLETED
assert task.completed_at is None # Not set automatically