Initial commit: Pixel AI comic/video creation platform
- FastAPI backend with SQLModel, Alembic migrations, AgentScope agents - Next.js 15 frontend with React 19, Tailwind, Zustand, React Flow - Multi-provider AI system (DashScope, Kling, MiniMax, Volcengine, OpenAI, etc.) - All HTTP clients migrated from sync requests to async httpx - Admin-managed API keys via environment variables - SSRF vulnerability fixed in ensure_url()
This commit is contained in:
241
backend/tests/test_integration.py
Normal file
241
backend/tests/test_integration.py
Normal file
@@ -0,0 +1,241 @@
|
||||
"""
|
||||
集成测试
|
||||
|
||||
测试完整的请求流程和组件集成
|
||||
"""
|
||||
import pytest
|
||||
import sys
|
||||
import os
|
||||
|
||||
# 添加项目根目录到路径
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
from src.main import app
|
||||
from src.utils.errors import ErrorCode
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def unwrap_response_data(response):
|
||||
payload = response.json()
|
||||
return payload.get("data", payload)
|
||||
|
||||
|
||||
class TestHealthEndpoints:
|
||||
"""健康检查端点测试"""
|
||||
|
||||
def test_basic_health_check(self):
|
||||
"""测试基础健康检查"""
|
||||
response = client.get("/health")
|
||||
assert response.status_code == 200
|
||||
|
||||
data = unwrap_response_data(response)
|
||||
assert data["status"] == "healthy"
|
||||
assert "service" in data
|
||||
assert "timestamp" in data
|
||||
assert "uptime_seconds" in data
|
||||
|
||||
def test_detailed_health_check(self):
|
||||
"""测试详细健康检查"""
|
||||
response = client.get("/health/detailed")
|
||||
assert response.status_code == 200
|
||||
|
||||
data = unwrap_response_data(response)
|
||||
assert "status" in data
|
||||
assert "components" in data
|
||||
assert "database" in data["components"]
|
||||
assert "task_manager" in data["components"]
|
||||
|
||||
def test_liveness_probe(self):
|
||||
"""测试存活探针"""
|
||||
response = client.get("/health/live")
|
||||
assert response.status_code == 200
|
||||
|
||||
data = unwrap_response_data(response)
|
||||
assert data["status"] == "alive"
|
||||
|
||||
def test_readiness_probe(self):
|
||||
"""测试就绪探针"""
|
||||
response = client.get("/health/ready")
|
||||
# 可能返回 200 或 503,取决于组件状态
|
||||
assert response.status_code in [200, 503]
|
||||
|
||||
def test_metrics_endpoint(self):
|
||||
"""测试 Prometheus 指标端点"""
|
||||
response = client.get("/metrics")
|
||||
assert response.status_code == 200
|
||||
assert "text/plain" in response.headers["content-type"]
|
||||
|
||||
# 检查是否包含一些关键指标
|
||||
content = response.text
|
||||
assert "task_created_total" in content or "http_request" in content
|
||||
|
||||
|
||||
class TestErrorHandling:
|
||||
"""错误处理测试"""
|
||||
|
||||
def test_404_not_found(self):
|
||||
"""测试 404 错误"""
|
||||
response = client.get("/api/projects/non-existent-id-12345")
|
||||
# 项目不存在会返回 404(HTTPException)
|
||||
assert response.status_code == 404
|
||||
|
||||
data = response.json()
|
||||
assert "detail" in data or "message" in data
|
||||
|
||||
def test_invalid_parameter(self):
|
||||
"""测试参数验证错误"""
|
||||
# 测试一个需要参数的端点
|
||||
response = client.post("/api/v1/generations/image", json={
|
||||
# 缺少必需的 prompt 参数
|
||||
})
|
||||
|
||||
# 应该返回 422(Pydantic 验证错误)
|
||||
assert response.status_code == 422
|
||||
|
||||
def test_method_not_allowed(self):
|
||||
"""测试方法不允许"""
|
||||
response = client.put("/health") # health 只支持 GET
|
||||
assert response.status_code == 405
|
||||
|
||||
|
||||
class TestConfigEndpoints:
|
||||
"""配置端点测试"""
|
||||
|
||||
def test_get_system_config(self):
|
||||
"""测试获取系统配置"""
|
||||
response = client.get("/api/v1/config/system")
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_get_models_config(self):
|
||||
"""测试获取模型配置"""
|
||||
response = client.get("/api/v1/config/models")
|
||||
assert response.status_code == 200
|
||||
|
||||
payload = unwrap_response_data(response)
|
||||
assert isinstance(payload, dict)
|
||||
|
||||
def test_get_defaults(self):
|
||||
"""测试获取默认模型"""
|
||||
response = client.get("/api/v1/config/defaults")
|
||||
assert response.status_code == 200
|
||||
|
||||
data = response.json()
|
||||
assert "data" in data
|
||||
|
||||
def test_get_styles(self):
|
||||
"""测试获取样式配置"""
|
||||
response = client.get("/api/v1/config/styles")
|
||||
assert response.status_code == 200
|
||||
|
||||
data = unwrap_response_data(response)
|
||||
assert isinstance(data, dict)
|
||||
|
||||
|
||||
class TestTaskEndpoints:
|
||||
"""任务管理端点测试"""
|
||||
|
||||
def test_get_task_stats_from_new_controller(self):
|
||||
"""测试获取任务统计(新的 tasks 控制器)"""
|
||||
# 由于路由冲突,旧的 generations.py 的 /tasks 路由会先匹配
|
||||
# 我们测试新的 tasks 控制器的功能通过直接导入
|
||||
from src.services.task_manager import task_manager
|
||||
stats = task_manager.get_stats()
|
||||
assert "total_tasks" in stats
|
||||
assert "queue_size" in stats
|
||||
|
||||
def test_get_task_from_old_controller(self):
|
||||
"""测试获取任务(旧的 generations 控制器)"""
|
||||
# 旧的 generations.py 控制器有 /tasks 路由
|
||||
response = client.get("/api/v1/tasks")
|
||||
# 应该返回 200(旧控制器的响应)
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_get_nonexistent_task(self):
|
||||
"""测试获取不存在的任务"""
|
||||
response = client.get("/api/v1/tasks/non-existent-task-id-12345")
|
||||
# 可能返回 404 或 200(取决于哪个控制器处理)
|
||||
assert response.status_code in [200, 404]
|
||||
|
||||
|
||||
class TestCanvasEndpoints:
|
||||
"""画布端点测试"""
|
||||
|
||||
def test_get_canvas_default(self):
|
||||
"""测试获取默认画布"""
|
||||
response = client.get("/api/v1/canvas?id=default")
|
||||
assert response.status_code == 200
|
||||
|
||||
data = response.json()
|
||||
assert "data" in data
|
||||
|
||||
def test_save_canvas(self):
|
||||
"""测试保存画布"""
|
||||
canvas_data = {
|
||||
"id": "test-canvas",
|
||||
"projectId": "test-project",
|
||||
"nodes": [],
|
||||
"connections": [],
|
||||
"groups": [],
|
||||
"history": [],
|
||||
"history_index": 0
|
||||
}
|
||||
|
||||
response = client.post("/api/v1/canvas", json=canvas_data)
|
||||
assert response.status_code == 200
|
||||
|
||||
data = response.json()
|
||||
assert "data" in data
|
||||
|
||||
|
||||
class TestPerformanceHeaders:
|
||||
"""性能监控头测试"""
|
||||
|
||||
def test_process_time_header(self):
|
||||
"""测试处理时间头"""
|
||||
response = client.get("/health")
|
||||
assert response.status_code == 200
|
||||
|
||||
# 检查是否有处理时间头
|
||||
assert "X-Process-Time" in response.headers
|
||||
|
||||
# 处理时间应该是一个数字
|
||||
process_time = float(response.headers["X-Process-Time"])
|
||||
assert process_time >= 0
|
||||
|
||||
|
||||
class TestCORS:
|
||||
"""CORS 测试"""
|
||||
|
||||
def test_cors_headers(self):
|
||||
"""测试 CORS 头"""
|
||||
response = client.options("/api/v1/config/system", headers={
|
||||
"Origin": "http://localhost:3000",
|
||||
"Access-Control-Request-Method": "GET"
|
||||
})
|
||||
|
||||
# 检查 CORS 头
|
||||
assert "access-control-allow-origin" in response.headers
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
"""测试 OpenAPI 规范"""
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
|
||||
schema = response.json()
|
||||
assert "openapi" in schema
|
||||
assert "info" in schema
|
||||
assert "paths" in schema
|
||||
|
||||
|
||||
def test_docs_endpoint():
|
||||
"""测试文档端点"""
|
||||
response = client.get("/docs")
|
||||
assert response.status_code == 200
|
||||
assert "text/html" in response.headers["content-type"]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v", "--tb=short"])
|
||||
Reference in New Issue
Block a user