rust_rule_engine/engine/
coverage.rs

1//! Module đo coverage cho rule engine
2//! Lưu lại thông tin rule đã được test, facts đã test, và sinh báo cáo coverage
3
4use std::collections::{HashMap, HashSet};
5
6#[derive(Debug, Default)]
7pub struct RuleCoverage {
8    /// Tên rule -> số lần được kích hoạt
9    pub rule_hits: HashMap<String, usize>,
10    /// Tên rule -> facts đã test
11    pub rule_facts: HashMap<String, HashSet<String>>,
12    /// Facts đã test toàn bộ
13    pub tested_facts: HashSet<String>,
14}
15
16impl RuleCoverage {
17    pub fn new() -> Self {
18        Self::default()
19    }
20
21    /// Ghi nhận rule được kích hoạt với facts
22    pub fn record_hit(&mut self, rule_name: &str, facts_id: &str) {
23        *self.rule_hits.entry(rule_name.to_string()).or_insert(0) += 1;
24        self.rule_facts.entry(rule_name.to_string())
25            .or_insert_with(HashSet::new)
26            .insert(facts_id.to_string());
27        self.tested_facts.insert(facts_id.to_string());
28    }
29
30    /// Sinh báo cáo coverage, cảnh báo rule chưa được test
31    pub fn report(&self, all_rules: &[String]) -> String {
32        let mut report = String::new();
33        report.push_str("Rule Coverage Report\n====================\n");
34        let mut untested = Vec::new();
35        for rule in all_rules {
36            let hits = self.rule_hits.get(rule).copied().unwrap_or(0);
37            let facts = self.rule_facts.get(rule).map(|f| f.len()).unwrap_or(0);
38            report.push_str(&format!(
39                "Rule: {} | Hits: {} | Facts tested: {}\n",
40                rule, hits, facts
41            ));
42            if hits == 0 {
43                untested.push(rule.clone());
44            }
45        }
46        report.push_str(&format!("Total facts tested: {}\n", self.tested_facts.len()));
47        if !untested.is_empty() {
48            report.push_str("\n⚠️ Cảnh báo: Các rule chưa được test:\n");
49            for rule in untested {
50                report.push_str(&format!("  - {}\n", rule));
51            }
52        }
53        report
54    }
55}
56
57// TODO: Thêm hàm sinh test case cho facts và chạy toàn bộ rule để đo coverage
58
59/// Lấy tất cả điều kiện Single từ ConditionGroup
60
61fn flatten_conditions(group: &crate::engine::rule::ConditionGroup) -> Vec<crate::engine::rule::Condition> {
62    use crate::engine::rule::ConditionGroup;
63    let mut out = Vec::new();
64    match group {
65        ConditionGroup::Single(cond) => out.push(cond.clone()),
66        ConditionGroup::Compound { left, right, .. } => {
67            out.extend(flatten_conditions(left));
68            out.extend(flatten_conditions(right));
69        }
70        ConditionGroup::Not(inner) | ConditionGroup::Exists(inner) | ConditionGroup::Forall(inner) => {
71            out.extend(flatten_conditions(inner));
72        }
73        ConditionGroup::Accumulate { .. } => {
74            // Accumulate doesn't have simple single conditions to flatten
75            // Skip for now
76        }
77    }
78    out
79}
80
81/// Sinh facts mẫu cho một rule dựa trên nhiều kiểu dữ liệu và kết hợp nhiều điều kiện
82pub fn generate_test_facts_for_rule(rule: &crate::engine::rule::Rule) -> Vec<crate::Facts> {
83    use crate::types::Value;
84    let conds = flatten_conditions(&rule.conditions);
85    let mut test_facts = Vec::new();
86
87    // Sinh facts cho từng điều kiện riêng lẻ
88    for cond in &conds {
89        let mut facts = crate::Facts::new();
90        let field = cond.field.clone();
91        match &cond.value {
92            Value::Integer(i) => {
93                facts.set(&field, Value::Integer(*i));
94                test_facts.push(facts.clone());
95                facts.set(&field, Value::Integer(i + 1));
96                test_facts.push(facts.clone());
97            }
98            Value::Boolean(b) => {
99                facts.set(&field, Value::Boolean(*b));
100                test_facts.push(facts.clone());
101                facts.set(&field, Value::Boolean(!b));
102                test_facts.push(facts.clone());
103            }
104            Value::String(s) => {
105                facts.set(&field, Value::String(s.clone()));
106                test_facts.push(facts.clone());
107                facts.set(&field, Value::String("other_value".to_string()));
108                test_facts.push(facts.clone());
109            }
110            _ => {}
111        }
112    }
113
114    // Sinh facts kết hợp nhiều điều kiện (all true)
115    if !conds.is_empty() {
116        let mut facts = crate::Facts::new();
117        for cond in &conds {
118            let field = cond.field.clone();
119            match &cond.value {
120                Value::Integer(i) => facts.set(&field, Value::Integer(*i)),
121                Value::Boolean(b) => facts.set(&field, Value::Boolean(*b)),
122                Value::String(s) => facts.set(&field, Value::String(s.clone())),
123                _ => {}
124            }
125        }
126        test_facts.push(facts);
127    }
128
129    test_facts
130}