fix tests

This commit is contained in:
Lukas 2025-11-19 21:20:22 +01:00
parent 17c7a2e295
commit b1f4d41b27
8 changed files with 234 additions and 136 deletions

View File

@ -17,7 +17,7 @@
"keep_days": 30 "keep_days": 30
}, },
"other": { "other": {
"master_password_hash": "$pbkdf2-sha256$29000$SKlVihGiVIpR6v1fi9H6Xw$rElvHKWqc8WesNfrOJe4CjQI2janLKJPSy6XSOnkq2c" "master_password_hash": "$pbkdf2-sha256$29000$sRYC4DzH.L9Xao3xXuudMw$3OJi3MXsfV3fXaW9bxtssdVc6BUmkV7i1Ww5FxWKOnM"
}, },
"version": "1.0.0" "version": "1.0.0"
} }

File diff suppressed because one or more lines are too long

View File

@ -3,24 +3,25 @@ Health check controller for monitoring and status endpoints.
This module provides health check endpoints for application monitoring. This module provides health check endpoints for application monitoring.
""" """
from typing import Optional from fastapi import APIRouter
from fastapi import APIRouter, Depends from src.config.settings import settings
from src.server.utils.dependencies import _series_app
from src.core.SeriesApp import SeriesApp
from src.server.utils.dependencies import get_series_app
router = APIRouter(prefix="/health", tags=["health"]) router = APIRouter(prefix="/health", tags=["health"])
@router.get("") @router.get("")
async def health_check( async def health_check():
series_app: Optional[SeriesApp] = Depends(get_series_app) """Health check endpoint for monitoring.
):
"""Health check endpoint for monitoring.""" This endpoint does not depend on anime_directory configuration
and should always return 200 OK for basic health monitoring.
"""
return { return {
"status": "healthy", "status": "healthy",
"service": "aniworld-api", "service": "aniworld-api",
"version": "1.0.0", "version": "1.0.0",
"series_app_initialized": series_app is not None "series_app_initialized": _series_app is not None,
"anime_directory_configured": bool(settings.anime_directory)
} }

View File

@ -442,15 +442,18 @@ class TestFrontendJavaScriptIntegration:
async def test_queue_operations_compatibility(self, authenticated_client): async def test_queue_operations_compatibility(self, authenticated_client):
"""Test queue operations match queue.js expectations.""" """Test queue operations match queue.js expectations."""
# Test start # Test start - should return 400 when queue is empty (valid behavior)
response = await authenticated_client.post("/api/queue/start") response = await authenticated_client.post("/api/queue/start")
assert response.status_code == 200 assert response.status_code in [200, 400]
if response.status_code == 400:
# Verify error message indicates empty queue
assert "No pending downloads" in response.json()["detail"]
# Test pause # Test pause - always succeeds even if nothing is processing
response = await authenticated_client.post("/api/queue/pause") response = await authenticated_client.post("/api/queue/pause")
assert response.status_code == 200 assert response.status_code == 200
# Test stop # Test stop - always succeeds even if nothing is processing
response = await authenticated_client.post("/api/queue/stop") response = await authenticated_client.post("/api/queue/stop")
assert response.status_code == 200 assert response.status_code == 200

View File

@ -26,11 +26,33 @@ from src.server.models.download import (
) )
from src.server.services.anime_service import AnimeService from src.server.services.anime_service import AnimeService
from src.server.services.auth_service import auth_service from src.server.services.auth_service import auth_service
from src.server.services.config_service import get_config_service
from src.server.services.download_service import DownloadService from src.server.services.download_service import DownloadService
from src.server.services.progress_service import get_progress_service from src.server.services.progress_service import get_progress_service
from src.server.services.websocket_service import get_websocket_service from src.server.services.websocket_service import get_websocket_service
@pytest.fixture(autouse=True)
def setup_temp_config(tmp_path):
"""Setup temporary config directory for tests."""
config_service = get_config_service()
original_path = config_service.config_path
original_backup_dir = config_service.backup_dir
# Set temporary paths
temp_data = tmp_path / "data"
temp_data.mkdir(exist_ok=True)
config_service.config_path = temp_data / "config.json"
config_service.backup_dir = temp_data / "config_backups"
config_service.backup_dir.mkdir(exist_ok=True)
yield
# Restore original paths
config_service.config_path = original_path
config_service.backup_dir = original_backup_dir
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def reset_auth(): def reset_auth():
"""Reset authentication state before each test.""" """Reset authentication state before each test."""

View File

@ -8,6 +8,7 @@ concurrent requests and maintain acceptable response times.
import asyncio import asyncio
import time import time
from typing import Any, Dict, List from typing import Any, Dict, List
from unittest.mock import AsyncMock, MagicMock
import pytest import pytest
from httpx import ASGITransport, AsyncClient from httpx import ASGITransport, AsyncClient
@ -20,6 +21,22 @@ from src.server.fastapi_app import app
class TestAPILoadTesting: class TestAPILoadTesting:
"""Load testing for API endpoints.""" """Load testing for API endpoints."""
@pytest.fixture(autouse=True)
def mock_series_app_dependency(self):
"""Mock SeriesApp dependency for performance tests."""
from src.server.utils.dependencies import get_series_app
mock_app = MagicMock()
mock_app.list = MagicMock()
mock_app.list.GetMissingEpisode = MagicMock(return_value=[])
mock_app.search = AsyncMock(return_value=[])
app.dependency_overrides[get_series_app] = lambda: mock_app
yield
app.dependency_overrides.clear()
@pytest.fixture @pytest.fixture
async def client(self): async def client(self):
"""Create async HTTP client.""" """Create async HTTP client."""

View File

@ -17,12 +17,28 @@ class TestSQLInjection:
@pytest.fixture @pytest.fixture
async def client(self): async def client(self):
"""Create async HTTP client for testing.""" """Create async HTTP client for testing."""
from unittest.mock import AsyncMock, MagicMock
from httpx import ASGITransport from httpx import ASGITransport
from src.server.utils.dependencies import get_series_app
# Mock SeriesApp to avoid 503 errors
mock_app = MagicMock()
mock_app.list = MagicMock()
mock_app.list.GetMissingEpisode = MagicMock(return_value=[])
mock_app.search = AsyncMock(return_value=[])
# Override dependency
app.dependency_overrides[get_series_app] = lambda: mock_app
async with AsyncClient( async with AsyncClient(
transport=ASGITransport(app=app), base_url="http://test" transport=ASGITransport(app=app), base_url="http://test"
) as ac: ) as ac:
yield ac yield ac
# Cleanup
app.dependency_overrides.clear()
# Classic SQL Injection payloads # Classic SQL Injection payloads
SQL_INJECTION_PAYLOADS = [ SQL_INJECTION_PAYLOADS = [
@ -138,12 +154,28 @@ class TestNoSQLInjection:
@pytest.fixture @pytest.fixture
async def client(self): async def client(self):
"""Create async HTTP client for testing.""" """Create async HTTP client for testing."""
from unittest.mock import AsyncMock, MagicMock
from httpx import ASGITransport from httpx import ASGITransport
from src.server.utils.dependencies import get_series_app
# Mock SeriesApp to avoid 503 errors
mock_app = MagicMock()
mock_app.list = MagicMock()
mock_app.list.GetMissingEpisode = MagicMock(return_value=[])
mock_app.search = AsyncMock(return_value=[])
# Override dependency
app.dependency_overrides[get_series_app] = lambda: mock_app
async with AsyncClient( async with AsyncClient(
transport=ASGITransport(app=app), base_url="http://test" transport=ASGITransport(app=app), base_url="http://test"
) as ac: ) as ac:
yield ac yield ac
# Cleanup
app.dependency_overrides.clear()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_nosql_injection_in_query(self, client): async def test_nosql_injection_in_query(self, client):
@ -240,17 +272,33 @@ class TestORMInjection:
@pytest.mark.security @pytest.mark.security
class TestDatabaseSecurity: class TestDatabaseSecurity:
"""General database security tests.""" """Security tests for database access patterns."""
@pytest.fixture @pytest.fixture
async def client(self): async def client(self):
"""Create async HTTP client for testing.""" """Create async HTTP client for testing."""
from unittest.mock import AsyncMock, MagicMock
from httpx import ASGITransport from httpx import ASGITransport
from src.server.utils.dependencies import get_series_app
# Mock SeriesApp to avoid 503 errors
mock_app = MagicMock()
mock_app.list = MagicMock()
mock_app.list.GetMissingEpisode = MagicMock(return_value=[])
mock_app.search = AsyncMock(return_value=[])
# Override dependency
app.dependency_overrides[get_series_app] = lambda: mock_app
async with AsyncClient( async with AsyncClient(
transport=ASGITransport(app=app), base_url="http://test" transport=ASGITransport(app=app), base_url="http://test"
) as ac: ) as ac:
yield ac yield ac
# Cleanup
app.dependency_overrides.clear()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_error_messages_no_leak_info(self, client): async def test_error_messages_no_leak_info(self, client):

View File

@ -1,8 +1,7 @@
"""Unit tests for setup redirect middleware.""" """Unit tests for setup redirect middleware."""
import pytest import pytest
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.testclient import TestClient from httpx import ASGITransport, AsyncClient
from starlette.responses import JSONResponse
from src.server.middleware.setup_redirect import SetupRedirectMiddleware from src.server.middleware.setup_redirect import SetupRedirectMiddleware
from src.server.services.auth_service import auth_service from src.server.services.auth_service import auth_service
@ -46,9 +45,11 @@ def app():
@pytest.fixture @pytest.fixture
def client(app): async def client(app):
"""Create a test client.""" """Create an async test client."""
return TestClient(app) transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as ac:
yield ac
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
@ -95,10 +96,11 @@ def reset_config_service():
class TestSetupRedirectMiddleware: class TestSetupRedirectMiddleware:
"""Test cases for setup redirect middleware.""" """Test cases for setup redirect middleware."""
def test_redirect_to_setup_when_not_configured(self, client): @pytest.mark.asyncio
"""Test that HTML requests are redirected to /setup when not configured.""" async def test_redirect_to_setup_when_not_configured(self, client):
"""Test that HTML requests redirect to /setup when not configured."""
# Request home page with HTML accept header (don't follow redirects) # Request home page with HTML accept header (don't follow redirects)
response = client.get( response = await client.get(
"/", headers={"Accept": "text/html"}, follow_redirects=False "/", headers={"Accept": "text/html"}, follow_redirects=False
) )
@ -106,36 +108,40 @@ class TestSetupRedirectMiddleware:
assert response.status_code == 302 assert response.status_code == 302
assert response.headers["location"] == "/setup" assert response.headers["location"] == "/setup"
def test_setup_page_accessible_without_config(self, client): @pytest.mark.asyncio
"""Test that /setup page is accessible even when not configured.""" async def test_setup_page_accessible_without_config(self, client):
response = client.get("/setup") """Test that /setup page is accessible when not configured."""
response = await client.get("/setup")
# Should not redirect # Should not redirect
assert response.status_code == 200 assert response.status_code == 200
assert response.json()["message"] == "Setup page" assert response.json()["message"] == "Setup page"
def test_api_returns_503_when_not_configured(self, client): @pytest.mark.asyncio
async def test_api_returns_503_when_not_configured(self, client):
"""Test that API requests return 503 when not configured.""" """Test that API requests return 503 when not configured."""
response = client.get("/api/data") response = await client.get("/api/data")
# Should return 503 Service Unavailable # Should return 503 Service Unavailable
assert response.status_code == 503 assert response.status_code == 503
assert "setup_url" in response.json() assert "setup_url" in response.json()
assert response.json()["setup_url"] == "/setup" assert response.json()["setup_url"] == "/setup"
def test_exempt_api_endpoints_accessible(self, client): @pytest.mark.asyncio
async def test_exempt_api_endpoints_accessible(self, client):
"""Test that exempt API endpoints are accessible without setup.""" """Test that exempt API endpoints are accessible without setup."""
# Health endpoint should be accessible # Health endpoint should be accessible
response = client.get("/api/health") response = await client.get("/api/health")
assert response.status_code == 200 assert response.status_code == 200
assert response.json()["status"] == "ok" assert response.json()["status"] == "ok"
# Auth status endpoint should be accessible # Auth status endpoint should be accessible
response = client.get("/api/auth/status") response = await client.get("/api/auth/status")
assert response.status_code == 200 assert response.status_code == 200
assert response.json()["configured"] is False assert response.json()["configured"] is False
def test_no_redirect_when_configured(self, client): @pytest.mark.asyncio
async def test_no_redirect_when_configured(self, client):
"""Test that no redirect happens when auth and config are set up.""" """Test that no redirect happens when auth and config are set up."""
# Configure auth service # Configure auth service
auth_service.setup_master_password("Test@Password123") auth_service.setup_master_password("Test@Password123")
@ -147,13 +153,14 @@ class TestSetupRedirectMiddleware:
config_service.save_config(config, create_backup=False) config_service.save_config(config, create_backup=False)
# Request home page # Request home page
response = client.get("/", headers={"Accept": "text/html"}) response = await client.get("/", headers={"Accept": "text/html"})
# Should not redirect # Should not redirect
assert response.status_code == 200 assert response.status_code == 200
assert response.json()["message"] == "Home page" assert response.json()["message"] == "Home page"
def test_api_works_when_configured(self, client): @pytest.mark.asyncio
async def test_api_works_when_configured(self, client):
"""Test that API requests work normally when configured.""" """Test that API requests work normally when configured."""
# Configure auth service # Configure auth service
auth_service.setup_master_password("Test@Password123") auth_service.setup_master_password("Test@Password123")
@ -165,44 +172,44 @@ class TestSetupRedirectMiddleware:
config_service.save_config(config, create_backup=False) config_service.save_config(config, create_backup=False)
# Request API endpoint # Request API endpoint
response = client.get("/api/data") response = await client.get("/api/data")
# Should work normally # Should work normally
assert response.status_code == 200 assert response.status_code == 200
assert response.json()["data"] == "some data" assert response.json()["data"] == "some data"
def test_static_files_always_accessible(self, client): @pytest.mark.asyncio
async def test_static_files_always_accessible(self, client, app):
"""Test that static file paths are always accessible.""" """Test that static file paths are always accessible."""
# Create a route that mimics static file serving # Create a route that mimics static file serving
from fastapi import FastAPI
app = client.app
@app.get("/static/css/style.css") @app.get("/static/css/style.css")
async def static_css(): async def static_css():
return {"content": "css"} return {"content": "css"}
# Request static file # Request static file
response = client.get("/static/css/style.css") response = await client.get("/static/css/style.css")
# Should be accessible even without setup # Should be accessible even without setup
assert response.status_code == 200 assert response.status_code == 200
def test_redirect_when_only_auth_configured(self, client): @pytest.mark.asyncio
async def test_redirect_when_only_auth_configured(self, client):
"""Test redirect when auth is configured but config is invalid.""" """Test redirect when auth is configured but config is invalid."""
# Configure auth but don't create config file # Configure auth but don't create config file
auth_service.setup_master_password("Test@Password123") auth_service.setup_master_password("Test@Password123")
# Request home page # Request home page
response = client.get("/", headers={"Accept": "text/html"}) response = await client.get("/", headers={"Accept": "text/html"})
# Should still work because load_config creates default config # Should still work because load_config creates default config
# This is the current behavior - may need to adjust if we want # This is the current behavior - may need to adjust if we want
# stricter setup requirements # stricter setup requirements
assert response.status_code in [200, 302] assert response.status_code in [200, 302]
def test_root_path_redirect(self, client): @pytest.mark.asyncio
async def test_root_path_redirect(self, client):
"""Test that root path redirects to setup when not configured.""" """Test that root path redirects to setup when not configured."""
response = client.get( response = await client.get(
"/", headers={"Accept": "text/html"}, follow_redirects=False "/", headers={"Accept": "text/html"}, follow_redirects=False
) )
@ -210,8 +217,8 @@ class TestSetupRedirectMiddleware:
assert response.status_code == 302 assert response.status_code == 302
assert response.headers["location"] == "/setup" assert response.headers["location"] == "/setup"
def test_path_matching_exact_and_prefix(self, client): def test_path_matching_exact_and_prefix(self):
"""Test that path matching works for both exact and prefix matches.""" """Test that path matching works for both exact and prefix."""
middleware = SetupRedirectMiddleware(app=FastAPI()) middleware = SetupRedirectMiddleware(app=FastAPI())
# Exact matches # Exact matches