"""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)