- Add NFO API endpoints: diagnostics, repair, validate, needs-repair - Create /settings/nfo page with full NFO management UI - Add NFO status section to edit modal with repair functionality - Add anime details API for edit modal pre-fill data - Fix auth test fixtures in test_nfo_diagnostics_repair.py Implements NFO diagnostics when editing anime series via right-click menu. Users can now check NFO status, see missing tags, and repair NFOs directly from the edit modal or the dedicated NFO settings page.
156 lines
5.4 KiB
Python
156 lines
5.4 KiB
Python
"""Tests for NFO diagnostics and repair API endpoints.
|
|
|
|
These tests verify the NFO diagnostics, repair, validate, and needs-repair
|
|
endpoints. Note: The existing conftest.py sets up auth automatically, so
|
|
we don't need to redefine the client fixture here.
|
|
"""
|
|
from unittest.mock import AsyncMock, Mock
|
|
|
|
import pytest
|
|
from httpx import ASGITransport, AsyncClient
|
|
|
|
from src.server.fastapi_app import app
|
|
from src.server.services.auth_service import auth_service
|
|
|
|
|
|
# Note: conftest.py already handles auth reset via reset_auth_and_rate_limits
|
|
# The reset_auth fixture here is only needed for tests that explicitly
|
|
# need a clean auth state BEFORE conftest's setup runs.
|
|
# @pytest.fixture(autouse=True)
|
|
@pytest.fixture
|
|
def reset_auth():
|
|
"""Reset authentication state before each test."""
|
|
original_hash = auth_service._hash
|
|
auth_service._hash = None
|
|
auth_service._failed.clear()
|
|
yield
|
|
auth_service._hash = original_hash
|
|
auth_service._failed.clear()
|
|
|
|
|
|
@pytest.fixture
|
|
async def client():
|
|
"""Create an async test client."""
|
|
transport = ASGITransport(app=app)
|
|
async with AsyncClient(transport=transport, base_url="http://test") as ac:
|
|
yield ac
|
|
|
|
|
|
@pytest.fixture
|
|
async def authenticated_client(client):
|
|
"""Create an authenticated test client with token.
|
|
|
|
Note: conftest.py already sets up auth with password "TestPass123!".
|
|
This fixture just logs in to get a token.
|
|
"""
|
|
# Login to get token (auth is already set up by conftest)
|
|
response = await client.post(
|
|
"/api/auth/login",
|
|
json={"password": "TestPass123!"}
|
|
)
|
|
# If already logged in from conftest, might return error - that's ok
|
|
if response.status_code == 200:
|
|
token = response.json()["access_token"]
|
|
client.headers.update({"Authorization": f"Bearer {token}"})
|
|
yield client
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_anime_service():
|
|
"""Create mock anime service."""
|
|
service = Mock()
|
|
service.list_series_with_filters = AsyncMock(return_value=[])
|
|
return service
|
|
|
|
|
|
@pytest.fixture
|
|
def override_anime_service(mock_anime_service):
|
|
"""Override anime service dependency."""
|
|
from src.server.utils.dependencies import get_anime_service
|
|
app.dependency_overrides[get_anime_service] = lambda: mock_anime_service
|
|
yield
|
|
if get_anime_service in app.dependency_overrides:
|
|
del app.dependency_overrides[get_anime_service]
|
|
|
|
|
|
class TestNfoNeedsRepair:
|
|
"""Tests for GET /api/nfo/needs-repair."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_needs_repair_requires_auth(self, client):
|
|
"""Test needs-repair endpoint requires authentication."""
|
|
response = await client.get("/api/nfo/needs-repair")
|
|
# Without auth, returns 401
|
|
assert response.status_code == 401
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_needs_repair_returns_data(
|
|
self, authenticated_client, override_anime_service
|
|
):
|
|
"""Test needs-repair endpoint returns proper structure when mocked."""
|
|
response = await authenticated_client.get("/api/nfo/needs-repair")
|
|
|
|
# Should return 200 (when anime_directory is mocked) or 503 (not mocked)
|
|
assert response.status_code in (200, 503)
|
|
|
|
if response.status_code == 200:
|
|
data = response.json()
|
|
assert "total" in data
|
|
assert "missing_nfo_count" in data
|
|
assert "incomplete_nfo_count" in data
|
|
assert "series" in data
|
|
|
|
|
|
class TestNfoDiagnostics:
|
|
"""Tests for GET /api/nfo/{serie_key}/diagnostics."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_diagnostics_requires_auth(self, client):
|
|
"""Test diagnostics endpoint requires authentication."""
|
|
response = await client.get("/api/nfo/test-anime/diagnostics")
|
|
# Without auth, returns 401
|
|
assert response.status_code == 401
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_diagnostics_returns_404_for_nonexistent(
|
|
self, authenticated_client, override_anime_service
|
|
):
|
|
"""Test diagnostics for non-existent series returns 404."""
|
|
response = await authenticated_client.get("/api/nfo/nonexistent-key/diagnostics")
|
|
assert response.status_code == 404
|
|
|
|
|
|
class TestNfoRepair:
|
|
"""Tests for POST /api/nfo/{serie_key}/repair."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_repair_requires_auth(self, client):
|
|
"""Test repair endpoint requires authentication."""
|
|
response = await client.post("/api/nfo/test-anime/repair", json={})
|
|
assert response.status_code == 401
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_repair_returns_404_for_nonexistent(
|
|
self, authenticated_client, override_anime_service
|
|
):
|
|
"""Test repair for non-existent series returns 404."""
|
|
response = await authenticated_client.post("/api/nfo/nonexistent-key/repair", json={})
|
|
assert response.status_code == 404
|
|
|
|
|
|
class TestNfoValidate:
|
|
"""Tests for GET /api/nfo/{serie_key}/validate."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_validate_requires_auth(self, client):
|
|
"""Test validate endpoint requires authentication."""
|
|
response = await client.get("/api/nfo/test-anime/validate")
|
|
assert response.status_code == 401
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_validate_returns_404_for_nonexistent(
|
|
self, authenticated_client, override_anime_service
|
|
):
|
|
"""Test validate for non-existent series returns 404."""
|
|
response = await authenticated_client.get("/api/nfo/nonexistent-key/validate")
|
|
assert response.status_code == 404 |