feat(phase0): 工具链 + 契约快照测试,修复既有 bug
- 新增 pyproject.toml / pytest.ini / Makefile / requirements-dev.txt - 新增 3 个契约快照测试(路由167/权限串148/表名25),守护前端与数据契约 - 修复 requirements.txt UTF-16 编码导致 pip 无法解析 - 修复 JobLog 未继承 Base 导致无法映射(fastapi_job_log 表缺失于 metadata) - 修复 base_model.to_dict() 未序列化纯 date 类型(birthday 等导致 500) - 补充 .gitignore(.env/缓存/上传目录) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
"""契约快照测试公共工具。
|
||||
|
||||
契约 = 前端/数据依赖的硬不变量:
|
||||
- 路由 (METHOD /path)
|
||||
- 权限字符串 (@permission_required("sys:..."))
|
||||
- 数据库表名
|
||||
|
||||
用法:设置环境变量 UPDATE_GOLDEN=1 运行一次以重新生成 golden。
|
||||
"""
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
GOLDEN_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "golden")
|
||||
|
||||
PERMISSION_RE = re.compile(r'@permission_required\(\s*["\'](sys:[^"\']+)["\']')
|
||||
|
||||
UPDATE_GOLDEN = os.getenv("UPDATE_GOLDEN") == "1"
|
||||
|
||||
|
||||
def collect_routes():
|
||||
"""通过 openapi schema 收集全部 (METHOD /path),去重排序。"""
|
||||
from apps import create_app
|
||||
|
||||
app = create_app()
|
||||
spec = app.openapi()
|
||||
return sorted(
|
||||
f"{method.upper()} {path}"
|
||||
for path, methods in spec["paths"].items()
|
||||
for method in methods
|
||||
)
|
||||
|
||||
|
||||
def collect_permissions():
|
||||
"""静态扫描源码中的 @permission_required("sys:...") 权限串。"""
|
||||
perms = set()
|
||||
for root in ("apps", "modules"):
|
||||
for path in glob.glob(os.path.join(root, "**", "*.py"), recursive=True):
|
||||
with open(path, encoding="utf-8") as f:
|
||||
perms.update(PERMISSION_RE.findall(f.read()))
|
||||
return sorted(perms)
|
||||
|
||||
|
||||
def collect_tables():
|
||||
"""从 SQLAlchemy metadata 收集全部表名。"""
|
||||
from extensions.sqlalchemy import Base
|
||||
|
||||
return sorted(Base.metadata.tables.keys())
|
||||
|
||||
|
||||
def compare_snapshot(name: str, actual: list):
|
||||
"""与 golden 对比;UPDATE_GOLDEN=1 时重新生成并跳过断言。"""
|
||||
path = os.path.join(GOLDEN_DIR, name)
|
||||
if UPDATE_GOLDEN:
|
||||
os.makedirs(GOLDEN_DIR, exist_ok=True)
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump(actual, f, ensure_ascii=False, indent=2)
|
||||
return
|
||||
with open(path, encoding="utf-8") as f:
|
||||
golden = json.load(f)
|
||||
assert actual == golden, (
|
||||
f"契约快照 [{name}] 不一致!\n"
|
||||
f"新增: {sorted(set(actual) - set(golden))}\n"
|
||||
f"缺失: {sorted(set(golden) - set(actual))}"
|
||||
)
|
||||
Reference in New Issue
Block a user