Aniworld/tests/api/test_anime_endpoints.py

138 lines
4.4 KiB
Python

"""Tests for anime API endpoints."""
import asyncio
import pytest
from httpx import ASGITransport, AsyncClient
from src.server.api import anime as anime_module
from src.server.fastapi_app import app
from src.server.services.auth_service import auth_service
class FakeSerie:
"""Mock Serie object for testing."""
def __init__(self, key, name, folder, episodeDict=None):
"""Initialize fake serie."""
self.key = key
self.name = name
self.folder = folder
self.episodeDict = episodeDict or {}
class FakeSeriesApp:
"""Mock SeriesApp for testing."""
def __init__(self):
"""Initialize fake series app."""
self.List = self
self._items = [
FakeSerie("1", "Test Show", "test_show", {1: [1, 2]}),
FakeSerie("2", "Complete Show", "complete_show", {}),
]
def GetMissingEpisode(self):
"""Return series with missing episodes."""
return [s for s in self._items if s.episodeDict]
def GetList(self):
"""Return all series."""
return self._items
def ReScan(self, callback):
"""Trigger rescan with callback."""
callback()
@pytest.fixture(autouse=True)
def reset_auth_state():
"""Reset auth service state before each test."""
if hasattr(auth_service, '_failed'):
auth_service._failed.clear()
yield
if hasattr(auth_service, '_failed'):
auth_service._failed.clear()
@pytest.fixture
async def authenticated_client():
"""Create authenticated async client."""
if not auth_service.is_configured():
auth_service.setup_master_password("TestPass123!")
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
# Login to get token
r = await client.post(
"/api/auth/login", json={"password": "TestPass123!"}
)
if r.status_code == 200:
token = r.json()["access_token"]
client.headers["Authorization"] = f"Bearer {token}"
yield client
def test_list_anime_direct_call():
"""Test list_anime function directly."""
fake = FakeSeriesApp()
result = asyncio.run(anime_module.list_anime(series_app=fake))
assert isinstance(result, list)
assert any(item.title == "Test Show" for item in result)
def test_get_anime_detail_direct_call():
"""Test get_anime function directly."""
fake = FakeSeriesApp()
result = asyncio.run(anime_module.get_anime("1", series_app=fake))
assert result.title == "Test Show"
assert "1-1" in result.episodes
def test_rescan_direct_call():
"""Test trigger_rescan function directly."""
fake = FakeSeriesApp()
result = asyncio.run(anime_module.trigger_rescan(series_app=fake))
assert result["success"] is True
@pytest.mark.anyio
async def test_list_anime_endpoint_unauthorized():
"""Test GET /api/v1/anime without authentication."""
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.get("/api/v1/anime/")
# Should work without auth or return 401/503
assert response.status_code in (200, 401, 503)
@pytest.mark.anyio
async def test_rescan_endpoint_unauthorized():
"""Test POST /api/v1/anime/rescan without authentication."""
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.post("/api/v1/anime/rescan")
# Should require auth or return service error
assert response.status_code in (401, 503)
@pytest.mark.anyio
async def test_search_anime_endpoint_unauthorized():
"""Test POST /api/v1/anime/search without authentication."""
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.post(
"/api/v1/anime/search", json={"query": "test"}
)
# Should work or require auth
assert response.status_code in (200, 401, 503)
@pytest.mark.anyio
async def test_get_anime_detail_endpoint_unauthorized():
"""Test GET /api/v1/anime/{id} without authentication."""
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.get("/api/v1/anime/1")
# Should work or require auth
assert response.status_code in (200, 401, 404, 503)