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
},
"other": {
"master_password_hash": "$pbkdf2-sha256$29000$SKlVihGiVIpR6v1fi9H6Xw$rElvHKWqc8WesNfrOJe4CjQI2janLKJPSy6XSOnkq2c"
"master_password_hash": "$pbkdf2-sha256$29000$sRYC4DzH.L9Xao3xXuudMw$3OJi3MXsfV3fXaW9bxtssdVc6BUmkV7i1Ww5FxWKOnM"
},
"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.
"""
from typing import Optional
from fastapi import APIRouter
from fastapi import APIRouter, Depends
from src.core.SeriesApp import SeriesApp
from src.server.utils.dependencies import get_series_app
from src.config.settings import settings
from src.server.utils.dependencies import _series_app
router = APIRouter(prefix="/health", tags=["health"])
@router.get("")
async def health_check(
series_app: Optional[SeriesApp] = Depends(get_series_app)
):
"""Health check endpoint for monitoring."""
async def health_check():
"""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 {
"status": "healthy",
"service": "aniworld-api",
"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):
"""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")
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")
assert response.status_code == 200
# Test stop
# Test stop - always succeeds even if nothing is processing
response = await authenticated_client.post("/api/queue/stop")
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.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.progress_service import get_progress_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)
def reset_auth():
"""Reset authentication state before each test."""

View File

@ -8,6 +8,7 @@ concurrent requests and maintain acceptable response times.
import asyncio
import time
from typing import Any, Dict, List
from unittest.mock import AsyncMock, MagicMock
import pytest
from httpx import ASGITransport, AsyncClient
@ -20,6 +21,22 @@ from src.server.fastapi_app import app
class TestAPILoadTesting:
"""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
async def client(self):
"""Create async HTTP client."""

View File

@ -17,12 +17,28 @@ class TestSQLInjection:
@pytest.fixture
async def client(self):
"""Create async HTTP client for testing."""
from unittest.mock import AsyncMock, MagicMock
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(
transport=ASGITransport(app=app), base_url="http://test"
) as ac:
yield ac
# Cleanup
app.dependency_overrides.clear()
# Classic SQL Injection payloads
SQL_INJECTION_PAYLOADS = [
@ -138,12 +154,28 @@ class TestNoSQLInjection:
@pytest.fixture
async def client(self):
"""Create async HTTP client for testing."""
from unittest.mock import AsyncMock, MagicMock
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(
transport=ASGITransport(app=app), base_url="http://test"
) as ac:
yield ac
# Cleanup
app.dependency_overrides.clear()
@pytest.mark.asyncio
async def test_nosql_injection_in_query(self, client):
@ -240,17 +272,33 @@ class TestORMInjection:
@pytest.mark.security
class TestDatabaseSecurity:
"""General database security tests."""
"""Security tests for database access patterns."""
@pytest.fixture
async def client(self):
"""Create async HTTP client for testing."""
from unittest.mock import AsyncMock, MagicMock
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(
transport=ASGITransport(app=app), base_url="http://test"
) as ac:
yield ac
# Cleanup
app.dependency_overrides.clear()
@pytest.mark.asyncio
async def test_error_messages_no_leak_info(self, client):

View File

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