peijun1's picture
Deploy AI Studio Proxy API to Hugging Face Spaces
a5784e9
Raw
History Blame Contribute Delete
7.17 kB
"""
High-quality tests for api_utils/routers/static.py - Static file serving.
Focus: Test static file endpoints with both success and error paths.
Strategy: Mock Path.exists() to control file existence, test actual routing logic.
"""
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
from fastapi import HTTPException
class TestReadIndex:
"""Tests for read_index endpoint."""
@pytest.mark.asyncio
async def test_read_index_react_exists(self):
"""
Test scenario: React index.html exists
Expected: Return FileResponse with React index.html
"""
from api_utils.routers.static import read_index
mock_logger = MagicMock()
with patch.object(Path, "exists", return_value=True):
response = await read_index(logger=mock_logger)
assert response is not None
@pytest.mark.asyncio
async def test_read_index_not_built(self):
"""
Test scenario: React build does not exist
Expected: Return 503 error (Service Unavailable)
"""
from api_utils.routers.static import read_index
mock_logger = MagicMock()
with patch.object(Path, "exists", return_value=False):
with pytest.raises(HTTPException) as exc_info:
await read_index(logger=mock_logger)
assert exc_info.value.status_code == 503
assert "Frontend not built" in exc_info.value.detail
mock_logger.error.assert_called_once()
class TestServeReactAssets:
"""Tests for serve_react_assets endpoint."""
@pytest.mark.asyncio
async def test_serve_react_assets_js(self):
"""
Test scenario: JS asset exists
Expected: Return FileResponse with application/javascript media type
"""
from api_utils.routers.static import serve_react_assets
mock_logger = MagicMock()
with patch.object(Path, "exists", return_value=True):
response = await serve_react_assets("main.js", logger=mock_logger)
assert response is not None
assert response.media_type == "application/javascript"
@pytest.mark.asyncio
async def test_serve_react_assets_css(self):
"""
Test scenario: CSS asset exists
Expected: Return FileResponse with text/css media type
"""
from api_utils.routers.static import serve_react_assets
mock_logger = MagicMock()
with patch.object(Path, "exists", return_value=True):
response = await serve_react_assets("style.css", logger=mock_logger)
assert response is not None
assert response.media_type == "text/css"
@pytest.mark.asyncio
async def test_serve_react_assets_map(self):
"""
Test scenario: Source map asset exists
Expected: Return FileResponse with application/json media type
"""
from api_utils.routers.static import serve_react_assets
mock_logger = MagicMock()
with patch.object(Path, "exists", return_value=True):
response = await serve_react_assets("main.js.map", logger=mock_logger)
assert response is not None
assert response.media_type == "application/json"
@pytest.mark.asyncio
async def test_serve_react_assets_not_found(self):
"""
Test scenario: Asset not found
Expected: Return 404 error
"""
from api_utils.routers.static import serve_react_assets
mock_logger = MagicMock()
with patch.object(Path, "exists", return_value=False):
with pytest.raises(HTTPException) as exc_info:
await serve_react_assets("missing.js", logger=mock_logger)
assert exc_info.value.status_code == 404
assert "missing.js" in exc_info.value.detail
mock_logger.debug.assert_called_once()
class TestGetStaticFilesApp:
"""Tests for get_static_files_app factory function."""
def test_get_static_files_app_exists(self):
"""
Test scenario: Assets directory exists
Expected: Return StaticFiles instance
"""
from api_utils.routers.static import get_static_files_app
with (
patch("api_utils.routers.static.Path.exists", return_value=True),
patch("os.path.isdir", return_value=True),
):
result = get_static_files_app()
assert result is not None
def test_get_static_files_app_not_exists(self):
"""
Test scenario: Assets directory does not exist
Expected: Return None
"""
from api_utils.routers.static import get_static_files_app
with patch.object(Path, "exists", return_value=False):
result = get_static_files_app()
assert result is None
class TestDirectoryTraversalProtection:
"""Tests for directory traversal attack prevention."""
@pytest.mark.asyncio
async def test_serve_react_assets_traversal_blocked(self):
"""
Test scenario: Attempt directory traversal attack
Expected: Return 403 error
"""
from api_utils.routers.static import serve_react_assets
mock_logger = MagicMock()
# First mock: file exists, second mock for resolve().relative_to() failure
with patch.object(Path, "exists", return_value=True):
# When the resolved path is outside assets dir, relative_to raises ValueError
with patch.object(Path, "resolve") as mock_resolve:
mock_resolved = MagicMock()
mock_resolved.relative_to.side_effect = ValueError(
"Not a relative path"
)
mock_resolve.return_value = mock_resolved
with pytest.raises(HTTPException) as exc_info:
await serve_react_assets("../../../etc/passwd", logger=mock_logger)
assert exc_info.value.status_code == 403
assert "Access denied" in exc_info.value.detail
mock_logger.warning.assert_called_once()
@pytest.mark.asyncio
async def test_serve_react_assets_additional_mime_types(self):
"""
Test scenario: Additional MIME type support
Expected: Correctly identify more file types
"""
from api_utils.routers.static import serve_react_assets
mock_logger = MagicMock()
test_cases = [
("image.svg", "image/svg+xml"),
("image.png", "image/png"),
("font.woff2", "font/woff2"),
]
for filename, expected_media_type in test_cases:
with patch.object(Path, "exists", return_value=True):
with patch.object(Path, "resolve") as mock_resolve:
mock_resolved = MagicMock()
mock_resolved.relative_to.return_value = Path(filename)
mock_resolve.return_value = mock_resolved
response = await serve_react_assets(filename, logger=mock_logger)
assert response is not None
assert response.media_type == expected_media_type