refactor: overhaul NFO settings UI and backend

- Rework nfo-settings page with improved styling and layout
- Update edit-modal and context-menu with enhanced functionality
- Refactor NFO API endpoints and models
- Remove deprecated test_nfo_diagnostics_repair.py
- Clean up tasks.md documentation
This commit is contained in:
2026-06-20 20:32:46 +02:00
parent 7a1b2e565e
commit a8e54876e3
11 changed files with 2107 additions and 1708 deletions

View File

@@ -1,156 +0,0 @@
"""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