fix tests
This commit is contained in:
parent
17c7a2e295
commit
b1f4d41b27
@ -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
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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."""
|
||||||
|
|||||||
@ -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."""
|
||||||
|
|||||||
@ -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):
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user