1use crate::{Error, Result};
32use chrono::{DateTime, Duration, Utc};
33use mockforge_core::time_travel_now;
34use serde::{Deserialize, Serialize};
35use serde_json::Value;
36use std::collections::HashMap;
37use std::sync::Arc;
38use tokio::sync::RwLock;
39use tracing::{debug, info, warn};
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43#[serde(tag = "type", rename_all = "lowercase")]
44pub enum MutationTrigger {
45 Interval {
47 duration_seconds: u64,
49 },
50 AtTime {
52 hour: u8,
54 minute: u8,
56 },
57 FieldThreshold {
59 field: String,
61 threshold: Value,
63 operator: ComparisonOperator,
65 },
66}
67
68#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
70#[serde(rename_all = "lowercase")]
71pub enum ComparisonOperator {
72 Gt,
74 Lt,
76 Eq,
78 Gte,
80 Lte,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86#[serde(tag = "type", rename_all = "lowercase")]
87pub enum MutationOperation {
88 Set {
90 field: String,
92 value: Value,
94 },
95 Increment {
97 field: String,
99 amount: f64,
101 },
102 Decrement {
104 field: String,
106 amount: f64,
108 },
109 Transform {
111 field: String,
113 expression: String,
115 },
116 UpdateStatus {
118 status: String,
120 },
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct MutationRule {
126 pub id: String,
128 pub entity_name: String,
130 pub trigger: MutationTrigger,
132 pub operation: MutationOperation,
134 #[serde(default = "default_true")]
136 pub enabled: bool,
137 #[serde(default)]
139 pub description: Option<String>,
140 #[serde(default)]
142 pub condition: Option<String>,
143 #[serde(default)]
145 pub last_execution: Option<DateTime<Utc>>,
146 #[serde(default)]
148 pub next_execution: Option<DateTime<Utc>>,
149 #[serde(default)]
151 pub execution_count: usize,
152}
153
154fn default_true() -> bool {
155 true
156}
157
158impl MutationRule {
159 pub fn new(
161 id: String,
162 entity_name: String,
163 trigger: MutationTrigger,
164 operation: MutationOperation,
165 ) -> Self {
166 Self {
167 id,
168 entity_name,
169 trigger,
170 operation,
171 enabled: true,
172 description: None,
173 condition: None,
174 last_execution: None,
175 next_execution: None,
176 execution_count: 0,
177 }
178 }
179
180 pub fn calculate_next_execution(&self, from: DateTime<Utc>) -> Option<DateTime<Utc>> {
182 if !self.enabled {
183 return None;
184 }
185
186 match &self.trigger {
187 MutationTrigger::Interval { duration_seconds } => {
188 Some(from + Duration::seconds(*duration_seconds as i64))
189 }
190 MutationTrigger::AtTime { hour, minute } => {
191 let mut next =
193 from.date_naive().and_hms_opt(*hour as u32, *minute as u32, 0)?.and_utc();
194
195 if next <= from {
197 next += Duration::days(1);
198 }
199
200 Some(next)
201 }
202 MutationTrigger::FieldThreshold { .. } => {
203 None
205 }
206 }
207 }
208}
209
210pub struct MutationRuleManager {
212 rules: Arc<RwLock<HashMap<String, MutationRule>>>,
214}
215
216impl MutationRuleManager {
217 pub fn new() -> Self {
219 Self {
220 rules: Arc::new(RwLock::new(HashMap::new())),
221 }
222 }
223
224 pub async fn add_rule(&self, mut rule: MutationRule) -> Result<()> {
226 let now = time_travel_now();
228 rule.next_execution = rule.calculate_next_execution(now);
229
230 let rule_id = rule.id.clone();
231
232 let mut rules = self.rules.write().await;
233 rules.insert(rule_id.clone(), rule);
234
235 info!("Added mutation rule '{}' for entity '{}'", rule_id, rules[&rule_id].entity_name);
236 Ok(())
237 }
238
239 pub async fn remove_rule(&self, rule_id: &str) -> bool {
241 let mut rules = self.rules.write().await;
242 let removed = rules.remove(rule_id).is_some();
243
244 if removed {
245 info!("Removed mutation rule '{}'", rule_id);
246 }
247
248 removed
249 }
250
251 pub async fn get_rule(&self, rule_id: &str) -> Option<MutationRule> {
253 let rules = self.rules.read().await;
254 rules.get(rule_id).cloned()
255 }
256
257 pub async fn list_rules(&self) -> Vec<MutationRule> {
259 let rules = self.rules.read().await;
260 rules.values().cloned().collect()
261 }
262
263 pub async fn list_rules_for_entity(&self, entity_name: &str) -> Vec<MutationRule> {
265 let rules = self.rules.read().await;
266 rules.values().filter(|rule| rule.entity_name == entity_name).cloned().collect()
267 }
268
269 pub async fn set_rule_enabled(&self, rule_id: &str, enabled: bool) -> Result<()> {
271 let mut rules = self.rules.write().await;
272
273 if let Some(rule) = rules.get_mut(rule_id) {
274 rule.enabled = enabled;
275
276 if enabled {
278 let now = time_travel_now();
279 rule.next_execution = rule.calculate_next_execution(now);
280 } else {
281 rule.next_execution = None;
282 }
283
284 info!("Mutation rule '{}' {}", rule_id, if enabled { "enabled" } else { "disabled" });
285 Ok(())
286 } else {
287 Err(Error::generic(format!("Mutation rule '{}' not found", rule_id)))
288 }
289 }
290
291 pub async fn check_and_execute(
296 &self,
297 database: &dyn crate::database::VirtualDatabase,
298 registry: &crate::entities::EntityRegistry,
299 ) -> Result<usize> {
300 let now = time_travel_now();
301 let mut executed = 0;
302
303 let mut rules_to_execute = Vec::new();
305
306 {
307 let rules = self.rules.read().await;
308 for rule in rules.values() {
309 if !rule.enabled {
310 continue;
311 }
312
313 if let Some(next) = rule.next_execution {
314 if now >= next {
315 rules_to_execute.push(rule.id.clone());
316 }
317 }
318 }
319 }
320
321 for rule_id in rules_to_execute {
323 if let Err(e) = self.execute_rule(&rule_id, database, registry).await {
324 warn!("Error executing mutation rule '{}': {}", rule_id, e);
325 } else {
326 executed += 1;
327 }
328 }
329
330 Ok(executed)
331 }
332
333 fn evaluate_transformation_expression(
342 expression: &str,
343 record: &HashMap<String, Value>,
344 ) -> Result<Value> {
345 use regex::Regex;
346
347 let record_value: Value =
349 Value::Object(record.iter().map(|(k, v)| (k.clone(), v.clone())).collect());
350
351 let re = Regex::new(r"\{\{([^}]+)\}\}")
353 .map_err(|e| Error::generic(format!("Failed to compile regex: {}", e)))?;
354
355 let substituted = re.replace_all(expression, |caps: ®ex::Captures| {
356 let var_name = caps.get(1).unwrap().as_str().trim();
357 if let Some(value) = record.get(var_name) {
359 if let Some(s) = value.as_str() {
361 s.to_string()
362 } else if let Some(n) = value.as_f64() {
363 n.to_string()
364 } else if let Some(b) = value.as_bool() {
365 b.to_string()
366 } else {
367 value.to_string()
368 }
369 } else {
370 if var_name.starts_with('$') {
372 if let Ok(selector) = jsonpath::Selector::new(var_name) {
374 let results: Vec<_> = selector.find(&record_value).collect();
375 if let Some(first) = results.first() {
376 if let Some(s) = first.as_str() {
377 return s.to_string();
378 } else if let Some(n) = first.as_f64() {
379 return n.to_string();
380 } else if let Some(b) = first.as_bool() {
381 return b.to_string();
382 }
383 }
384 }
385 }
386 format!("{{{{{}}}}}", var_name) }
388 });
389
390 let substituted_str = substituted.to_string();
392
393 if substituted_str.contains('+')
395 || substituted_str.contains('-')
396 || substituted_str.contains('*')
397 || substituted_str.contains('/')
398 {
399 if let Ok(result) = Self::evaluate_math_expression(&substituted_str) {
401 return Ok(serde_json::json!(result));
402 }
403 }
404
405 if substituted_str.contains(".toUpperCase()") {
407 let base = substituted_str.replace(".toUpperCase()", "");
408 return Ok(Value::String(base.to_uppercase()));
409 }
410 if substituted_str.contains(".toLowerCase()") {
411 let base = substituted_str.replace(".toLowerCase()", "");
412 return Ok(Value::String(base.to_lowercase()));
413 }
414 if substituted_str.contains(".trim()") {
415 let base = substituted_str.replace(".trim()", "");
416 return Ok(Value::String(base.trim().to_string()));
417 }
418
419 Ok(Value::String(substituted_str))
421 }
422
423 fn evaluate_math_expression(expr: &str) -> Result<f64> {
428 let expr = expr.replace(' ', "");
433
434 let mut result = 0.0;
437 let mut current_num = String::new();
438 let mut last_op = '+';
439
440 for ch in expr.chars() {
441 match ch {
442 '+' | '-' | '*' | '/' => {
443 if !current_num.is_empty() {
444 let num: f64 = current_num.parse().map_err(|_| {
445 Error::generic(format!("Invalid number: {}", current_num))
446 })?;
447
448 match last_op {
449 '+' => result += num,
450 '-' => result -= num,
451 '*' => result *= num,
452 '/' => {
453 if num == 0.0 {
454 return Err(Error::generic("Division by zero".to_string()));
455 }
456 result /= num;
457 }
458 _ => {}
459 }
460
461 current_num.clear();
462 }
463 last_op = ch;
464 }
465 '0'..='9' | '.' => {
466 current_num.push(ch);
467 }
468 _ => {
469 return Err(Error::generic(format!("Invalid character in expression: {}", ch)));
470 }
471 }
472 }
473
474 if !current_num.is_empty() {
476 let num: f64 = current_num
477 .parse()
478 .map_err(|_| Error::generic(format!("Invalid number: {}", current_num)))?;
479
480 match last_op {
481 '+' => result += num,
482 '-' => result -= num,
483 '*' => result *= num,
484 '/' => {
485 if num == 0.0 {
486 return Err(Error::generic("Division by zero".to_string()));
487 }
488 result /= num;
489 }
490 _ => result = num, }
492 }
493
494 Ok(result)
495 }
496
497 fn evaluate_condition(condition: &str, record: &Value) -> Result<bool> {
506 if let Ok(selector) = jsonpath::Selector::new(condition) {
512 let results: Vec<_> = selector.find(record).collect();
514 if !results.is_empty() {
515 for result in results {
517 match result {
518 Value::Bool(b) => {
519 if *b {
520 return Ok(true);
521 }
522 }
523 Value::Null => continue,
524 Value::String(s) => {
525 if !s.is_empty() {
526 return Ok(true);
527 }
528 }
529 Value::Number(n) => {
530 if n.as_f64().map(|f| f != 0.0).unwrap_or(false) {
531 return Ok(true);
532 }
533 }
534 _ => return Ok(true), }
536 }
537 }
538 return Ok(false);
539 }
540
541 if condition.contains("==") {
544 let parts: Vec<&str> = condition.split("==").map(|s| s.trim()).collect();
545 if parts.len() == 2 {
546 let path = parts[0].trim();
547 let expected = parts[1].trim().trim_matches('\'').trim_matches('"');
548
549 if let Ok(selector) = jsonpath::Selector::new(path) {
550 let results: Vec<_> = selector.find(record).collect();
551 for result in results {
552 match result {
553 Value::String(s) if s == expected => return Ok(true),
554 Value::Number(n) => {
555 if let Ok(expected_num) = expected.parse::<f64>() {
556 if n.as_f64().map(|f| f == expected_num).unwrap_or(false) {
557 return Ok(true);
558 }
559 }
560 }
561 _ => {}
562 }
563 }
564 }
565 }
566 } else if condition.contains(">") {
567 let parts: Vec<&str> = condition.split(">").map(|s| s.trim()).collect();
568 if parts.len() == 2 {
569 let path = parts[0].trim();
570 if let Ok(expected_num) = parts[1].trim().parse::<f64>() {
571 if let Ok(selector) = jsonpath::Selector::new(path) {
572 let results: Vec<_> = selector.find(record).collect();
573 for result in results {
574 if let Value::Number(n) = result {
575 if n.as_f64().map(|f| f > expected_num).unwrap_or(false) {
576 return Ok(true);
577 }
578 }
579 }
580 }
581 }
582 }
583 } else if condition.contains("<") {
584 let parts: Vec<&str> = condition.split("<").map(|s| s.trim()).collect();
585 if parts.len() == 2 {
586 let path = parts[0].trim();
587 if let Ok(expected_num) = parts[1].trim().parse::<f64>() {
588 if let Ok(selector) = jsonpath::Selector::new(path) {
589 let results: Vec<_> = selector.find(record).collect();
590 for result in results {
591 if let Value::Number(n) = result {
592 if n.as_f64().map(|f| f < expected_num).unwrap_or(false) {
593 return Ok(true);
594 }
595 }
596 }
597 }
598 }
599 }
600 }
601
602 warn!("Could not evaluate condition '{}', treating as false", condition);
604 Ok(false)
605 }
606
607 async fn execute_rule(
609 &self,
610 rule_id: &str,
611 database: &dyn crate::database::VirtualDatabase,
612 registry: &crate::entities::EntityRegistry,
613 ) -> Result<()> {
614 let now = time_travel_now();
615
616 let rule = {
618 let rules = self.rules.read().await;
619 rules
620 .get(rule_id)
621 .ok_or_else(|| Error::generic(format!("Mutation rule '{}' not found", rule_id)))?
622 .clone()
623 };
624
625 let entity = registry
627 .get(&rule.entity_name)
628 .ok_or_else(|| Error::generic(format!("Entity '{}' not found", rule.entity_name)))?;
629
630 let table_name = entity.table_name();
631
632 let query = format!("SELECT * FROM {}", table_name);
634 let records = database.query(&query, &[]).await?;
635
636 let pk_field = entity.schema.primary_key.first().map(|s| s.as_str()).unwrap_or("id");
638
639 for record in records {
640 if let Some(ref condition) = rule.condition {
642 let record_value =
644 Value::Object(record.iter().map(|(k, v)| (k.clone(), v.clone())).collect());
645
646 if !MutationRuleManager::evaluate_condition(condition, &record_value)? {
650 debug!("Condition '{}' not met for record, skipping", condition);
651 continue;
652 }
653 }
654
655 let pk_value = record
657 .get(pk_field)
658 .ok_or_else(|| Error::generic(format!("Primary key '{}' not found", pk_field)))?;
659
660 match &rule.operation {
662 MutationOperation::Set { field, value } => {
663 let update_query =
664 format!("UPDATE {} SET {} = ? WHERE {} = ?", table_name, field, pk_field);
665 database.execute(&update_query, &[value.clone(), pk_value.clone()]).await?;
666 }
667 MutationOperation::Increment { field, amount } => {
668 if let Some(current) = record.get(field) {
670 let new_value = if let Some(num) = current.as_f64() {
671 Value::Number(
672 serde_json::Number::from_f64(num + amount)
673 .unwrap_or_else(|| serde_json::Number::from(0)),
674 )
675 } else if let Some(num) = current.as_i64() {
676 Value::Number(serde_json::Number::from(num + *amount as i64))
677 } else {
678 continue; };
680
681 let update_query = format!(
682 "UPDATE {} SET {} = ? WHERE {} = ?",
683 table_name, field, pk_field
684 );
685 database.execute(&update_query, &[new_value, pk_value.clone()]).await?;
686 }
687 }
688 MutationOperation::Decrement { field, amount } => {
689 if let Some(current) = record.get(field) {
691 let new_value = if let Some(num) = current.as_f64() {
692 Value::Number(
693 serde_json::Number::from_f64(num - amount)
694 .unwrap_or_else(|| serde_json::Number::from(0)),
695 )
696 } else if let Some(num) = current.as_i64() {
697 Value::Number(serde_json::Number::from(num - *amount as i64))
698 } else {
699 continue; };
701
702 let update_query = format!(
703 "UPDATE {} SET {} = ? WHERE {} = ?",
704 table_name, field, pk_field
705 );
706 database.execute(&update_query, &[new_value, pk_value.clone()]).await?;
707 }
708 }
709 MutationOperation::Transform { field, expression } => {
710 let transformed_value =
712 Self::evaluate_transformation_expression(expression, &record)?;
713
714 let update_query =
715 format!("UPDATE {} SET {} = ? WHERE {} = ?", table_name, field, pk_field);
716 database.execute(&update_query, &[transformed_value, pk_value.clone()]).await?;
717 }
718 MutationOperation::UpdateStatus { status } => {
719 let update_query =
720 format!("UPDATE {} SET status = ? WHERE {} = ?", table_name, pk_field);
721 database
722 .execute(&update_query, &[Value::String(status.clone()), pk_value.clone()])
723 .await?;
724 }
725 }
726 }
727
728 {
730 let mut rules = self.rules.write().await;
731 if let Some(rule) = rules.get_mut(rule_id) {
732 rule.last_execution = Some(now);
733 rule.execution_count += 1;
734
735 rule.next_execution = rule.calculate_next_execution(now);
737 }
738 }
739
740 info!("Executed mutation rule '{}' on entity '{}'", rule_id, rule.entity_name);
741 Ok(())
742 }
743}
744
745impl Default for MutationRuleManager {
746 fn default() -> Self {
747 Self::new()
748 }
749}
750
751#[cfg(test)]
752mod tests {
753 use super::*;
754
755 #[test]
757 fn test_mutation_trigger_interval_serialize() {
758 let trigger = MutationTrigger::Interval {
759 duration_seconds: 3600,
760 };
761 let json = serde_json::to_string(&trigger).unwrap();
762 assert!(json.contains("interval"));
763 assert!(json.contains("3600"));
764 }
765
766 #[test]
767 fn test_mutation_trigger_at_time_serialize() {
768 let trigger = MutationTrigger::AtTime {
769 hour: 9,
770 minute: 30,
771 };
772 let json = serde_json::to_string(&trigger).unwrap();
773 assert!(json.contains("attime"));
774 assert!(json.contains("\"hour\":9"));
775 }
776
777 #[test]
778 fn test_mutation_trigger_field_threshold_serialize() {
779 let trigger = MutationTrigger::FieldThreshold {
780 field: "age".to_string(),
781 threshold: serde_json::json!(100),
782 operator: ComparisonOperator::Gt,
783 };
784 let json = serde_json::to_string(&trigger).unwrap();
785 assert!(json.contains("fieldthreshold"));
786 assert!(json.contains("age"));
787 }
788
789 #[test]
790 fn test_mutation_trigger_clone() {
791 let trigger = MutationTrigger::Interval {
792 duration_seconds: 60,
793 };
794 let cloned = trigger.clone();
795 match cloned {
796 MutationTrigger::Interval { duration_seconds } => {
797 assert_eq!(duration_seconds, 60);
798 }
799 _ => panic!("Expected Interval variant"),
800 }
801 }
802
803 #[test]
804 fn test_mutation_trigger_debug() {
805 let trigger = MutationTrigger::Interval {
806 duration_seconds: 120,
807 };
808 let debug = format!("{:?}", trigger);
809 assert!(debug.contains("Interval"));
810 }
811
812 #[test]
814 fn test_mutation_operation_set() {
815 let op = MutationOperation::Set {
816 field: "status".to_string(),
817 value: serde_json::json!("active"),
818 };
819 let json = serde_json::to_string(&op).unwrap();
820 assert!(json.contains("set"));
821 assert!(json.contains("status"));
822 }
823
824 #[test]
825 fn test_mutation_operation_increment() {
826 let op = MutationOperation::Increment {
827 field: "count".to_string(),
828 amount: 5.0,
829 };
830 let json = serde_json::to_string(&op).unwrap();
831 assert!(json.contains("increment"));
832 assert!(json.contains("count"));
833 }
834
835 #[test]
836 fn test_mutation_operation_decrement() {
837 let op = MutationOperation::Decrement {
838 field: "balance".to_string(),
839 amount: 10.5,
840 };
841 let json = serde_json::to_string(&op).unwrap();
842 assert!(json.contains("decrement"));
843 assert!(json.contains("balance"));
844 }
845
846 #[test]
847 fn test_mutation_operation_transform() {
848 let op = MutationOperation::Transform {
849 field: "value".to_string(),
850 expression: "{{value}} * 2".to_string(),
851 };
852 let json = serde_json::to_string(&op).unwrap();
853 assert!(json.contains("transform"));
854 }
855
856 #[test]
857 fn test_mutation_operation_update_status() {
858 let op = MutationOperation::UpdateStatus {
859 status: "completed".to_string(),
860 };
861 let json = serde_json::to_string(&op).unwrap();
862 assert!(json.contains("updatestatus"));
863 assert!(json.contains("completed"));
864 }
865
866 #[test]
867 fn test_mutation_operation_clone() {
868 let op = MutationOperation::Set {
869 field: "test".to_string(),
870 value: serde_json::json!(42),
871 };
872 let cloned = op.clone();
873 match cloned {
874 MutationOperation::Set { field, value } => {
875 assert_eq!(field, "test");
876 assert_eq!(value, serde_json::json!(42));
877 }
878 _ => panic!("Expected Set variant"),
879 }
880 }
881
882 #[test]
884 fn test_mutation_rule_creation() {
885 let rule = MutationRule::new(
886 "test-1".to_string(),
887 "User".to_string(),
888 MutationTrigger::Interval {
889 duration_seconds: 3600,
890 },
891 MutationOperation::Increment {
892 field: "count".to_string(),
893 amount: 1.0,
894 },
895 );
896
897 assert_eq!(rule.id, "test-1");
898 assert_eq!(rule.entity_name, "User");
899 assert!(rule.enabled);
900 assert!(rule.description.is_none());
901 assert!(rule.condition.is_none());
902 assert!(rule.last_execution.is_none());
903 assert_eq!(rule.execution_count, 0);
904 }
905
906 #[test]
907 fn test_mutation_rule_defaults() {
908 let rule = MutationRule::new(
909 "rule-1".to_string(),
910 "Order".to_string(),
911 MutationTrigger::Interval {
912 duration_seconds: 60,
913 },
914 MutationOperation::Set {
915 field: "status".to_string(),
916 value: serde_json::json!("processed"),
917 },
918 );
919
920 assert!(rule.enabled);
922 assert!(rule.description.is_none());
923 assert!(rule.condition.is_none());
924 assert!(rule.last_execution.is_none());
925 assert_eq!(rule.execution_count, 0);
926 }
927
928 #[test]
929 fn test_mutation_rule_clone() {
930 let rule = MutationRule::new(
931 "clone-test".to_string(),
932 "Entity".to_string(),
933 MutationTrigger::Interval {
934 duration_seconds: 100,
935 },
936 MutationOperation::Increment {
937 field: "counter".to_string(),
938 amount: 1.0,
939 },
940 );
941
942 let cloned = rule.clone();
943 assert_eq!(rule.id, cloned.id);
944 assert_eq!(rule.entity_name, cloned.entity_name);
945 assert_eq!(rule.enabled, cloned.enabled);
946 }
947
948 #[test]
949 fn test_mutation_rule_debug() {
950 let rule = MutationRule::new(
951 "debug-rule".to_string(),
952 "Test".to_string(),
953 MutationTrigger::Interval {
954 duration_seconds: 10,
955 },
956 MutationOperation::Set {
957 field: "f".to_string(),
958 value: serde_json::json!("v"),
959 },
960 );
961
962 let debug = format!("{:?}", rule);
963 assert!(debug.contains("MutationRule"));
964 assert!(debug.contains("debug-rule"));
965 }
966
967 #[test]
968 fn test_mutation_trigger_interval() {
969 let rule = MutationRule::new(
970 "test-1".to_string(),
971 "User".to_string(),
972 MutationTrigger::Interval {
973 duration_seconds: 3600,
974 },
975 MutationOperation::Set {
976 field: "status".to_string(),
977 value: serde_json::json!("active"),
978 },
979 );
980
981 let now = Utc::now();
982 let next = rule.calculate_next_execution(now).unwrap();
983 let duration = next - now;
984
985 assert!(duration.num_seconds() >= 3599 && duration.num_seconds() <= 3601);
987 }
988
989 #[test]
990 fn test_mutation_rule_calculate_next_execution_disabled() {
991 let mut rule = MutationRule::new(
992 "disabled-rule".to_string(),
993 "Entity".to_string(),
994 MutationTrigger::Interval {
995 duration_seconds: 60,
996 },
997 MutationOperation::Set {
998 field: "f".to_string(),
999 value: serde_json::json!("v"),
1000 },
1001 );
1002 rule.enabled = false;
1003
1004 let now = Utc::now();
1005 assert!(rule.calculate_next_execution(now).is_none());
1006 }
1007
1008 #[test]
1009 fn test_mutation_rule_calculate_next_execution_field_threshold() {
1010 let rule = MutationRule::new(
1011 "threshold-rule".to_string(),
1012 "Entity".to_string(),
1013 MutationTrigger::FieldThreshold {
1014 field: "value".to_string(),
1015 threshold: serde_json::json!(100),
1016 operator: ComparisonOperator::Gt,
1017 },
1018 MutationOperation::Set {
1019 field: "f".to_string(),
1020 value: serde_json::json!("v"),
1021 },
1022 );
1023
1024 let now = Utc::now();
1026 assert!(rule.calculate_next_execution(now).is_none());
1027 }
1028
1029 #[tokio::test]
1031 async fn test_mutation_rule_manager() {
1032 let manager = MutationRuleManager::new();
1033
1034 let rule = MutationRule::new(
1035 "test-1".to_string(),
1036 "User".to_string(),
1037 MutationTrigger::Interval {
1038 duration_seconds: 3600,
1039 },
1040 MutationOperation::Increment {
1041 field: "count".to_string(),
1042 amount: 1.0,
1043 },
1044 );
1045
1046 manager.add_rule(rule).await.unwrap();
1047
1048 let rules = manager.list_rules().await;
1049 assert_eq!(rules.len(), 1);
1050 assert_eq!(rules[0].id, "test-1");
1051 }
1052
1053 #[tokio::test]
1054 async fn test_mutation_rule_manager_new() {
1055 let manager = MutationRuleManager::new();
1056 let rules = manager.list_rules().await;
1057 assert!(rules.is_empty());
1058 }
1059
1060 #[tokio::test]
1061 async fn test_mutation_rule_manager_default() {
1062 let manager = MutationRuleManager::default();
1063 let rules = manager.list_rules().await;
1064 assert!(rules.is_empty());
1065 }
1066
1067 #[tokio::test]
1068 async fn test_mutation_rule_manager_add_and_get_rule() {
1069 let manager = MutationRuleManager::new();
1070
1071 let rule = MutationRule::new(
1072 "get-test".to_string(),
1073 "Order".to_string(),
1074 MutationTrigger::Interval {
1075 duration_seconds: 60,
1076 },
1077 MutationOperation::Set {
1078 field: "status".to_string(),
1079 value: serde_json::json!("done"),
1080 },
1081 );
1082
1083 manager.add_rule(rule).await.unwrap();
1084
1085 let retrieved = manager.get_rule("get-test").await;
1086 assert!(retrieved.is_some());
1087 assert_eq!(retrieved.unwrap().id, "get-test");
1088 }
1089
1090 #[tokio::test]
1091 async fn test_mutation_rule_manager_get_nonexistent() {
1092 let manager = MutationRuleManager::new();
1093 let retrieved = manager.get_rule("nonexistent").await;
1094 assert!(retrieved.is_none());
1095 }
1096
1097 #[tokio::test]
1098 async fn test_mutation_rule_manager_remove_rule() {
1099 let manager = MutationRuleManager::new();
1100
1101 let rule = MutationRule::new(
1102 "remove-test".to_string(),
1103 "Entity".to_string(),
1104 MutationTrigger::Interval {
1105 duration_seconds: 60,
1106 },
1107 MutationOperation::Set {
1108 field: "f".to_string(),
1109 value: serde_json::json!("v"),
1110 },
1111 );
1112
1113 manager.add_rule(rule).await.unwrap();
1114 assert!(manager.get_rule("remove-test").await.is_some());
1115
1116 let removed = manager.remove_rule("remove-test").await;
1117 assert!(removed);
1118 assert!(manager.get_rule("remove-test").await.is_none());
1119 }
1120
1121 #[tokio::test]
1122 async fn test_mutation_rule_manager_remove_nonexistent() {
1123 let manager = MutationRuleManager::new();
1124 let removed = manager.remove_rule("nonexistent").await;
1125 assert!(!removed);
1126 }
1127
1128 #[tokio::test]
1129 async fn test_mutation_rule_manager_list_rules() {
1130 let manager = MutationRuleManager::new();
1131
1132 for i in 1..=3 {
1134 let rule = MutationRule::new(
1135 format!("rule-{}", i),
1136 "Entity".to_string(),
1137 MutationTrigger::Interval {
1138 duration_seconds: 60,
1139 },
1140 MutationOperation::Set {
1141 field: "f".to_string(),
1142 value: serde_json::json!("v"),
1143 },
1144 );
1145 manager.add_rule(rule).await.unwrap();
1146 }
1147
1148 let rules = manager.list_rules().await;
1149 assert_eq!(rules.len(), 3);
1150 }
1151
1152 #[tokio::test]
1153 async fn test_mutation_rule_manager_list_rules_for_entity() {
1154 let manager = MutationRuleManager::new();
1155
1156 let rule1 = MutationRule::new(
1158 "user-rule".to_string(),
1159 "User".to_string(),
1160 MutationTrigger::Interval {
1161 duration_seconds: 60,
1162 },
1163 MutationOperation::Set {
1164 field: "f".to_string(),
1165 value: serde_json::json!("v"),
1166 },
1167 );
1168
1169 let rule2 = MutationRule::new(
1170 "order-rule".to_string(),
1171 "Order".to_string(),
1172 MutationTrigger::Interval {
1173 duration_seconds: 60,
1174 },
1175 MutationOperation::Set {
1176 field: "f".to_string(),
1177 value: serde_json::json!("v"),
1178 },
1179 );
1180
1181 let rule3 = MutationRule::new(
1182 "user-rule-2".to_string(),
1183 "User".to_string(),
1184 MutationTrigger::Interval {
1185 duration_seconds: 120,
1186 },
1187 MutationOperation::Increment {
1188 field: "count".to_string(),
1189 amount: 1.0,
1190 },
1191 );
1192
1193 manager.add_rule(rule1).await.unwrap();
1194 manager.add_rule(rule2).await.unwrap();
1195 manager.add_rule(rule3).await.unwrap();
1196
1197 let user_rules = manager.list_rules_for_entity("User").await;
1198 assert_eq!(user_rules.len(), 2);
1199
1200 let order_rules = manager.list_rules_for_entity("Order").await;
1201 assert_eq!(order_rules.len(), 1);
1202
1203 let product_rules = manager.list_rules_for_entity("Product").await;
1204 assert!(product_rules.is_empty());
1205 }
1206
1207 #[tokio::test]
1208 async fn test_mutation_rule_manager_set_rule_enabled() {
1209 let manager = MutationRuleManager::new();
1210
1211 let rule = MutationRule::new(
1212 "enable-test".to_string(),
1213 "Entity".to_string(),
1214 MutationTrigger::Interval {
1215 duration_seconds: 60,
1216 },
1217 MutationOperation::Set {
1218 field: "f".to_string(),
1219 value: serde_json::json!("v"),
1220 },
1221 );
1222
1223 manager.add_rule(rule).await.unwrap();
1224
1225 manager.set_rule_enabled("enable-test", false).await.unwrap();
1227 let disabled_rule = manager.get_rule("enable-test").await.unwrap();
1228 assert!(!disabled_rule.enabled);
1229 assert!(disabled_rule.next_execution.is_none());
1230
1231 manager.set_rule_enabled("enable-test", true).await.unwrap();
1233 let enabled_rule = manager.get_rule("enable-test").await.unwrap();
1234 assert!(enabled_rule.enabled);
1235 assert!(enabled_rule.next_execution.is_some());
1236 }
1237
1238 #[tokio::test]
1239 async fn test_mutation_rule_manager_set_rule_enabled_nonexistent() {
1240 let manager = MutationRuleManager::new();
1241 let result = manager.set_rule_enabled("nonexistent", true).await;
1242 assert!(result.is_err());
1243 }
1244
1245 #[test]
1247 fn test_comparison_operator_variants() {
1248 let operators = vec![
1249 ComparisonOperator::Gt,
1250 ComparisonOperator::Lt,
1251 ComparisonOperator::Eq,
1252 ComparisonOperator::Gte,
1253 ComparisonOperator::Lte,
1254 ];
1255
1256 for op in operators {
1257 let json = serde_json::to_string(&op).unwrap();
1258 assert!(!json.is_empty());
1259 }
1260 }
1261
1262 #[test]
1263 fn test_comparison_operator_clone() {
1264 let op = ComparisonOperator::Gt;
1265 let cloned = op;
1266 assert!(matches!(cloned, ComparisonOperator::Gt));
1267 }
1268
1269 #[test]
1270 fn test_comparison_operator_debug() {
1271 let op = ComparisonOperator::Lt;
1272 let debug = format!("{:?}", op);
1273 assert!(debug.contains("Lt"));
1274 }
1275
1276 #[test]
1277 fn test_comparison_operator_eq() {
1278 let op1 = ComparisonOperator::Gt;
1279 let op2 = ComparisonOperator::Gt;
1280 let op3 = ComparisonOperator::Lt;
1281 assert_eq!(op1, op2);
1282 assert_ne!(op1, op3);
1283 }
1284
1285 #[test]
1286 fn test_comparison_operator_serialize() {
1287 assert_eq!(serde_json::to_string(&ComparisonOperator::Gt).unwrap(), "\"gt\"");
1288 assert_eq!(serde_json::to_string(&ComparisonOperator::Lt).unwrap(), "\"lt\"");
1289 assert_eq!(serde_json::to_string(&ComparisonOperator::Eq).unwrap(), "\"eq\"");
1290 assert_eq!(serde_json::to_string(&ComparisonOperator::Gte).unwrap(), "\"gte\"");
1291 assert_eq!(serde_json::to_string(&ComparisonOperator::Lte).unwrap(), "\"lte\"");
1292 }
1293}