chore: 初始化项目结构与登录鉴权模块
后端 FastAPI 六层结构(api/services/repositories/models/schemas/core), 统一响应信封、全局异常、日志脱敏;6 张表经 Alembic 迁移建库,SQL 留存 db/migration;登录鉴权含图形验证码(Redis GETDEL 防重放)、JWT、登录限流、 登出黑名单(TTL=令牌剩余有效期)、登录日志。 前端 Vue3 + TS + Element Plus 骨架,含登录页、路由守卫、请求封装与 Vite 代理。 工程配置:uv 锁定 Python 3.12、ruff 检查与格式化、editorconfig、gitattributes。 测试 184 项(单元 155 / 集成 29)全绿,浏览器端到端 8 项通过。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
commit
273ebf4657
101 files changed
+8725
No files matched your search
@@ -0,0 +1,61 @@
|
||||
"""全局异常处理器单元测试。
|
||||
|
||||
重点验证校验错误的文案压缩逻辑:FastAPI 默认返回 422 与 detail 数组,
|
||||
本项目需映射为 400 与单行中文提示。
|
||||
"""
|
||||
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
|
||||
from app.core.constants import CODE_BAD_REQUEST
|
||||
from app.core.exception_handlers import _build_response, _format_validation_errors
|
||||
|
||||
|
||||
class TestFormatValidationErrors:
|
||||
"""校验错误文案压缩。"""
|
||||
|
||||
def test_包含字段名与原始说明(self) -> None:
|
||||
exc = RequestValidationError(
|
||||
[{"loc": ("body", "username"), "msg": "Field required", "type": "missing"}]
|
||||
)
|
||||
assert _format_validation_errors(exc) == "参数错误:username Field required"
|
||||
|
||||
def test_去掉来源前缀只保留字段路径(self) -> None:
|
||||
exc = RequestValidationError(
|
||||
[
|
||||
{
|
||||
"loc": ("query", "pageNum"),
|
||||
"msg": "Input should be greater than 0",
|
||||
"type": "greater_than",
|
||||
}
|
||||
]
|
||||
)
|
||||
assert "query" not in _format_validation_errors(exc)
|
||||
assert "pageNum" in _format_validation_errors(exc)
|
||||
|
||||
def test_多个错误时只取第一条(self) -> None:
|
||||
exc = RequestValidationError(
|
||||
[
|
||||
{"loc": ("body", "a"), "msg": "错误甲", "type": "missing"},
|
||||
{"loc": ("body", "b"), "msg": "错误乙", "type": "missing"},
|
||||
]
|
||||
)
|
||||
message = _format_validation_errors(exc)
|
||||
assert "错误甲" in message
|
||||
assert "错误乙" not in message
|
||||
|
||||
def test_错误列表为空时兜底(self) -> None:
|
||||
assert _format_validation_errors(RequestValidationError([])) == "参数错误"
|
||||
|
||||
|
||||
class TestBuildResponse:
|
||||
"""统一响应构造:HTTP 状态码与响应体 code 必须一致。"""
|
||||
|
||||
def test_状态码与响应体code一致(self) -> None:
|
||||
response = _build_response(CODE_BAD_REQUEST, "参数错误")
|
||||
assert response.status_code == CODE_BAD_REQUEST
|
||||
import json
|
||||
|
||||
body = json.loads(response.body)
|
||||
assert body["code"] == CODE_BAD_REQUEST
|
||||
assert body["message"] == "参数错误"
|
||||
assert body["data"] is None
|
||||
Reference in new issue
Block a user