- 新增 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>
67 lines
2.0 KiB
Python
67 lines
2.0 KiB
Python
"""契约快照测试公共工具。
|
|
|
|
契约 = 前端/数据依赖的硬不变量:
|
|
- 路由 (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))}"
|
|
)
|