fix tests

This commit is contained in:
Lukas 2025-11-15 09:11:02 +01:00
parent 8ae8b0cdfb
commit f91875f6fc
10 changed files with 223 additions and 138 deletions

104
fix_tests.py Normal file
View File

@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""Script to batch fix common test issues after API changes."""
import re
import sys
from pathlib import Path
def fix_add_to_queue_calls(content: str) -> str:
"""Add serie_folder parameter to add_to_queue calls."""
# Pattern: add_to_queue(\n serie_id="...",
# Add: serie_folder="...",
pattern = r'(add_to_queue\(\s+serie_id="([^"]+)",)'
def replace_func(match):
serie_id = match.group(2)
# Extract just the series name without number if present
serie_folder = serie_id.split('-')[0] if '-' in serie_id else serie_id
return f'{match.group(1)}\n serie_folder="{serie_folder}",'
return re.sub(pattern, replace_func, content)
def fix_queue_status_response(content: str) -> str:
"""Fix queue status response structure - remove nested 'status' key."""
# Replace data["status"]["pending"] with data["pending_queue"]
content = re.sub(r'data\["status"\]\["pending"\]', 'data["pending_queue"]', content)
content = re.sub(r'data\["status"\]\["active"\]', 'data["active_downloads"]', content)
content = re.sub(r'data\["status"\]\["completed"\]', 'data["completed_downloads"]', content)
content = re.sub(r'data\["status"\]\["failed"\]', 'data["failed_downloads"]', content)
content = re.sub(r'data\["status"\]\["is_running"\]', 'data["is_running"]', content)
content = re.sub(r'data\["status"\]\["is_paused"\]', 'data["is_paused"]', content)
# Also fix response.json()["status"]["..."]
content = re.sub(r'response\.json\(\)\["status"\]\["pending"\]', 'response.json()["pending_queue"]', content)
content = re.sub(r'response\.json\(\)\["status"\]\["is_running"\]', 'response.json()["is_running"]', content)
content = re.sub(r'status\.json\(\)\["status"\]\["is_running"\]', 'status.json()["is_running"]', content)
content = re.sub(r'status\.json\(\)\["status"\]\["failed"\]', 'status.json()["failed_downloads"]', content)
content = re.sub(r'status\.json\(\)\["status"\]\["completed"\]', 'status.json()["completed_downloads"]', content)
# Fix assert "status" in data
content = re.sub(r'assert "status" in data', 'assert "is_running" in data', content)
return content
def fix_anime_service_init(content: str) -> str:
"""Fix AnimeService initialization in test fixtures."""
# This one is complex, so we'll just note files that need manual review
if 'AnimeService(' in content and 'directory=' in content:
print(" ⚠️ Contains AnimeService with directory= parameter - needs manual review")
return content
def main():
test_dir = Path(__file__).parent / "tests"
if not test_dir.exists():
print(f"Error: {test_dir} not found")
sys.exit(1)
files_to_fix = [
# Download service tests
"unit/test_download_service.py",
"unit/test_download_progress_websocket.py",
"integration/test_download_progress_integration.py",
"integration/test_websocket_integration.py",
# API tests with queue status
"api/test_queue_features.py",
"api/test_download_endpoints.py",
"frontend/test_existing_ui_integration.py",
]
for file_path in files_to_fix:
full_path = test_dir / file_path
if not full_path.exists():
print(f"Skipping {file_path} (not found)")
continue
print(f"Processing {file_path}...")
# Read content
content = full_path.read_text()
original_content = content
# Apply fixes
if 'add_to_queue(' in content:
content = fix_add_to_queue_calls(content)
if 'data["status"]' in content or 'response.json()["status"]' in content:
content = fix_queue_status_response(content)
content = fix_anime_service_init(content)
# Write back if changed
if content != original_content:
full_path.write_text(content)
print(f" ✓ Updated {file_path}")
else:
print(f" - No changes needed for {file_path}")
if __name__ == "__main__":
main()

View File

@ -74,7 +74,7 @@ class TestQueueDisplay:
data = response.json()
# Verify structure
assert "status" in data
assert "is_running" in data
assert "statistics" in data
status = data["status"]
@ -107,7 +107,7 @@ class TestQueueDisplay:
assert response.status_code == 200
data = response.json()
pending = data["status"]["pending"]
pending = data["pending_queue"]
assert len(pending) > 0
item = pending[0]
@ -140,7 +140,7 @@ class TestQueueReordering:
)
existing_items = [
item["id"]
for item in status_response.json()["status"]["pending"]
for item in status_response.json()["pending_queue"]
]
if existing_items:
await client.request(
@ -190,7 +190,7 @@ class TestQueueReordering:
)
current_order = [
item["id"]
for item in status_response.json()["status"]["pending"]
for item in status_response.json()["pending_queue"]
]
assert current_order == new_order
@ -270,7 +270,7 @@ class TestQueueControl:
"/api/queue/status",
headers=auth_headers
)
assert status.json()["status"]["is_running"] is False
assert status.json()["is_running"] is False
# Start queue
await client.post("/api/queue/start", headers=auth_headers)
@ -280,7 +280,7 @@ class TestQueueControl:
"/api/queue/status",
headers=auth_headers
)
assert status.json()["status"]["is_running"] is True
assert status.json()["is_running"] is True
# Stop queue
await client.post("/api/queue/stop", headers=auth_headers)
@ -290,7 +290,7 @@ class TestQueueControl:
"/api/queue/status",
headers=auth_headers
)
assert status.json()["status"]["is_running"] is False
assert status.json()["is_running"] is False
class TestCompletedDownloads:
@ -323,7 +323,7 @@ class TestCompletedDownloads:
data = status.json()
completed_count = data["statistics"]["completed_count"]
completed_list = len(data["status"]["completed"])
completed_list = len(data["completed_downloads"])
# Count should match list length
assert completed_count == completed_list
@ -390,7 +390,7 @@ class TestFailedDownloads:
data = status.json()
failed_count = data["statistics"]["failed_count"]
failed_list = len(data["status"]["failed"])
failed_list = len(data["failed_downloads"])
# Count should match list length
assert failed_count == failed_list

View File

@ -244,7 +244,7 @@ class TestFrontendDownloadAPI:
assert response.status_code == 200
data = response.json()
# Check for expected response structure
assert "status" in data or "statistics" in data
assert "is_running" in data or "statistics" in data
async def test_start_download_queue(self, authenticated_client):
"""Test POST /api/queue/start starts next download."""

View File

@ -63,15 +63,11 @@ def websocket_service():
@pytest.fixture
async def anime_service(mock_series_app, progress_service):
"""Create an AnimeService."""
with patch(
"src.server.services.anime_service.SeriesApp",
return_value=mock_series_app
):
service = AnimeService(
directory="/test/anime",
progress_service=progress_service,
)
yield service
service = AnimeService(
series_app=mock_series_app,
progress_service=progress_service,
)
yield service
@pytest.fixture
@ -130,6 +126,7 @@ class TestDownloadProgressIntegration:
# Add download to queue
await download_service.add_to_queue(
serie_id="integration_test",
serie_folder="integration_test",
serie_folder="test_folder",
serie_name="Integration Test Anime",
episodes=[EpisodeIdentifier(season=1, episode=1)],
@ -203,6 +200,7 @@ class TestDownloadProgressIntegration:
# Add and start download
await download_service.add_to_queue(
serie_id="client_test",
serie_folder="client_test",
serie_folder="test_folder",
serie_name="Client Test Anime",
episodes=[EpisodeIdentifier(season=1, episode=1)],
@ -266,6 +264,7 @@ class TestDownloadProgressIntegration:
# Start download
await download_service.add_to_queue(
serie_id="multi_client_test",
serie_folder="multi_client_test",
serie_folder="test_folder",
serie_name="Multi Client Test",
episodes=[EpisodeIdentifier(season=1, episode=1)],
@ -313,6 +312,7 @@ class TestDownloadProgressIntegration:
await download_service.add_to_queue(
serie_id="structure_test",
serie_folder="structure_test",
serie_folder="test_folder",
serie_name="Structure Test",
episodes=[EpisodeIdentifier(season=2, episode=3)],
@ -380,6 +380,7 @@ class TestDownloadProgressIntegration:
# Start download after disconnect
await download_service.add_to_queue(
serie_id="disconnect_test",
serie_folder="disconnect_test",
serie_folder="test_folder",
serie_name="Disconnect Test",
episodes=[EpisodeIdentifier(season=1, episode=1)],

View File

@ -47,12 +47,11 @@ def websocket_service():
@pytest.fixture
async def anime_service(mock_series_app, progress_service):
"""Create an AnimeService with mocked dependencies."""
with patch("src.server.services.anime_service.SeriesApp", return_value=mock_series_app):
service = AnimeService(
directory="/test/anime",
progress_service=progress_service,
)
yield service
service = AnimeService(
series_app=mock_series_app,
progress_service=progress_service,
)
yield service
@pytest.fixture
@ -86,6 +85,7 @@ class TestWebSocketDownloadIntegration:
# Add item to queue
item_ids = await download_service.add_to_queue(
serie_id="test_serie",
serie_folder="test_serie",
serie_name="Test Anime",
episodes=[EpisodeIdentifier(season=1, episode=1)],
priority=DownloadPriority.HIGH,
@ -112,6 +112,7 @@ class TestWebSocketDownloadIntegration:
# Add items
item_ids = await download_service.add_to_queue(
serie_id="test",
serie_folder="test",
serie_name="Test",
episodes=[EpisodeIdentifier(season=1, episode=i) for i in range(1, 4)],
priority=DownloadPriority.NORMAL,
@ -398,6 +399,7 @@ class TestWebSocketEndToEnd:
# Add items to queue
item_ids = await download_service.add_to_queue(
serie_id="test",
serie_folder="test",
serie_name="Test Anime",
episodes=[EpisodeIdentifier(season=1, episode=1)],
priority=DownloadPriority.HIGH,

View File

@ -6,7 +6,7 @@ error handling, and progress reporting integration.
from __future__ import annotations
import asyncio
from unittest.mock import AsyncMock, MagicMock, patch
from unittest.mock import AsyncMock, MagicMock
import pytest
@ -15,16 +15,17 @@ from src.server.services.progress_service import ProgressService
@pytest.fixture
def mock_series_app():
def mock_series_app(tmp_path):
"""Create a mock SeriesApp instance."""
with patch("src.server.services.anime_service.SeriesApp") as mock_class:
mock_instance = MagicMock()
mock_instance.series_list = []
mock_instance.search = MagicMock(return_value=[])
mock_instance.ReScan = MagicMock()
mock_instance.download = MagicMock(return_value=True)
mock_class.return_value = mock_instance
yield mock_instance
mock_instance = MagicMock()
mock_instance.directory_to_search = str(tmp_path)
mock_instance.series_list = []
mock_instance.search = AsyncMock(return_value=[])
mock_instance.rescan = AsyncMock()
mock_instance.download = AsyncMock(return_value=True)
mock_instance.download_status = None
mock_instance.scan_status = None
return mock_instance
@pytest.fixture
@ -42,8 +43,7 @@ def mock_progress_service():
def anime_service(tmp_path, mock_series_app, mock_progress_service):
"""Create an AnimeService instance for testing."""
return AnimeService(
directory=str(tmp_path),
max_workers=2,
series_app=mock_series_app,
progress_service=mock_progress_service,
)
@ -51,35 +51,38 @@ def anime_service(tmp_path, mock_series_app, mock_progress_service):
class TestAnimeServiceInitialization:
"""Test AnimeService initialization."""
def test_initialization_success(self, tmp_path, mock_progress_service):
def test_initialization_success(
self, mock_series_app, mock_progress_service
):
"""Test successful service initialization."""
with patch("src.server.services.anime_service.SeriesApp"):
service = AnimeService(
directory=str(tmp_path),
max_workers=2,
progress_service=mock_progress_service,
)
assert service._directory == str(tmp_path)
assert service._executor is not None
assert service._progress_service is mock_progress_service
service = AnimeService(
series_app=mock_series_app,
progress_service=mock_progress_service,
)
assert service._app is mock_series_app
assert service._progress_service is mock_progress_service
def test_initialization_failure_raises_error(
self, tmp_path, mock_progress_service
):
"""Test SeriesApp initialization failure raises error."""
with patch(
"src.server.services.anime_service.SeriesApp"
) as mock_class:
mock_class.side_effect = Exception("Initialization failed")
with pytest.raises(
AnimeServiceError, match="Initialization failed"
):
AnimeService(
directory=str(tmp_path),
progress_service=mock_progress_service,
)
bad_series_app = MagicMock()
bad_series_app.directory_to_search = str(tmp_path)
# Make event subscription fail
def raise_error(*args):
raise Exception("Initialization failed")
bad_series_app.__setattr__ = raise_error
with pytest.raises(
AnimeServiceError, match="Initialization failed"
):
AnimeService(
series_app=bad_series_app,
progress_service=mock_progress_service,
)
class TestListMissing:
@ -321,12 +324,12 @@ class TestConcurrency:
class TestFactoryFunction:
"""Test factory function."""
def test_get_anime_service(self, tmp_path):
def test_get_anime_service(self):
"""Test get_anime_service factory function."""
from src.server.services.anime_service import get_anime_service
# The factory function doesn't take directory anymore
service = get_anime_service()
with patch("src.server.services.anime_service.SeriesApp"):
service = get_anime_service(directory=str(tmp_path))
assert isinstance(service, AnimeService)
assert service._directory == str(tmp_path)
assert isinstance(service, AnimeService)
assert service._app is not None

View File

@ -48,15 +48,15 @@ class TestDownloadPriority:
def test_all_priorities_exist(self):
"""Test that all expected priorities are defined."""
assert DownloadPriority.LOW == "low"
assert DownloadPriority.NORMAL == "normal"
assert DownloadPriority.HIGH == "high"
assert DownloadPriority.LOW == "LOW"
assert DownloadPriority.NORMAL == "NORMAL"
assert DownloadPriority.HIGH == "HIGH"
def test_priority_values(self):
"""Test that priority values are lowercase strings."""
"""Test that priority values are uppercase strings."""
for priority in DownloadPriority:
assert isinstance(priority.value, str)
assert priority.value.islower()
assert priority.value.isupper()
class TestEpisodeIdentifier:

View File

@ -76,15 +76,11 @@ def progress_service():
@pytest.fixture
async def anime_service(mock_series_app, progress_service):
"""Create an AnimeService with mocked dependencies."""
with patch(
"src.server.services.anime_service.SeriesApp",
return_value=mock_series_app
):
service = AnimeService(
directory="/test/anime",
progress_service=progress_service,
)
yield service
service = AnimeService(
series_app=mock_series_app,
progress_service=progress_service,
)
yield service
@pytest.fixture
@ -118,7 +114,7 @@ class TestDownloadProgressWebSocket:
# Add item to queue
item_ids = await download_service.add_to_queue(
serie_id="test_serie_1",
serie_folder="test_folder",
serie_folder="test_serie_1",
serie_name="Test Anime",
episodes=[EpisodeIdentifier(season=1, episode=1)],
priority=DownloadPriority.NORMAL,
@ -168,7 +164,7 @@ class TestDownloadProgressWebSocket:
# Add item with specific episode info
await download_service.add_to_queue(
serie_id="test_serie_2",
serie_folder="test_folder",
serie_folder="test_serie_2",
serie_name="My Test Anime",
episodes=[EpisodeIdentifier(season=2, episode=5)],
priority=DownloadPriority.HIGH,
@ -203,7 +199,7 @@ class TestDownloadProgressWebSocket:
await download_service.add_to_queue(
serie_id="test_serie_3",
serie_folder="test_folder",
serie_folder="test_serie_3",
serie_name="Progress Test",
episodes=[EpisodeIdentifier(season=1, episode=1)],
)
@ -240,7 +236,7 @@ class TestDownloadProgressWebSocket:
await download_service.add_to_queue(
serie_id="test_serie_4",
serie_folder="test_folder",
serie_folder="test_serie_4",
serie_name="Speed Test",
episodes=[EpisodeIdentifier(season=1, episode=1)],
)
@ -272,7 +268,7 @@ class TestDownloadProgressWebSocket:
await download_service.add_to_queue(
serie_id="test_serie_5",
serie_folder="test_folder",
serie_folder="test_serie_5",
serie_name="No Broadcast Test",
episodes=[EpisodeIdentifier(season=1, episode=1)],
)
@ -300,7 +296,7 @@ class TestDownloadProgressWebSocket:
await download_service.add_to_queue(
serie_id="test_serie_6",
serie_folder="test_folder",
serie_folder="test_serie_6",
serie_name="Error Handling Test",
episodes=[EpisodeIdentifier(season=1, episode=1)],
)
@ -331,7 +327,7 @@ class TestDownloadProgressWebSocket:
# Add multiple episodes
item_ids = await download_service.add_to_queue(
serie_id="test_serie_7",
serie_folder="test_folder",
serie_folder="test_serie_7",
serie_name="Multi Episode Test",
episodes=[
EpisodeIdentifier(season=1, episode=1),
@ -378,7 +374,7 @@ class TestDownloadProgressWebSocket:
await download_service.add_to_queue(
serie_id="test_serie_8",
serie_folder="test_folder",
serie_folder="test_serie_8",
serie_name="Model Test",
episodes=[EpisodeIdentifier(season=1, episode=1)],
)

View File

@ -118,6 +118,7 @@ class TestQueueManagement:
item_ids = await download_service.add_to_queue(
serie_id="series-1",
serie_folder="series",
serie_name="Test Series",
episodes=episodes,
priority=DownloadPriority.NORMAL,
@ -142,6 +143,7 @@ class TestQueueManagement:
item_ids = await download_service.add_to_queue(
serie_id="series-1",
serie_folder="series",
serie_name="Test Series",
episodes=episodes,
priority=DownloadPriority.NORMAL,
@ -155,6 +157,7 @@ class TestQueueManagement:
"""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)],
)
@ -171,6 +174,7 @@ class TestQueueManagement:
# Add items to 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),
@ -200,6 +204,7 @@ class TestQueueManagement:
# 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),
@ -236,6 +241,7 @@ class TestQueueManagement:
# 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)],
)
@ -258,6 +264,7 @@ class TestQueueManagement:
# 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)],
)
@ -279,6 +286,7 @@ class TestQueueStatus:
# 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),
@ -302,6 +310,7 @@ class TestQueueStatus:
# 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),
@ -380,6 +389,7 @@ class TestPersistence:
"""Test that queue state is persisted to disk."""
await download_service.add_to_queue(
serie_id="series-1",
serie_folder="series",
serie_name="Test Series",
episodes=[EpisodeIdentifier(season=1, episode=1)],
)
@ -408,6 +418,7 @@ class TestPersistence:
await service1.add_to_queue(
serie_id="series-1",
serie_folder="series",
serie_name="Test Series",
episodes=[
EpisodeIdentifier(season=1, episode=1),
@ -491,6 +502,7 @@ class TestBroadcastCallbacks:
await download_service.add_to_queue(
serie_id="series-1",
serie_folder="series",
serie_name="Test Series",
episodes=[EpisodeIdentifier(season=1, episode=1)],
)
@ -530,6 +542,7 @@ class TestBroadcastCallbacks:
# Add an item to the queue
await download_service.add_to_queue(
serie_id="series-1",
serie_folder="series",
serie_name="Test Series",
episodes=[EpisodeIdentifier(season=1, episode=1)],
)
@ -617,6 +630,7 @@ class TestErrorHandling:
await download_service.add_to_queue(
serie_id="series-1",
serie_folder="series",
serie_name="Test Series",
episodes=[EpisodeIdentifier(season=1, episode=1)],
)

View File

@ -7,11 +7,10 @@ Tests the functionality of SeriesApp including:
- Download with progress callbacks
- Directory scanning with progress reporting
- Async versions of operations
- Cancellation support
- Error handling
"""
from unittest.mock import Mock, patch
from unittest.mock import AsyncMock, Mock, patch
import pytest
@ -35,62 +34,30 @@ class TestSeriesAppInitialization:
# Verify initialization
assert app.directory_to_search == test_dir
assert app._operation_status == OperationStatus.IDLE
assert app._cancel_flag is False
assert app._current_operation is None
mock_loaders.assert_called_once()
mock_scanner.assert_called_once()
@patch('src.core.SeriesApp.Loaders')
@patch('src.core.SeriesApp.SerieScanner')
@patch('src.core.SeriesApp.SerieList')
def test_init_with_callbacks(
self, mock_serie_list, mock_scanner, mock_loaders
):
"""Test initialization with progress and error callbacks."""
def test_init_failure_raises_error(self, mock_loaders):
"""Test that initialization failure raises error."""
test_dir = "/test/anime"
progress_callback = Mock()
error_callback = Mock()
# Create app with callbacks
app = SeriesApp(
test_dir,
progress_callback=progress_callback,
error_callback=error_callback
)
# Verify callbacks are stored
assert app.progress_callback == progress_callback
assert app.error_callback == error_callback
@patch('src.core.SeriesApp.Loaders')
def test_init_failure_calls_error_callback(self, mock_loaders):
"""Test that initialization failure triggers error callback."""
test_dir = "/test/anime"
error_callback = Mock()
# Make Loaders raise an exception
mock_loaders.side_effect = RuntimeError("Init failed")
# Create app should raise but call error callback
# Create app should raise
with pytest.raises(RuntimeError):
SeriesApp(test_dir, error_callback=error_callback)
# Verify error callback was called
error_callback.assert_called_once()
assert isinstance(
error_callback.call_args[0][0],
RuntimeError
)
SeriesApp(test_dir)
class TestSeriesAppSearch:
"""Test search functionality."""
@pytest.mark.asyncio
@patch('src.core.SeriesApp.Loaders')
@patch('src.core.SeriesApp.SerieScanner')
@patch('src.core.SeriesApp.SerieList')
def test_search_success(
async def test_search_success(
self, mock_serie_list, mock_scanner, mock_loaders
):
"""Test successful search."""
@ -104,34 +71,32 @@ class TestSeriesAppSearch:
]
app.loader.search = Mock(return_value=expected_results)
# Perform search
results = app.search("test anime")
# Perform search (now async)
results = await app.search("test anime")
# Verify results
assert results == expected_results
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')
def test_search_failure_calls_error_callback(
async def test_search_failure_raises_error(
self, mock_serie_list, mock_scanner, mock_loaders
):
"""Test search failure triggers error callback."""
"""Test search failure raises error."""
test_dir = "/test/anime"
error_callback = Mock()
app = SeriesApp(test_dir, error_callback=error_callback)
app = SeriesApp(test_dir)
# Make search raise an exception
app.loader.search = Mock(
side_effect=RuntimeError("Search failed")
)
# Search should raise and call error callback
# Search should raise
with pytest.raises(RuntimeError):
app.search("test")
error_callback.assert_called_once()
await app.search("test")
class TestSeriesAppDownload: