geo/backend/tests/test_api/test_health_api.py

205 lines
6.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""健康检查API测试"""
import pytest
import pytest_asyncio
from httpx import AsyncClient, ASGITransport
from sqlalchemy.ext.asyncio import async_sessionmaker, AsyncSession, create_async_engine
from sqlalchemy.pool import StaticPool
from app.database import Base
from app.main import app
from app.api.deps import get_db
# ==================== Fixtures ====================
@pytest_asyncio.fixture
async def async_engine():
"""创建测试用SQLite异步引擎"""
engine = create_async_engine(
"sqlite+aiosqlite:///:memory:",
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
yield engine
await engine.dispose()
@pytest_asyncio.fixture
async def async_session(async_engine):
"""创建测试用异步数据库会话"""
async_session_maker = async_sessionmaker(
async_engine,
class_=AsyncSession,
expire_on_commit=False,
autoflush=False,
autocommit=False,
)
async with async_session_maker() as session:
yield session
@pytest_asyncio.fixture
async def async_client(async_session):
"""创建异步HTTP客户端用于API测试"""
async def override_get_db():
yield async_session
app.dependency_overrides[get_db] = override_get_db
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
yield client
app.dependency_overrides.clear()
# ==================== 测试类 ====================
class TestHealthAPI:
"""健康检查API测试"""
@pytest.mark.asyncio
async def test_basic_health(self):
"""测试基本健康检查"""
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.get("/health")
assert response.status_code == 200
data = response.json()
assert data["status"] == "healthy"
assert "timestamp" in data
@pytest.mark.asyncio
async def test_liveness(self):
"""测试存活探针"""
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.get("/health/liveness")
assert response.status_code == 200
data = response.json()
assert data["status"] == "alive"
@pytest.mark.asyncio
async def test_ready_endpoint(self):
"""测试就绪端点"""
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.get("/ready")
# 返回200或503取决于依赖服务状态
assert response.status_code in [200, 503]
data = response.json()
assert "status" in data
assert "checks" in data
assert "database" in data["checks"]
assert "redis" in data["checks"]
@pytest.mark.asyncio
async def test_metrics(self):
"""测试Prometheus指标端点"""
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.get("/metrics")
assert response.status_code == 200
# Prometheus指标返回文本格式
assert "text/plain" in response.headers["content-type"] or \
"text/plain" in str(response.headers.get("content-type", ""))
@pytest.mark.asyncio
async def test_detailed_health(self, async_client):
"""测试详细健康检查"""
response = await async_client.get("/health/detailed")
assert response.status_code == 200
data = response.json()
# 检查返回结构
assert "checks" in data
assert "app" in data
@pytest.mark.asyncio
async def test_readiness_probe(self, async_client):
"""测试就绪探针"""
response = await async_client.get("/health/readiness")
# 健康状态应该是200unhealthy是503
assert response.status_code in [200, 503]
data = response.json()
assert "status" in data
assert "checks" in data
class TestHealthEndpointsStructure:
"""健康检查端点结构测试"""
@pytest.mark.asyncio
async def test_health_endpoint_returns_json(self):
"""测试健康端点返回JSON格式"""
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.get("/health")
assert response.status_code == 200
assert "application/json" in response.headers.get("content-type", "")
@pytest.mark.asyncio
async def test_detailed_health_has_required_fields(self, async_client):
"""测试详细健康检查包含必需字段"""
response = await async_client.get("/health/detailed")
assert response.status_code == 200
data = response.json()
# 检查app信息
if "app" in data:
app_info = data["app"]
assert isinstance(app_info, dict)
@pytest.mark.asyncio
async def test_ready_checks_database_and_redis(self, async_client):
"""测试就绪检查包含数据库和Redis检查"""
response = await async_client.get("/ready")
data = response.json()
checks = data.get("checks", {})
assert "database" in checks
assert "redis" in checks
class TestHealthCheckIndependence:
"""健康检查独立性测试"""
@pytest.mark.asyncio
async def test_health_no_auth_required(self):
"""测试健康检查不需要认证"""
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
# 不带token访问
response = await client.get("/health")
assert response.status_code == 200
@pytest.mark.asyncio
async def test_liveness_no_auth_required(self):
"""测试存活探针不需要认证"""
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.get("/health/liveness")
assert response.status_code == 200
@pytest.mark.asyncio
async def test_metrics_no_auth_required(self):
"""测试指标端点不需要认证"""
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.get("/metrics")
assert response.status_code == 200