Aniworld/tests/api/test_queue_features.py
2025-10-30 21:13:08 +01:00

467 lines
14 KiB
Python

"""Tests for queue management features.
This module tests the queue page functionality including:
- Display of queued items in organized lists
- Drag-and-drop reordering
- Starting and stopping queue processing
- Filtering completed and failed downloads
"""
import pytest
from httpx import ASGITransport, AsyncClient
from src.server.fastapi_app import app
@pytest.fixture
async def client():
"""Create an async test client."""
transport = ASGITransport(app=app)
async with AsyncClient(
transport=transport, base_url="http://test"
) as client:
yield client
@pytest.fixture
async def auth_headers(client: AsyncClient):
"""Get authentication headers with valid JWT token."""
# Setup auth
await client.post(
"/api/auth/setup",
json={"master_password": "TestPass123!"}
)
# Login
response = await client.post(
"/api/auth/login",
json={"password": "TestPass123!"}
)
data = response.json()
token = data["access_token"]
return {"Authorization": f"Bearer {token}"}
@pytest.fixture
def sample_download_request():
"""Sample download request for testing."""
return {
"serie_id": "test-series",
"serie_name": "Test Series",
"episodes": [
{"season": 1, "episode": 1},
{"season": 1, "episode": 2}
],
"priority": "normal"
}
class TestQueueDisplay:
"""Test queue display and organization."""
@pytest.mark.asyncio
async def test_queue_status_includes_all_sections(
self, client: AsyncClient, auth_headers: dict
):
"""Test queue status includes all sections."""
response = await client.get(
"/api/queue/status",
headers=auth_headers
)
assert response.status_code == 200
data = response.json()
# Verify structure
assert "status" in data
assert "statistics" in data
status = data["status"]
assert "active" in status
assert "pending" in status
assert "completed" in status
assert "failed" in status
assert "is_running" in status
assert "is_paused" in status
@pytest.mark.asyncio
async def test_queue_items_have_required_fields(
self, client: AsyncClient, auth_headers: dict,
sample_download_request: dict
):
"""Test queue items have required display fields."""
# Add an item to the queue
add_response = await client.post(
"/api/queue/add",
json=sample_download_request,
headers=auth_headers
)
assert add_response.status_code == 201
# Get queue status
response = await client.get(
"/api/queue/status",
headers=auth_headers
)
assert response.status_code == 200
data = response.json()
pending = data["status"]["pending"]
assert len(pending) > 0
item = pending[0]
# Verify required fields for display
assert "id" in item
assert "serie_name" in item
assert "episode" in item
assert "priority" in item
assert "added_at" in item
# Verify episode structure
episode = item["episode"]
assert "season" in episode
assert "episode" in episode
class TestQueueReordering:
"""Test queue reordering functionality."""
@pytest.mark.asyncio
async def test_reorder_queue_with_item_ids(
self, client: AsyncClient, auth_headers: dict
):
"""Test reordering queue using item_ids array."""
# Clear existing queue first
status_response = await client.get(
"/api/queue/status",
headers=auth_headers
)
existing_items = [
item["id"]
for item in status_response.json()["status"]["pending"]
]
if existing_items:
await client.request(
"DELETE",
"/api/queue/",
json={"item_ids": existing_items},
headers=auth_headers
)
# Add exactly 3 items
added_ids = []
for i in range(3):
response = await client.post(
"/api/queue/add",
json={
"serie_id": f"test-{i}",
"serie_name": f"Test Series {i}",
"episodes": [{"season": 1, "episode": i+1}],
"priority": "normal"
},
headers=auth_headers
)
if response.status_code == 201:
data = response.json()
if "added_items" in data and data["added_items"]:
added_ids.extend(data["added_items"])
assert len(added_ids) == 3, f"Expected 3 items, got {len(added_ids)}"
# Reverse the order
new_order = list(reversed(added_ids))
# Reorder
reorder_response = await client.post(
"/api/queue/reorder",
json={"item_ids": new_order},
headers=auth_headers
)
assert reorder_response.status_code == 200
assert reorder_response.json()["status"] == "success"
# Verify new order
status_response = await client.get(
"/api/queue/status",
headers=auth_headers
)
current_order = [
item["id"]
for item in status_response.json()["status"]["pending"]
]
assert current_order == new_order
@pytest.mark.asyncio
async def test_reorder_with_invalid_ids(
self, client: AsyncClient, auth_headers: dict
):
"""Test reordering with non-existent IDs succeeds (idempotent)."""
response = await client.post(
"/api/queue/reorder",
json={"item_ids": ["invalid-id-1", "invalid-id-2"]},
headers=auth_headers
)
# Bulk reorder is idempotent and succeeds even with invalid IDs
# It just ignores items that don't exist
assert response.status_code == 200
@pytest.mark.asyncio
async def test_reorder_empty_list(
self, client: AsyncClient, auth_headers: dict
):
"""Test reordering with empty list."""
response = await client.post(
"/api/queue/reorder",
json={"item_ids": []},
headers=auth_headers
)
# Should succeed but do nothing
assert response.status_code in [200, 404]
class TestQueueControl:
"""Test queue start/stop functionality."""
@pytest.mark.asyncio
async def test_start_queue(
self, client: AsyncClient, auth_headers: dict
):
"""Test starting the download queue."""
response = await client.post(
"/api/queue/start",
headers=auth_headers
)
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
@pytest.mark.asyncio
async def test_stop_queue(
self, client: AsyncClient, auth_headers: dict
):
"""Test stopping the download queue."""
# Start first
await client.post("/api/queue/start", headers=auth_headers)
# Then stop
response = await client.post(
"/api/queue/stop",
headers=auth_headers
)
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
@pytest.mark.asyncio
async def test_queue_status_reflects_running_state(
self, client: AsyncClient, auth_headers: dict
):
"""Test queue status reflects running state."""
# Initially not running
status = await client.get(
"/api/queue/status",
headers=auth_headers
)
assert status.json()["status"]["is_running"] is False
# Start queue
await client.post("/api/queue/start", headers=auth_headers)
# Should be running
status = await client.get(
"/api/queue/status",
headers=auth_headers
)
assert status.json()["status"]["is_running"] is True
# Stop queue
await client.post("/api/queue/stop", headers=auth_headers)
# Should not be running
status = await client.get(
"/api/queue/status",
headers=auth_headers
)
assert status.json()["status"]["is_running"] is False
class TestCompletedDownloads:
"""Test completed downloads management."""
@pytest.mark.asyncio
async def test_clear_completed_downloads(
self, client: AsyncClient, auth_headers: dict
):
"""Test clearing completed downloads."""
response = await client.delete(
"/api/queue/completed",
headers=auth_headers
)
assert response.status_code == 200
data = response.json()
assert "count" in data
assert data["status"] == "success"
@pytest.mark.asyncio
async def test_completed_section_count(
self, client: AsyncClient, auth_headers: dict
):
"""Test that completed count is accurate."""
status = await client.get(
"/api/queue/status",
headers=auth_headers
)
data = status.json()
completed_count = data["statistics"]["completed_count"]
completed_list = len(data["status"]["completed"])
# Count should match list length
assert completed_count == completed_list
class TestFailedDownloads:
"""Test failed downloads management."""
@pytest.mark.asyncio
async def test_clear_failed_downloads(
self, client: AsyncClient, auth_headers: dict
):
"""Test clearing failed downloads."""
response = await client.delete(
"/api/queue/failed",
headers=auth_headers
)
assert response.status_code == 200
data = response.json()
assert "count" in data
assert data["status"] == "success"
@pytest.mark.asyncio
async def test_retry_failed_downloads(
self, client: AsyncClient, auth_headers: dict
):
"""Test retrying failed downloads."""
response = await client.post(
"/api/queue/retry",
json={"item_ids": []},
headers=auth_headers
)
assert response.status_code == 200
data = response.json()
assert "retried_count" in data
assert data["status"] == "success"
@pytest.mark.asyncio
async def test_retry_specific_failed_download(
self, client: AsyncClient, auth_headers: dict
):
"""Test retrying a specific failed download."""
# Test the endpoint accepts the format
response = await client.post(
"/api/queue/retry",
json={"item_ids": ["some-id"]},
headers=auth_headers
)
# Should succeed even if ID doesn't exist (idempotent)
assert response.status_code == 200
@pytest.mark.asyncio
async def test_failed_section_count(
self, client: AsyncClient, auth_headers: dict
):
"""Test that failed count is accurate."""
status = await client.get(
"/api/queue/status",
headers=auth_headers
)
data = status.json()
failed_count = data["statistics"]["failed_count"]
failed_list = len(data["status"]["failed"])
# Count should match list length
assert failed_count == failed_list
class TestBulkOperations:
"""Test bulk queue operations."""
@pytest.mark.asyncio
async def test_remove_multiple_items(
self, client: AsyncClient, auth_headers: dict
):
"""Test removing multiple items from queue."""
# Add multiple items
item_ids = []
for i in range(3):
add_response = await client.post(
"/api/queue/add",
json={
"serie_id": f"bulk-test-{i}",
"serie_name": f"Bulk Test {i}",
"episodes": [{"season": 1, "episode": i+1}],
"priority": "normal"
},
headers=auth_headers
)
if add_response.status_code == 201:
data = add_response.json()
if "added_items" in data and len(data["added_items"]) > 0:
item_ids.append(data["added_items"][0])
# Remove all at once
if item_ids:
response = await client.request(
"DELETE",
"/api/queue/",
json={"item_ids": item_ids},
headers=auth_headers
)
assert response.status_code == 204
@pytest.mark.asyncio
async def test_clear_entire_pending_queue(
self, client: AsyncClient, auth_headers: dict
):
"""Test clearing entire pending queue."""
# Get all pending items
status = await client.get(
"/api/queue/status",
headers=auth_headers
)
pending = status.json()["status"]["pending"]
if pending:
item_ids = [item["id"] for item in pending]
# Remove all
response = await client.request(
"DELETE",
"/api/queue/",
json={"item_ids": item_ids},
headers=auth_headers
)
assert response.status_code == 204
# Verify queue is empty
status = await client.get(
"/api/queue/status",
headers=auth_headers
)
assert len(status.json()["status"]["pending"]) == 0