| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 |
- from __future__ import annotations
- import json
- import re
- from pathlib import Path
- ROOT = Path(__file__).resolve().parents[1]
- UAT_CASES = ROOT / "docs/acceptance/P2_WP13_UAT_CASES.json"
- UAT_REPORT = ROOT / "docs/acceptance/P2_WP13_UAT_PLAN_AND_REPORT.md"
- DEFECT_REGISTER = ROOT / "docs/acceptance/P2_WP13_DEFECT_REGISTER.md"
- SIGNOFF = ROOT / "docs/acceptance/P2_WP13_SIGNOFF_CHECKLIST.md"
- USER_GUIDE = ROOT / "docs/guides/DATAOPS_PHASE2_USER_OPERATION_GUIDE.md"
- OPERATIONS_HANDOVER = ROOT / "docs/runbooks/DATAOPS_PHASE2_OPERATIONS_HANDOVER.md"
- TRAINING = ROOT / "docs/training/DATAOPS_PHASE2_TRAINING_PLAN.md"
- PHASE3_BACKLOG = ROOT / "docs/roadmap/DATAOPS_PHASE3_BACKLOG.md"
- CENSUS = ROOT / "docs/FUNCTION_MODULE_CENSUS_20260726.md"
- CENSUS_AUDIT = ROOT / "docs/validation/P2_WP13_FUNCTION_CENSUS_AUDIT.md"
- EVIDENCE = ROOT / "docs/validation/P2_WP13_ACCEPTANCE_HANDOVER_EVIDENCE.md"
- PHASE2_PLAN = ROOT / "docs/DATAOPS_PHASE2_3_MONTH_DEVELOPMENT_PLAN_20260730.md"
- DELIVERABLES = (
- UAT_CASES,
- UAT_REPORT,
- DEFECT_REGISTER,
- SIGNOFF,
- USER_GUIDE,
- OPERATIONS_HANDOVER,
- TRAINING,
- PHASE3_BACKLOG,
- CENSUS_AUDIT,
- EVIDENCE,
- )
- P2_COMPLETED_LEDGER_IDS = {
- "GOV-05",
- "GOV-06",
- "GOV-07",
- "GOV-08",
- "GOV-09",
- "CAT-17",
- "CAT-23",
- "CAT-26",
- "OBS-04",
- "OBS-05",
- "OBS-06",
- "WFC-04",
- "WFC-05",
- "WFC-06",
- "WFC-07",
- "WFC-08",
- "WFC-09",
- "WFC-10",
- "WFC-11",
- "WFC-12",
- "WFC-15",
- }
- def _read(path: Path) -> str:
- return path.read_text(encoding="utf-8")
- def _cases() -> list[dict]:
- payload = json.loads(_read(UAT_CASES))
- assert payload["schema_version"] == "1.0"
- assert payload["work_package"] == "P2-WP13"
- return payload["cases"]
- def _ledger_rows() -> dict[str, list[str]]:
- rows: dict[str, list[str]] = {}
- for line in _read(CENSUS).splitlines():
- match = re.match(r"^\| ([A-Z]{3}-\d{2}) \| (.+) \| (.+) \| (.+) \|$", line)
- if match:
- rows[match.group(1)] = [match.group(2), match.group(3), match.group(4)]
- return rows
- def test_p2_wp13_deliverables_exist() -> None:
- missing = [str(path.relative_to(ROOT)) for path in DELIVERABLES if not path.is_file()]
- assert not missing, f"P2-WP13 交付物缺失: {missing}"
- def test_uat_cases_cover_all_work_packages_roles_and_external_boundaries() -> None:
- cases = _cases()
- assert len(cases) >= 24
- required_fields = {
- "id",
- "work_packages",
- "module_ids",
- "title",
- "actor",
- "acceptance_level",
- "preconditions",
- "steps",
- "expected",
- "evidence",
- "result",
- "notes",
- }
- actors = {"admin", "editor", "viewer", "second_domain_owner", "enterprise_acceptance"}
- for case in cases:
- assert required_fields <= case.keys(), case.get("id")
- assert case["id"].startswith("P2-WP13-UAT-")
- assert case["actor"] in actors
- assert case["acceptance_level"] in {"LOCAL_ENGINEERING", "ENTERPRISE_FORMAL"}
- assert case["result"] in {"PASS_LOCAL", "FAIL", "BLOCKED_EXTERNAL", "NOT_RUN"}
- assert case["work_packages"] and case["module_ids"]
- assert case["preconditions"] and len(case["steps"]) >= 2
- assert case["expected"] and case["evidence"]
- if case["acceptance_level"] == "ENTERPRISE_FORMAL":
- assert case["result"] != "PASS_LOCAL"
- covered = {work_package for case in cases for work_package in case["work_packages"]}
- assert {f"P2-WP{number:02d}" for number in range(1, 13)} <= covered
- assert actors - {"enterprise_acceptance"} <= {case["actor"] for case in cases}
- assert any(case["result"] == "BLOCKED_EXTERNAL" for case in cases)
- assert not any(case["result"] == "FAIL" for case in cases)
- def test_local_uat_evidence_references_existing_workspace_files() -> None:
- for case in _cases():
- if case["result"] != "PASS_LOCAL":
- continue
- for reference in case["evidence"]:
- path = ROOT / reference.split("#", 1)[0]
- assert path.is_file(), f"{case['id']} evidence missing: {reference}"
- def test_acceptance_docs_are_explicit_about_local_and_enterprise_status() -> None:
- report = _read(UAT_REPORT)
- for phrase in (
- "本地工程预验收",
- "企业正式验收",
- "不能替代企业真实数据 UAT",
- "BLOCKED_EXTERNAL",
- "P0",
- "签字",
- ):
- assert phrase in report
- for path in DELIVERABLES[1:]:
- assert not re.search(r"\b(?:TBD|TODO)\b|\{\{[^}]+\}\}", _read(path)), path.name
- def test_defect_register_and_signoff_are_fail_closed() -> None:
- defects = _read(DEFECT_REGISTER)
- for field in ("缺陷编号", "严重度", "状态", "复现与证据", "处置决定"):
- assert field in defects
- for severity in ("P0", "P1", "P2", "P3"):
- assert severity in defects
- assert "P0:0" in defects
- assert "未关闭 P1:0" in defects
- signoff = _read(SIGNOFF)
- for role in ("产品负责人", "业务负责人", "技术负责人", "安全负责人", "运维负责人"):
- assert role in signoff
- assert "不得预签" in signoff
- assert "BLOCKED_EXTERNAL" in signoff
- def test_user_operations_and_training_materials_cover_handover_scope() -> None:
- user_guide = _read(USER_GUIDE)
- for topic in (
- "管理员",
- "编辑者",
- "查看者",
- "第二业务域责任人",
- "主动元数据",
- "语义",
- "质量",
- "事故",
- "数据产品",
- "Agent",
- "安全",
- ):
- assert topic in user_guide
- operations = _read(OPERATIONS_HANDOVER)
- for topic in (
- "每日巡检",
- "每周巡检",
- "备份",
- "恢复",
- "回滚",
- "升级",
- "异常升级",
- "责任",
- "移交清单",
- ):
- assert topic in operations
- training = _read(TRAINING)
- for topic in ("用户培训", "运维培训", "练习", "签到", "补训", "BLOCKED_EXTERNAL"):
- assert topic in training
- def test_phase3_backlog_preserves_product_boundary_and_sso_gate() -> None:
- backlog = _read(PHASE3_BACKLOG)
- for field in ("待办编号", "来源模块", "优先级", "启动条件", "验收标准", "依赖", "目标阶段"):
- assert field in backlog
- for topic in ("单点登录", "OIDC", "IdP", "企业数据源", "多租户", "生产可观测", "第三业务域"):
- assert topic in backlog
- assert "不扩展为分析开发平台" in backlog
- def test_function_census_has_264_unique_audited_items_and_no_stale_p2_plans() -> None:
- rows = _ledger_rows()
- assert len(rows) == 264
- assert all(row[2].strip() for row in rows.values())
- for module_id in P2_COMPLETED_LEDGER_IDS:
- assert rows[module_id][2] != "规划中", module_id
- audit = _read(CENSUS_AUDIT)
- assert "264" in audit
- assert "264/264" in audit
- assert "生产已建设" not in audit
- assert "企业 UAT" in audit
- def test_phase2_plan_and_evidence_record_engineering_completion_without_formal_signoff() -> None:
- plan = _read(PHASE2_PLAN)
- section = plan.split("### P2-WP13 第二阶段验收与移交", 1)[1].split("## 8.", 1)[0]
- assert section.count("- [x]") == 5
- assert "本地工程完成" in section
- assert "企业正式验收未完成" in section
- evidence = _read(EVIDENCE)
- for section_name in (
- "文档契约",
- "UAT 覆盖",
- "角色与权限",
- "缺陷结论",
- "培训与移交",
- "功能台账",
- "外部门禁",
- ):
- assert section_name in evidence
- assert "tests/test_p2_wp13_acceptance_handover_contract.py" in evidence
- assert "PASS_LOCAL" in evidence
- assert "BLOCKED_EXTERNAL" in evidence
|