video-compliance-ai/backend/tests/test_health_integration.py
Your Name e4959d584f feat: 完善代理商端业务逻辑与前后端框架
主要更新:
- 更新代理商端文档,明确项目由品牌方分配流程
- 新增Brief配置详情页(已配置)设计稿
- 完善工作台紧急待办中品牌新任务功能
- 整理Pencil设计文件中代理商端页面顺序
- 新增后端FastAPI框架及核心API
- 新增前端Next.js页面和组件库
- 添加.gitignore排除构建和缓存文件

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 19:27:31 +08:00

242 lines
8.7 KiB
Python
Raw 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 集成测试
使用 testcontainers 运行真实 PostgreSQL 和 Redis
运行: pytest tests/test_health_integration.py -m integration
"""
import pytest
from httpx import AsyncClient, ASGITransport
from sqlalchemy import text
from sqlalchemy.ext.asyncio import create_async_engine
from app.main import app
from app.services.health import get_health_checker, DefaultHealthChecker
class RealHealthChecker:
"""
真实健康检查实现(用于集成测试)
正确处理资源释放,支持连接超时配置
"""
# 测试用短超时(秒),避免无效主机导致长时间等待
DEFAULT_CONNECT_TIMEOUT = 2
def __init__(self, db_url: str, redis_url: str, connect_timeout: float = DEFAULT_CONNECT_TIMEOUT):
self._db_url = db_url
self._redis_url = redis_url
self._connect_timeout = connect_timeout
async def check_database(self) -> bool:
"""检查数据库连接(确保资源释放)"""
engine = None
try:
engine = create_async_engine(
self._db_url,
connect_args={"timeout": self._connect_timeout}
)
async with engine.connect() as conn:
await conn.execute(text("SELECT 1"))
return True
except Exception:
return False
finally:
# 确保 engine 被正确释放
if engine is not None:
await engine.dispose()
async def check_redis(self) -> bool:
"""检查 Redis 连接(确保资源释放)"""
client = None
try:
import redis.asyncio as aioredis
client = aioredis.from_url(
self._redis_url,
socket_connect_timeout=self._connect_timeout
)
await client.ping()
return True
except Exception:
return False
finally:
# 确保 client 被正确释放
if client is not None:
try:
await client.aclose()
except Exception:
pass
async def check_all(self) -> dict[str, bool]:
"""检查所有依赖"""
return {
"database": await self.check_database(),
"redis": await self.check_redis(),
}
@pytest.mark.integration
class TestHealthCheckIntegration:
"""健康检查集成测试(需要 Docker"""
@pytest.mark.asyncio
async def test_readiness_with_real_postgres(self, postgres_container):
"""使用真实 PostgreSQL 测试就绪检查"""
# 获取容器连接信息
host = postgres_container.get_container_host_ip()
port = postgres_container.get_exposed_port(5432)
db_url = f"postgresql+asyncpg://test:test@{host}:{port}/test"
# 创建真实健康检查器
checker = RealHealthChecker(db_url=db_url, redis_url="redis://invalid:6379")
# 注入到 app
app.dependency_overrides[get_health_checker] = lambda: checker
try:
transport = ASGITransport(app=app, raise_app_exceptions=False)
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.get("/api/v1/health/ready")
data = response.json()
# 数据库应该健康
assert data["checks"]["database"] is True
# Redis 连接失败(无效地址)
assert data["checks"]["redis"] is False
# 整体不就绪
assert data["ready"] is False
finally:
app.dependency_overrides.clear()
@pytest.mark.asyncio
async def test_readiness_with_real_redis(self, redis_container):
"""使用真实 Redis 测试就绪检查"""
# 获取容器连接信息
host = redis_container.get_container_host_ip()
port = redis_container.get_exposed_port(6379)
redis_url = f"redis://{host}:{port}"
# 创建真实健康检查器
checker = RealHealthChecker(
db_url="postgresql+asyncpg://invalid:invalid@invalid:5432/invalid",
redis_url=redis_url
)
# 注入到 app
app.dependency_overrides[get_health_checker] = lambda: checker
try:
transport = ASGITransport(app=app, raise_app_exceptions=False)
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.get("/api/v1/health/ready")
data = response.json()
# 数据库连接失败(无效地址)
assert data["checks"]["database"] is False
# Redis 应该健康
assert data["checks"]["redis"] is True
# 整体不就绪
assert data["ready"] is False
finally:
app.dependency_overrides.clear()
@pytest.mark.asyncio
async def test_readiness_with_all_real_deps(
self, postgres_container, redis_container
):
"""使用真实 PostgreSQL 和 Redis 测试就绪检查"""
# PostgreSQL 连接信息
pg_host = postgres_container.get_container_host_ip()
pg_port = postgres_container.get_exposed_port(5432)
db_url = f"postgresql+asyncpg://test:test@{pg_host}:{pg_port}/test"
# Redis 连接信息
redis_host = redis_container.get_container_host_ip()
redis_port = redis_container.get_exposed_port(6379)
redis_url = f"redis://{redis_host}:{redis_port}"
# 创建真实健康检查器
checker = RealHealthChecker(db_url=db_url, redis_url=redis_url)
# 注入到 app
app.dependency_overrides[get_health_checker] = lambda: checker
try:
transport = ASGITransport(app=app, raise_app_exceptions=False)
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.get("/api/v1/health/ready")
data = response.json()
# 所有依赖应该健康
assert data["checks"]["database"] is True
assert data["checks"]["redis"] is True
# 整体就绪
assert data["ready"] is True
finally:
app.dependency_overrides.clear()
@pytest.mark.integration
class TestDatabaseConnectionIntegration:
"""数据库连接集成测试"""
@pytest.mark.asyncio
async def test_database_query_execution(self, postgres_container):
"""测试真实数据库查询执行"""
host = postgres_container.get_container_host_ip()
port = postgres_container.get_exposed_port(5432)
db_url = f"postgresql+asyncpg://test:test@{host}:{port}/test"
engine = create_async_engine(db_url)
try:
async with engine.connect() as conn:
result = await conn.execute(text("SELECT 1 as value"))
row = result.fetchone()
assert row is not None
assert row[0] == 1
finally:
await engine.dispose()
@pytest.mark.asyncio
async def test_database_connection_failure(self):
"""测试数据库连接失败场景"""
invalid_url = "postgresql+asyncpg://invalid:invalid@invalid:5432/invalid"
checker = RealHealthChecker(db_url=invalid_url, redis_url="redis://invalid:6379")
result = await checker.check_database()
assert result is False
@pytest.mark.integration
class TestDefaultHealthCheckerIntegration:
"""DefaultHealthChecker 集成测试"""
@pytest.mark.asyncio
async def test_default_checker_with_real_postgres(self, postgres_container):
"""测试 DefaultHealthChecker 使用真实 PostgreSQL"""
host = postgres_container.get_container_host_ip()
port = postgres_container.get_exposed_port(5432)
db_url = f"postgresql+asyncpg://test:test@{host}:{port}/test"
engine = create_async_engine(db_url)
try:
# 使用短超时避免无效主机长时间等待
checker = DefaultHealthChecker(
db_engine=engine,
redis_url="redis://invalid:6379",
connect_timeout=2
)
result = await checker.check_database()
assert result is True
finally:
await engine.dispose()
@pytest.mark.asyncio
async def test_default_checker_with_real_redis(self, redis_container):
"""测试 DefaultHealthChecker 使用真实 Redis"""
host = redis_container.get_container_host_ip()
port = redis_container.get_exposed_port(6379)
redis_url = f"redis://{host}:{port}"
checker = DefaultHealthChecker(db_engine=None, redis_url=redis_url)
result = await checker.check_redis()
assert result is True