test_report.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. """F5 全链路报告:只存摘要、保存/导出前脱敏、Markdown 结构与渠道校验(任务书 §10 / §24)。"""
  2. from __future__ import annotations
  3. import json
  4. from app import CHANNELS, REPORT_FIELDS, REPORTS, build_report_markdown
  5. from clients.redaction import REDACTED
  6. SECRET = "S3CRET-VALUE-DO-NOT-LEAK"
  7. PASSWORD = "pw-123456"
  8. ACCESS_KEY = "TESTKPARTNER"
  9. def _fields(**kw) -> dict:
  10. base = {
  11. "runId": "SIM-RPT-001",
  12. "channel": "DB_SYNC",
  13. "environment": "开发库 / 租户 T1",
  14. "objectCode": "ITEM",
  15. "simPrep": "建表并装载 3 行",
  16. "manualSteps": "在 Ai-DOP 登记 SIM_MODE3_DB 来源并触发同步",
  17. "simEvidence": "源表 3 行,最大增量 2026-10-12",
  18. "aidopConn": "测连成功",
  19. "flowIds": "SYNC-1001",
  20. "stagingCheck": "贴源 3 行,与源端一致",
  21. "targetResult": "目标表可查 3 行",
  22. "conclusion": "全链路打通",
  23. "gaps": "",
  24. "cleanup": "删除 SIM_MODE3_DB 来源",
  25. }
  26. base.update(kw)
  27. return base
  28. def test_report_fields_cover_taskbook_sections():
  29. labels = [label for _, label in REPORT_FIELDS]
  30. for expected in ("运行编号", "集成方式", "环境与测试租户", "模拟端准备结果",
  31. "人工执行步骤", "模拟端证据", "Ai-DOP 测连结果",
  32. "同步/入站流水编号", "贴源核验结果", "目标业务结果",
  33. "结论", "未完成项", "清理提示"):
  34. assert any(expected in label for label in labels), f"report missing section: {expected}"
  35. def test_report_save_redacts_secret_and_password():
  36. # 真实运行中密码来自环境变量 _secrets();这里把密码作为已知 Secret 传入,模拟同一路径
  37. entry = REPORTS.save(_fields(simPrep=f"用密码 {PASSWORD} 连接,Secret={SECRET} 初始化"),
  38. secrets=[SECRET, PASSWORD])
  39. dumped = json.dumps(entry, ensure_ascii=False)
  40. assert SECRET not in dumped
  41. assert PASSWORD not in dumped
  42. assert REDACTED in dumped
  43. def test_report_save_masks_chinese_password_label():
  44. # 测试人员自由文本写「密码 xxx」时也须遮蔽(任务书 §24),无需知道真实密码
  45. entry = REPORTS.save(_fields(simPrep=f"数据库连接:密码 {PASSWORD},端口 3306"))
  46. dumped = json.dumps(entry, ensure_ascii=False)
  47. assert PASSWORD not in dumped
  48. assert "3306" in dumped # 非敏感部分保留
  49. def test_report_save_masks_jwt_and_connection_string():
  50. entry = REPORTS.save(_fields(
  51. flowIds="token=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0.abcdEFGH1234,"
  52. "conn=mysql://root:pw-123456@127.0.0.1:3306/aidop_integration_sim"))
  53. dumped = json.dumps(entry, ensure_ascii=False)
  54. assert "eyJhbGciOiJIUzI1NiJ9" not in dumped
  55. assert "pw-123456" not in dumped
  56. assert "aidop_integration_sim" in dumped # 非敏感部分保留
  57. def test_report_save_keeps_only_last_four_of_access_key():
  58. entry = REPORTS.save(_fields(environment=f"开放身份 AccessKey={ACCESS_KEY}"),
  59. access_keys=[ACCESS_KEY])
  60. dumped = json.dumps(entry, ensure_ascii=False)
  61. assert ACCESS_KEY not in dumped
  62. assert "****TNER" in dumped
  63. def test_report_markdown_contains_all_sections_and_redaction_note():
  64. entry = REPORTS.save(_fields())
  65. md = build_report_markdown(entry)
  66. for label in ("运行编号 runId", "方式甲 DB_SYNC", "环境与测试租户", "对象",
  67. "模拟端准备结果", "人工执行步骤", "模拟端证据", "Ai-DOP 测连结果",
  68. "同步/入站流水编号", "贴源核验结果", "目标业务结果", "结论",
  69. "未完成项", "清理提示", "脱敏说明"):
  70. assert label in md
  71. assert entry["runId"] in md
  72. assert "SIM-RPT-001" in md
  73. def test_report_markdown_uses_channel_label():
  74. md = build_report_markdown(_fields(channel="API_INBOUND"))
  75. assert "方式丙 API_INBOUND(接口推送)" in md
  76. def test_channels_are_the_three_integration_modes():
  77. assert set(CHANNELS) == {"DB_SYNC", "API_PULL", "API_INBOUND"}
  78. def test_report_store_limit_and_delete():
  79. store = REPORTS
  80. entry = store.save(_fields(runId="SIM-RPT-DEL"))
  81. assert store.get(entry["id"]) is not None
  82. assert store.delete(entry["id"]) is True
  83. assert store.get(entry["id"]) is None
  84. assert store.delete(entry["id"]) is False