fix: rule_signature conditions=None防御 + 0行表格覆盖率 + UT覆盖 - Closes #21
CI / test (pull_request) Successful in 9s

- step3 rule_signature: trigger.conditions=None 时使用 `or []` 防御
- step1 _quick_validate: total_rows=0 时行覆盖率设为 100% 而非 0%
- test_step1: 新增 TestHasSectionContent (10个) + TestQuickValidateEmptySections (2个)
- test_step3: 新增 TestRuleSignature (7个) + TestNormalizeRule (4个)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 13:29:25 +08:00
parent 50eb37094a
commit da17b3b3b2
4 changed files with 380 additions and 1 deletions
@@ -305,3 +305,163 @@ def test_step3_audit_report():
if __name__ == "__main__":
success = run_all_tests()
sys.exit(0 if success else 1)
# ═══════════════════════════════════════════════════════════════════════════════
# Pure unit tests for step3 helper functions — no LLM output needed
# ═══════════════════════════════════════════════════════════════════════════════
from step3_merge_and_audit import rule_signature, _normalize_rule
class TestRuleSignature:
"""Unit tests for rule_signature with edge cases."""
def test_normal_rule(self):
"""Standard rule with valid trigger dict should produce a signature."""
rule = {
"path": ["国内", "系统限制", "前台打断"],
"trigger": {
"operator": "AND",
"conditions": [
{"signal": "车速", "operator": ">=", "value": "5"},
{"signal": "档位", "operator": "==", "value": "D"}
]
},
"actions": [
{"type": "system", "description": "弹出提示"}
]
}
sig = rule_signature(rule)
assert isinstance(sig, str)
assert len(sig) == 16 # sha256 hex digest[:16]
def test_trigger_is_none(self):
"""Rule with trigger: None should not crash."""
rule = {
"path": ["国内", "系统限制", "前台打断"],
"trigger": None,
"actions": [
{"type": "system", "description": "弹出提示"}
]
}
sig = rule_signature(rule)
assert isinstance(sig, str)
assert len(sig) == 16
def test_trigger_key_missing(self):
"""Rule without trigger key should not crash."""
rule = {
"path": ["国内", "系统限制"],
"actions": [
{"type": "system", "description": "限制启动"}
]
}
sig = rule_signature(rule)
assert isinstance(sig, str)
assert len(sig) == 16
def test_actions_is_none(self):
"""Rule with actions: None should not crash."""
rule = {
"path": ["国内"],
"trigger": {"conditions": []},
"actions": None
}
sig = rule_signature(rule)
assert isinstance(sig, str)
assert len(sig) == 16
def test_trigger_is_empty_dict(self):
"""Rule with trigger: {} should work."""
rule = {
"path": ["海外", "SDK限制"],
"trigger": {},
"actions": []
}
sig = rule_signature(rule)
assert isinstance(sig, str)
def test_trigger_conditions_is_none(self):
"""Rule with trigger.conditions: None should not crash."""
rule = {
"path": [],
"trigger": {"operator": "AND", "conditions": None},
"actions": [{"description": "do nothing"}]
}
# This might still crash if conditions is None because .get("conditions", [])
# returns None when the key exists with None value
# But our fix is on the trigger level, not conditions level
sig = rule_signature(rule)
assert isinstance(sig, str)
def test_deterministic_signature(self):
"""Same rule should produce the same signature every time."""
rule = {
"path": ["国内", "系统限制", "前台打断"],
"trigger": {
"operator": "OR",
"conditions": [
{"signal": "车速", "operator": ">", "value": "0"}
]
},
"actions": [
{"description": "test"}
]
}
sig1 = rule_signature(rule)
sig2 = rule_signature(rule)
assert sig1 == sig2
class TestNormalizeRule:
"""Unit tests for _normalize_rule."""
def test_normalize_null_trigger(self):
"""_normalize_rule should fix trigger: None."""
rule = {"trigger": None, "actions": []}
normalized = _normalize_rule(rule)
# _normalize_rule fills in default trigger with conditions
assert "trigger" in normalized
assert normalized["trigger"]["operator"] == "AND"
assert len(normalized["trigger"]["conditions"]) >= 1
# After normalization, rule_signature should work
sig = rule_signature(normalized)
assert isinstance(sig, str)
def test_normalize_missing_trigger(self):
"""_normalize_rule should add trigger if missing."""
rule = {"actions": []}
normalized = _normalize_rule(rule)
assert "trigger" in normalized
assert normalized["trigger"]["operator"] == "AND"
assert len(normalized["trigger"]["conditions"]) >= 1
def test_normalize_null_operator(self):
"""_normalize_rule should fix null operator in conditions."""
rule = {
"trigger": {
"conditions": [
{"signal": "车速", "operator": None, "value": "5"}
]
},
"actions": []
}
normalized = _normalize_rule(rule)
cond = normalized["trigger"]["conditions"][0]
assert cond["operator"] == "=="
def test_normalize_keeps_valid_rule(self):
"""_normalize_rule should not change a valid rule."""
rule = {
"trigger": {
"operator": "AND",
"conditions": [
{"signal": "车速", "operator": ">=", "value": "5"}
]
},
"actions": [{"type": "system", "description": "test"}]
}
normalized = _normalize_rule(rule)
assert normalized["trigger"]["operator"] == "AND"
assert normalized["trigger"]["conditions"][0]["operator"] == ">="