_verify_notify_tpl_cfg.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. # -*- coding: utf-8 -*-
  2. """P4-16 扩展:通知模板 / 通知渠道配置 冒烟验证(DB 侧)。
  3. 仅做结构与种子数据可用性检查,不依赖前后端 HTTP 联调:
  4. - T1:ApprovalFlowNotifyTemplate / ApprovalFlowNotifyConfig 两表存在,列齐。
  5. - T2:后端启动过至少一次后,系统预置模板应至少 9 条(NewTask / Urge / FlowCompleted / Transferred / Returned / AddSign / Withdrawn / Escalated / Timeout 的全局默认)。
  6. - T3:通知配置菜单 SysMenu Id=1310300010106 存在且已授权(RoleMenu + TenantMenu 有记录)。
  7. - T4:渲染兜底:模拟 BizType 级覆盖缺失,应能退回全局模板(通过 Python 端模拟 SELECT)。
  8. 使用方法:
  9. python _verify_notify_tpl_cfg.py
  10. 若 T2 失败(模板 0 条),说明后端尚未启动过(InitTables + EnsureSystemTemplates 未执行)。
  11. """
  12. from __future__ import annotations
  13. import pymysql
  14. import sys
  15. CONN_ARGS = dict(
  16. host='123.60.180.165', port=3306,
  17. user='aidopremote', password='1234567890aiDOP#',
  18. database='aidopdev', charset='utf8mb4', autocommit=True,
  19. )
  20. EXPECTED_TYPES = {"NewTask", "Urge", "FlowCompleted", "Transferred", "Returned", "AddSign", "Withdrawn", "Escalated", "Timeout"}
  21. def ok(name: str, cond: bool, detail: str = "") -> bool:
  22. flag = "PASS" if cond else "FAIL"
  23. print(f"[{flag}] {name}" + (f" - {detail}" if detail else ""))
  24. return cond
  25. def main() -> int:
  26. conn = pymysql.connect(**CONN_ARGS)
  27. passed = 0
  28. total = 0
  29. try:
  30. with conn.cursor(pymysql.cursors.DictCursor) as cur:
  31. # T1 表结构
  32. for t, expected_cols in [
  33. ("ApprovalFlowNotifyTemplate", {"NotifyType", "BizType", "Title", "Content", "IsEnabled", "IsSystem"}),
  34. ("ApprovalFlowNotifyConfig", {"ChannelKey", "Enabled", "WebhookUrl", "Secret", "TemplateId", "Remark"}),
  35. ]:
  36. total += 1
  37. cur.execute(f"SHOW TABLES LIKE '{t}'")
  38. has_tbl = bool(cur.fetchall())
  39. if not has_tbl:
  40. ok(f"T1 表 {t} 存在", False, "表不存在")
  41. continue
  42. cur.execute(f"DESC `{t}`")
  43. cols = {r["Field"] for r in cur.fetchall()}
  44. miss = expected_cols - cols
  45. if ok(f"T1 表 {t} 结构", not miss, f"缺字段: {miss}" if miss else f"列 {len(cols)} 个"):
  46. passed += 1
  47. # T2 默认模板种子
  48. total += 1
  49. cur.execute(
  50. "SELECT NotifyType, COUNT(*) AS n FROM ApprovalFlowNotifyTemplate "
  51. "WHERE BizType='' AND IsSystem=1 GROUP BY NotifyType"
  52. )
  53. rows = cur.fetchall()
  54. seed_types = {r["NotifyType"] for r in rows}
  55. miss = EXPECTED_TYPES - seed_types
  56. if ok(
  57. "T2 系统预置模板(全局)覆盖 9 类通知",
  58. not miss,
  59. f"缺: {miss}" if miss else f"已预置 {len(seed_types)} 类({len(rows)} 行)",
  60. ):
  61. passed += 1
  62. # T3 通知配置菜单
  63. total += 1
  64. cur.execute("SELECT Id, Title, Path FROM SysMenu WHERE Id=1310300010106")
  65. m = cur.fetchone()
  66. cur.execute("SELECT COUNT(*) AS n FROM SysRoleMenu WHERE MenuId=1310300010106")
  67. r = cur.fetchone()["n"]
  68. cur.execute("SELECT COUNT(*) AS n FROM SysTenantMenu WHERE MenuId=1310300010106")
  69. t_cnt = cur.fetchone()["n"]
  70. cond = bool(m) and r > 0 and t_cnt > 0
  71. detail = f"menu={bool(m)} RoleMenu={r} TenantMenu={t_cnt}"
  72. if ok("T3 通知配置菜单已挂载 + 授权", cond, detail):
  73. passed += 1
  74. # T4 渲染兜底(BizType 级不存在时回退全局)
  75. total += 1
  76. biz = "__VERIFY_NOT_EXIST_BIZ__"
  77. cur.execute(
  78. "SELECT Id FROM ApprovalFlowNotifyTemplate WHERE NotifyType='NewTask' AND BizType=%s",
  79. (biz,),
  80. )
  81. biz_hit = cur.fetchone()
  82. cur.execute(
  83. "SELECT Id FROM ApprovalFlowNotifyTemplate WHERE NotifyType='NewTask' AND BizType='' AND IsEnabled=1 LIMIT 1"
  84. )
  85. global_hit = cur.fetchone()
  86. fallback_ok = biz_hit is None and global_hit is not None
  87. if ok("T4 BizType 级缺失时可回退全局", fallback_ok):
  88. passed += 1
  89. print("\n==============================")
  90. print(f"结果:{passed}/{total} 通过")
  91. return 0 if passed == total else 1
  92. finally:
  93. conn.close()
  94. if __name__ == "__main__":
  95. sys.exit(main())