| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107 |
- """F5 全链路报告:只存摘要、保存/导出前脱敏、Markdown 结构与渠道校验(任务书 §10 / §24)。"""
- from __future__ import annotations
- import json
- from app import CHANNELS, REPORT_FIELDS, REPORTS, build_report_markdown
- from clients.redaction import REDACTED
- SECRET = "S3CRET-VALUE-DO-NOT-LEAK"
- PASSWORD = "pw-123456"
- ACCESS_KEY = "TESTKPARTNER"
- def _fields(**kw) -> dict:
- base = {
- "runId": "SIM-RPT-001",
- "channel": "DB_SYNC",
- "environment": "开发库 / 租户 T1",
- "objectCode": "ITEM",
- "simPrep": "建表并装载 3 行",
- "manualSteps": "在 Ai-DOP 登记 SIM_MODE3_DB 来源并触发同步",
- "simEvidence": "源表 3 行,最大增量 2026-10-12",
- "aidopConn": "测连成功",
- "flowIds": "SYNC-1001",
- "stagingCheck": "贴源 3 行,与源端一致",
- "targetResult": "目标表可查 3 行",
- "conclusion": "全链路打通",
- "gaps": "",
- "cleanup": "删除 SIM_MODE3_DB 来源",
- }
- base.update(kw)
- return base
- def test_report_fields_cover_taskbook_sections():
- labels = [label for _, label in REPORT_FIELDS]
- for expected in ("运行编号", "集成方式", "环境与测试租户", "模拟端准备结果",
- "人工执行步骤", "模拟端证据", "Ai-DOP 测连结果",
- "同步/入站流水编号", "贴源核验结果", "目标业务结果",
- "结论", "未完成项", "清理提示"):
- assert any(expected in label for label in labels), f"report missing section: {expected}"
- def test_report_save_redacts_secret_and_password():
- # 真实运行中密码来自环境变量 _secrets();这里把密码作为已知 Secret 传入,模拟同一路径
- entry = REPORTS.save(_fields(simPrep=f"用密码 {PASSWORD} 连接,Secret={SECRET} 初始化"),
- secrets=[SECRET, PASSWORD])
- dumped = json.dumps(entry, ensure_ascii=False)
- assert SECRET not in dumped
- assert PASSWORD not in dumped
- assert REDACTED in dumped
- def test_report_save_masks_chinese_password_label():
- # 测试人员自由文本写「密码 xxx」时也须遮蔽(任务书 §24),无需知道真实密码
- entry = REPORTS.save(_fields(simPrep=f"数据库连接:密码 {PASSWORD},端口 3306"))
- dumped = json.dumps(entry, ensure_ascii=False)
- assert PASSWORD not in dumped
- assert "3306" in dumped # 非敏感部分保留
- def test_report_save_masks_jwt_and_connection_string():
- entry = REPORTS.save(_fields(
- flowIds="token=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0.abcdEFGH1234,"
- "conn=mysql://root:pw-123456@127.0.0.1:3306/aidop_integration_sim"))
- dumped = json.dumps(entry, ensure_ascii=False)
- assert "eyJhbGciOiJIUzI1NiJ9" not in dumped
- assert "pw-123456" not in dumped
- assert "aidop_integration_sim" in dumped # 非敏感部分保留
- def test_report_save_keeps_only_last_four_of_access_key():
- entry = REPORTS.save(_fields(environment=f"开放身份 AccessKey={ACCESS_KEY}"),
- access_keys=[ACCESS_KEY])
- dumped = json.dumps(entry, ensure_ascii=False)
- assert ACCESS_KEY not in dumped
- assert "****TNER" in dumped
- def test_report_markdown_contains_all_sections_and_redaction_note():
- entry = REPORTS.save(_fields())
- md = build_report_markdown(entry)
- for label in ("运行编号 runId", "方式甲 DB_SYNC", "环境与测试租户", "对象",
- "模拟端准备结果", "人工执行步骤", "模拟端证据", "Ai-DOP 测连结果",
- "同步/入站流水编号", "贴源核验结果", "目标业务结果", "结论",
- "未完成项", "清理提示", "脱敏说明"):
- assert label in md
- assert entry["runId"] in md
- assert "SIM-RPT-001" in md
- def test_report_markdown_uses_channel_label():
- md = build_report_markdown(_fields(channel="API_INBOUND"))
- assert "方式丙 API_INBOUND(接口推送)" in md
- def test_channels_are_the_three_integration_modes():
- assert set(CHANNELS) == {"DB_SYNC", "API_PULL", "API_INBOUND"}
- def test_report_store_limit_and_delete():
- store = REPORTS
- entry = store.save(_fields(runId="SIM-RPT-DEL"))
- assert store.get(entry["id"]) is not None
- assert store.delete(entry["id"]) is True
- assert store.get(entry["id"]) is None
- assert store.delete(entry["id"]) is False
|