ricecoder_learning/
rule_persistence_property.rs

1/// Property-based tests for rule persistence correctness
2/// **Feature: ricecoder-learning, Property 2: Rule Persistence Correctness**
3/// **Validates: Requirements 1.3**
4
5#[cfg(test)]
6mod tests {
7    use crate::{Rule, RuleScope, RuleSource, RuleStorage};
8
9    #[tokio::test]
10    async fn test_rule_persistence_correctness_single() {
11        // Test that a single rule can be stored and retrieved with identical content
12        let storage = RuleStorage::new(RuleScope::Session);
13
14        let rule = Rule::new(
15            RuleScope::Session,
16            "test_pattern".to_string(),
17            "test_action".to_string(),
18            RuleSource::Learned,
19        );
20
21        let rule_id = rule.id.clone();
22
23        // Store the rule
24        let store_result = storage.store_rule(rule.clone()).await;
25        assert!(store_result.is_ok(), "Failed to store rule");
26        assert_eq!(store_result.unwrap(), rule_id, "Returned rule ID should match");
27
28        // Retrieve the rule
29        let retrieve_result = storage.get_rule(&rule_id).await;
30        assert!(retrieve_result.is_ok(), "Failed to retrieve rule");
31
32        let retrieved = retrieve_result.unwrap();
33
34        // Verify all fields match
35        assert_eq!(retrieved.id, rule.id, "Rule ID should match");
36        assert_eq!(retrieved.scope, rule.scope, "Rule scope should match");
37        assert_eq!(retrieved.pattern, rule.pattern, "Rule pattern should match");
38        assert_eq!(retrieved.action, rule.action, "Rule action should match");
39        assert_eq!(retrieved.source, rule.source, "Rule source should match");
40        assert_eq!(retrieved.version, rule.version, "Rule version should match");
41        assert_eq!(retrieved.confidence, rule.confidence, "Rule confidence should match");
42        assert_eq!(retrieved.usage_count, rule.usage_count, "Rule usage count should match");
43        assert_eq!(retrieved.success_rate, rule.success_rate, "Rule success rate should match");
44        assert_eq!(retrieved.metadata, rule.metadata, "Rule metadata should match");
45    }
46
47    #[tokio::test]
48    async fn test_multiple_rules_persistence() {
49        // Test that multiple rules can be stored and retrieved independently
50        let storage = RuleStorage::new(RuleScope::Session);
51
52        let mut rules = Vec::new();
53        for i in 0..5 {
54            let mut rule = Rule::new(
55                RuleScope::Session,
56                format!("pattern_{}", i),
57                format!("action_{}", i),
58                RuleSource::Learned,
59            );
60            rule.confidence = 0.5 + (i as f32 * 0.1);
61            rule.usage_count = i as u64 * 10;
62            rules.push(rule);
63        }
64
65        // Store all rules
66        let mut rule_ids = Vec::new();
67        for rule in &rules {
68            let result = storage.store_rule(rule.clone()).await;
69            assert!(result.is_ok(), "Failed to store rule");
70            rule_ids.push(result.unwrap());
71        }
72
73        // Verify all rules can be retrieved
74        for (i, rule_id) in rule_ids.iter().enumerate() {
75            let result = storage.get_rule(rule_id).await;
76            assert!(result.is_ok(), "Failed to retrieve rule {}", i);
77
78            let retrieved = result.unwrap();
79            let original = &rules[i];
80
81            assert_eq!(retrieved.id, original.id, "Rule {} ID should match", i);
82            assert_eq!(retrieved.pattern, original.pattern, "Rule {} pattern should match", i);
83            assert_eq!(retrieved.action, original.action, "Rule {} action should match", i);
84            assert_eq!(retrieved.confidence, original.confidence, "Rule {} confidence should match", i);
85        }
86    }
87
88    #[tokio::test]
89    async fn test_rule_deletion_persistence() {
90        // Test that deleted rules cannot be retrieved
91        let storage = RuleStorage::new(RuleScope::Session);
92
93        let rule = Rule::new(
94            RuleScope::Session,
95            "test_pattern".to_string(),
96            "test_action".to_string(),
97            RuleSource::Learned,
98        );
99
100        let rule_id = rule.id.clone();
101
102        // Store the rule
103        storage.store_rule(rule).await.unwrap();
104
105        // Verify it exists
106        let exists = storage.get_rule(&rule_id).await;
107        assert!(exists.is_ok(), "Rule should exist after storage");
108
109        // Delete the rule
110        let delete_result = storage.delete_rule(&rule_id).await;
111        assert!(delete_result.is_ok(), "Failed to delete rule");
112
113        // Verify it no longer exists
114        let not_found = storage.get_rule(&rule_id).await;
115        assert!(not_found.is_err(), "Rule should not exist after deletion");
116    }
117
118    #[tokio::test]
119    async fn test_list_rules_completeness() {
120        // Test that listing returns all stored rules
121        let storage = RuleStorage::new(RuleScope::Session);
122
123        let mut rules = Vec::new();
124        for i in 0..5 {
125            let rule = Rule::new(
126                RuleScope::Session,
127                format!("pattern_{}", i),
128                format!("action_{}", i),
129                RuleSource::Learned,
130            );
131            rules.push(rule);
132        }
133
134        // Store all rules
135        for rule in &rules {
136            storage.store_rule(rule.clone()).await.unwrap();
137        }
138
139        // List all rules
140        let listed = storage.list_rules().await.unwrap();
141
142        // Verify count matches
143        assert_eq!(listed.len(), rules.len(), "Listed rules count should match stored count");
144
145        // Verify all rules are present
146        for original in &rules {
147            let found = listed.iter().find(|r| r.id == original.id);
148            assert!(found.is_some(), "Rule {} should be in list", original.id);
149        }
150    }
151
152    #[tokio::test]
153    async fn test_rule_count_accuracy() {
154        // Test that rule count is accurate
155        let storage = RuleStorage::new(RuleScope::Session);
156
157        let mut rules = Vec::new();
158        for i in 0..5 {
159            let rule = Rule::new(
160                RuleScope::Session,
161                format!("pattern_{}", i),
162                format!("action_{}", i),
163                RuleSource::Learned,
164            );
165            rules.push(rule);
166        }
167
168        // Store all rules
169        for rule in &rules {
170            storage.store_rule(rule.clone()).await.unwrap();
171        }
172
173        // Check count
174        let count = storage.rule_count().await.unwrap();
175        assert_eq!(count, rules.len(), "Rule count should match stored count");
176    }
177
178    #[tokio::test]
179    async fn test_clear_all_completeness() {
180        // Test that clearing all rules removes all stored rules
181        let storage = RuleStorage::new(RuleScope::Session);
182
183        let mut rules = Vec::new();
184        for i in 0..5 {
185            let rule = Rule::new(
186                RuleScope::Session,
187                format!("pattern_{}", i),
188                format!("action_{}", i),
189                RuleSource::Learned,
190            );
191            rules.push(rule);
192        }
193
194        // Store all rules
195        for rule in &rules {
196            storage.store_rule(rule.clone()).await.unwrap();
197        }
198
199        // Verify rules exist
200        let count_before = storage.rule_count().await.unwrap();
201        assert_eq!(count_before, rules.len(), "Rules should be stored");
202
203        // Clear all
204        storage.clear_all().await.unwrap();
205
206        // Verify all rules are gone
207        let count_after = storage.rule_count().await.unwrap();
208        assert_eq!(count_after, 0, "All rules should be cleared");
209    }
210
211    #[tokio::test]
212    async fn test_scope_filtering_correctness() {
213        // Test that filtering by scope returns only rules in that scope
214        let storage = RuleStorage::new(RuleScope::Session);
215
216        let mut rules = Vec::new();
217        for i in 0..5 {
218            let rule = Rule::new(
219                RuleScope::Session,
220                format!("pattern_{}", i),
221                format!("action_{}", i),
222                RuleSource::Learned,
223            );
224            rules.push(rule);
225        }
226
227        // Store all rules (all will be Session scope)
228        for rule in &rules {
229            storage.store_rule(rule.clone()).await.unwrap();
230        }
231
232        // Filter by Session scope
233        let filtered = storage.filter_by_scope(RuleScope::Session).await.unwrap();
234
235        // All rules should be returned since they're all Session scope
236        assert_eq!(filtered.len(), rules.len(), "All rules should match Session scope");
237
238        // Verify all returned rules have Session scope
239        for rule in &filtered {
240            assert_eq!(rule.scope, RuleScope::Session, "Filtered rule should have Session scope");
241        }
242    }
243}