"""v7 机制第 8 项:MCDT 结果指标(评分卡输入模型)定向测试。 钉住的规则(对应任务 (a)(b)(c)): (a) 某维无对应验收节点时该维输入为 unknown,不得用过程指标填补。 (b) 过程指标(test_count、coverage)只作为证据字段透传,绝不直接 进入得分输入:只改 test_count/coverage 而节点状态不变时,每维 的得分输入不变;只改节点状态(pending->pass 或 pass->fail)时, 映射输出变化。 (c) 节点状态为 fail 时对应维不得为 pass;节点状态 unknown 时对应维 为 unknown。 诚实边界:此前 v6 头全仓 grep MCDT / 关键事实节点 / 验收节点为 0 命中,本项只是评分卡输入模型,不是产品功能;测试不声称 MCDT 已接入生产评分、门禁或真实审计循环。阈值一律仍是 test_default_unapproved,除非调用方显式传入全 user_approved。 """ import sys import unittest from pathlib import Path try: from cloud.audit_scorecard import DIMENSION_IDS as SCORECARD_DIMS from cloud.mcdt_nodes import ( AcceptanceNode, KeyFactNode, DIMENSION_IDS, nodes_to_dimension_inputs, ) except ImportError: sys.path.insert(0, str(Path(__file__).resolve().parents[2])) from cloud.audit_scorecard import DIMENSION_IDS as SCORECARD_DIMS from cloud.mcdt_nodes import ( AcceptanceNode, KeyFactNode, DIMENSION_IDS, nodes_to_dimension_inputs, ) def _acc(node_id, dim, status, **extra): node = { "id": node_id, "title": "t-%s" % node_id, "acceptance_criteria": ["criterion 1"], "status": status, "evidence_refs": ["ev-1"], "dimension": dim, } node.update(extra) return node def _fact(node_id, dim, status): return { "id": node_id, "fact": "fact-%s" % node_id, "status": status, "source": "src-1", "dimension": dim, } def _walk(obj): if isinstance(obj, dict): for key, value in obj.items(): yield key yield from _walk(value) elif isinstance(obj, (list, tuple)): for value in obj: yield from _walk(value) class McdtNodesTestCase(unittest.TestCase): def test_dimension_ids_match_scorecard(self): self.assertEqual(DIMENSION_IDS, SCORECARD_DIMS) self.assertEqual(len(DIMENSION_IDS), 7) def test_no_nodes_all_dimensions_unknown(self): out = nodes_to_dimension_inputs([], []) for dim in DIMENSION_IDS: entry = out["dimensions"][dim] self.assertEqual(entry["status"], "unknown") self.assertIn("no_acceptance_node_mapped", entry["missing_items"]) self.assertEqual(entry["score_input"]["node_status"], "unknown") def test_only_one_dimension_mapped_others_unknown(self): out = nodes_to_dimension_inputs( [_acc("a1", "delivery_quality", "pass")], []) self.assertEqual(out["dimensions"]["delivery_quality"]["status"], "pass") for dim in DIMENSION_IDS: if dim != "delivery_quality": self.assertEqual(out["dimensions"][dim]["status"], "unknown") def test_process_metrics_do_not_change_dimension_inputs(self): nodes = [_acc("a1", "delivery_quality", "pass"), _acc("a2", "gate_audit", "pending")] base = nodes_to_dimension_inputs( nodes, [], process_metrics={"test_count": 100, "coverage": 0.5}) changed = nodes_to_dimension_inputs( nodes, [], process_metrics={"test_count": 10000, "coverage": 0.99}) self.assertEqual(base["dimensions"], changed["dimensions"]) # 透传只在顶层,不在任何一维里。 self.assertEqual(changed["process_evidence"]["test_count"], 10000) self.assertEqual(changed["process_evidence"]["coverage"], 0.99) for entry in changed["dimensions"].values(): keys = set(_walk(entry)) self.assertNotIn("test_count", keys) self.assertNotIn("coverage", keys) def test_node_carried_process_keys_are_ignored(self): plain = nodes_to_dimension_inputs( [_acc("a1", "delivery_quality", "pass")], []) noisy = nodes_to_dimension_inputs( [_acc("a1", "delivery_quality", "pass", test_count=99999, coverage=0.01)], []) self.assertEqual(plain["dimensions"], noisy["dimensions"]) def test_pending_to_pass_changes_output(self): before = nodes_to_dimension_inputs( [_acc("a1", "delivery_quality", "pending")], []) after = nodes_to_dimension_inputs( [_acc("a1", "delivery_quality", "pass")], []) self.assertEqual( before["dimensions"]["delivery_quality"]["status"], "unknown") self.assertEqual( after["dimensions"]["delivery_quality"]["status"], "pass") self.assertNotEqual(before["dimensions"], after["dimensions"]) def test_pass_to_fail_changes_output_and_is_not_pass(self): before = nodes_to_dimension_inputs( [_acc("a1", "arch_contract", "pass")], []) after = nodes_to_dimension_inputs( [_acc("a1", "arch_contract", "fail")], []) self.assertEqual(before["dimensions"]["arch_contract"]["status"], "pass") self.assertEqual(after["dimensions"]["arch_contract"]["status"], "fail") self.assertNotEqual(before["dimensions"], after["dimensions"]) def test_fail_node_blocks_pass_even_with_other_pass_nodes(self): out = nodes_to_dimension_inputs( [_acc("a1", "gate_audit", "pass"), _acc("a2", "gate_audit", "fail")], []) self.assertEqual(out["dimensions"]["gate_audit"]["status"], "fail") def test_unknown_node_means_dimension_unknown(self): out = nodes_to_dimension_inputs( [_acc("a1", "delivery_quality", "unknown")], []) self.assertEqual(out["dimensions"]["delivery_quality"]["status"], "unknown") out2 = nodes_to_dimension_inputs( [_acc("a1", "delivery_quality", "pass"), _acc("a2", "delivery_quality", "unknown")], []) self.assertEqual(out2["dimensions"]["delivery_quality"]["status"], "unknown") def test_key_fact_fail_blocks_pass(self): out = nodes_to_dimension_inputs( [_acc("a1", "delivery_quality", "pass")], [_fact("f1", "delivery_quality", "fail")]) self.assertEqual(out["dimensions"]["delivery_quality"]["status"], "fail") def test_key_fact_unknown_blocks_pass(self): out = nodes_to_dimension_inputs( [_acc("a1", "delivery_quality", "pass")], [_fact("f1", "delivery_quality", "unknown")]) self.assertEqual(out["dimensions"]["delivery_quality"]["status"], "unknown") def test_key_fact_alone_cannot_pass_dimension(self): out = nodes_to_dimension_inputs( [], [_fact("f1", "delivery_quality", "pass")]) self.assertEqual(out["dimensions"]["delivery_quality"]["status"], "unknown") def test_unmapped_nodes_do_not_affect_any_dimension(self): out = nodes_to_dimension_inputs( [_acc("a1", None, "pass")], [_fact("f1", "bogus_dim", "pass")]) for dim in DIMENSION_IDS: self.assertEqual(out["dimensions"][dim]["status"], "unknown") self.assertIn("a1", out["unmapped_acceptance_node_ids"]) self.assertIn("f1", out["unmapped_key_fact_node_ids"]) def test_invalid_status_is_unknown_not_pass(self): out = nodes_to_dimension_inputs( [_acc("a1", "delivery_quality", "PASS")], []) self.assertEqual(out["dimensions"]["delivery_quality"]["status"], "unknown") def test_dataclass_nodes_work(self): out = nodes_to_dimension_inputs( [AcceptanceNode(id="a1", title="t", acceptance_criteria=["c"], status="pass", evidence_refs=["e"], dimension="arch_contract")], [KeyFactNode(id="f1", fact="f", status="pass", source="s", dimension="arch_contract")]) self.assertEqual(out["dimensions"]["arch_contract"]["status"], "pass") def test_thresholds_default_unapproved_and_do_not_change_status(self): nodes = [_acc("a1", "delivery_quality", "pass")] default = nodes_to_dimension_inputs(nodes, []) self.assertEqual(default["thresholds_source"], "test_default_unapproved") approved = nodes_to_dimension_inputs( nodes, [], thresholds={"x": {"value": 1, "source": "user_approved"}}) self.assertEqual(approved["thresholds_source"], "user_approved") self.assertEqual(default["dimensions"], approved["dimensions"]) # 未知维传了已拍板阈值也不能变 pass。 unknown_approved = nodes_to_dimension_inputs( [], [], thresholds={"x": {"value": 1, "source": "user_approved"}}) self.assertEqual( unknown_approved["dimensions"]["delivery_quality"]["status"], "unknown") def test_test_coverage_not_filled_by_coverage_metric(self): out = nodes_to_dimension_inputs( [], [], process_metrics={"test_count": 5000, "coverage": 0.99}) self.assertEqual(out["dimensions"]["test_coverage"]["status"], "unknown") self.assertIn("no_acceptance_node_mapped", out["dimensions"]["test_coverage"]["missing_items"]) if __name__ == "__main__": unittest.main()