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: serde_json::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: serde_json::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(crate::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, serde_json::Value>,
344 ) -> Result<serde_json::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(serde_json::Value::String(base.to_uppercase()));
409 }
410 if substituted_str.contains(".toLowerCase()") {
411 let base = substituted_str.replace(".toLowerCase()", "");
412 return Ok(serde_json::Value::String(base.to_lowercase()));
413 }
414 if substituted_str.contains(".trim()") {
415 let base = substituted_str.replace(".trim()", "");
416 return Ok(serde_json::Value::String(base.trim().to_string()));
417 }
418
419 Ok(serde_json::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 serde_json::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 serde_json::Value::Number(serde_json::Number::from(
677 num + *amount as i64,
678 ))
679 } else {
680 continue; };
682
683 let update_query = format!(
684 "UPDATE {} SET {} = ? WHERE {} = ?",
685 table_name, field, pk_field
686 );
687 database.execute(&update_query, &[new_value, pk_value.clone()]).await?;
688 }
689 }
690 MutationOperation::Decrement { field, amount } => {
691 if let Some(current) = record.get(field) {
693 let new_value = if let Some(num) = current.as_f64() {
694 serde_json::Value::Number(
695 serde_json::Number::from_f64(num - amount)
696 .unwrap_or_else(|| serde_json::Number::from(0)),
697 )
698 } else if let Some(num) = current.as_i64() {
699 serde_json::Value::Number(serde_json::Number::from(
700 num - *amount as i64,
701 ))
702 } else {
703 continue; };
705
706 let update_query = format!(
707 "UPDATE {} SET {} = ? WHERE {} = ?",
708 table_name, field, pk_field
709 );
710 database.execute(&update_query, &[new_value, pk_value.clone()]).await?;
711 }
712 }
713 MutationOperation::Transform { field, expression } => {
714 let transformed_value =
716 Self::evaluate_transformation_expression(expression, &record)?;
717
718 let update_query =
719 format!("UPDATE {} SET {} = ? WHERE {} = ?", table_name, field, pk_field);
720 database.execute(&update_query, &[transformed_value, pk_value.clone()]).await?;
721 }
722 MutationOperation::UpdateStatus { status } => {
723 let update_query =
724 format!("UPDATE {} SET status = ? WHERE {} = ?", table_name, pk_field);
725 database
726 .execute(
727 &update_query,
728 &[serde_json::Value::String(status.clone()), pk_value.clone()],
729 )
730 .await?;
731 }
732 }
733 }
734
735 {
737 let mut rules = self.rules.write().await;
738 if let Some(rule) = rules.get_mut(rule_id) {
739 rule.last_execution = Some(now);
740 rule.execution_count += 1;
741
742 rule.next_execution = rule.calculate_next_execution(now);
744 }
745 }
746
747 info!("Executed mutation rule '{}' on entity '{}'", rule_id, rule.entity_name);
748 Ok(())
749 }
750}
751
752impl Default for MutationRuleManager {
753 fn default() -> Self {
754 Self::new()
755 }
756}
757
758#[cfg(test)]
759mod tests {
760 use super::*;
761
762 #[test]
764 fn test_mutation_trigger_interval_serialize() {
765 let trigger = MutationTrigger::Interval {
766 duration_seconds: 3600,
767 };
768 let json = serde_json::to_string(&trigger).unwrap();
769 assert!(json.contains("interval"));
770 assert!(json.contains("3600"));
771 }
772
773 #[test]
774 fn test_mutation_trigger_at_time_serialize() {
775 let trigger = MutationTrigger::AtTime {
776 hour: 9,
777 minute: 30,
778 };
779 let json = serde_json::to_string(&trigger).unwrap();
780 assert!(json.contains("attime"));
781 assert!(json.contains("\"hour\":9"));
782 }
783
784 #[test]
785 fn test_mutation_trigger_field_threshold_serialize() {
786 let trigger = MutationTrigger::FieldThreshold {
787 field: "age".to_string(),
788 threshold: serde_json::json!(100),
789 operator: ComparisonOperator::Gt,
790 };
791 let json = serde_json::to_string(&trigger).unwrap();
792 assert!(json.contains("fieldthreshold"));
793 assert!(json.contains("age"));
794 }
795
796 #[test]
797 fn test_mutation_trigger_clone() {
798 let trigger = MutationTrigger::Interval {
799 duration_seconds: 60,
800 };
801 let cloned = trigger.clone();
802 match cloned {
803 MutationTrigger::Interval { duration_seconds } => {
804 assert_eq!(duration_seconds, 60);
805 }
806 _ => panic!("Expected Interval variant"),
807 }
808 }
809
810 #[test]
811 fn test_mutation_trigger_debug() {
812 let trigger = MutationTrigger::Interval {
813 duration_seconds: 120,
814 };
815 let debug = format!("{:?}", trigger);
816 assert!(debug.contains("Interval"));
817 }
818
819 #[test]
821 fn test_mutation_operation_set() {
822 let op = MutationOperation::Set {
823 field: "status".to_string(),
824 value: serde_json::json!("active"),
825 };
826 let json = serde_json::to_string(&op).unwrap();
827 assert!(json.contains("set"));
828 assert!(json.contains("status"));
829 }
830
831 #[test]
832 fn test_mutation_operation_increment() {
833 let op = MutationOperation::Increment {
834 field: "count".to_string(),
835 amount: 5.0,
836 };
837 let json = serde_json::to_string(&op).unwrap();
838 assert!(json.contains("increment"));
839 assert!(json.contains("count"));
840 }
841
842 #[test]
843 fn test_mutation_operation_decrement() {
844 let op = MutationOperation::Decrement {
845 field: "balance".to_string(),
846 amount: 10.5,
847 };
848 let json = serde_json::to_string(&op).unwrap();
849 assert!(json.contains("decrement"));
850 assert!(json.contains("balance"));
851 }
852
853 #[test]
854 fn test_mutation_operation_transform() {
855 let op = MutationOperation::Transform {
856 field: "value".to_string(),
857 expression: "{{value}} * 2".to_string(),
858 };
859 let json = serde_json::to_string(&op).unwrap();
860 assert!(json.contains("transform"));
861 }
862
863 #[test]
864 fn test_mutation_operation_update_status() {
865 let op = MutationOperation::UpdateStatus {
866 status: "completed".to_string(),
867 };
868 let json = serde_json::to_string(&op).unwrap();
869 assert!(json.contains("updatestatus"));
870 assert!(json.contains("completed"));
871 }
872
873 #[test]
874 fn test_mutation_operation_clone() {
875 let op = MutationOperation::Set {
876 field: "test".to_string(),
877 value: serde_json::json!(42),
878 };
879 let cloned = op.clone();
880 match cloned {
881 MutationOperation::Set { field, value } => {
882 assert_eq!(field, "test");
883 assert_eq!(value, serde_json::json!(42));
884 }
885 _ => panic!("Expected Set variant"),
886 }
887 }
888
889 #[test]
891 fn test_mutation_rule_creation() {
892 let rule = MutationRule::new(
893 "test-1".to_string(),
894 "User".to_string(),
895 MutationTrigger::Interval {
896 duration_seconds: 3600,
897 },
898 MutationOperation::Increment {
899 field: "count".to_string(),
900 amount: 1.0,
901 },
902 );
903
904 assert_eq!(rule.id, "test-1");
905 assert_eq!(rule.entity_name, "User");
906 assert!(rule.enabled);
907 assert!(rule.description.is_none());
908 assert!(rule.condition.is_none());
909 assert!(rule.last_execution.is_none());
910 assert_eq!(rule.execution_count, 0);
911 }
912
913 #[test]
914 fn test_mutation_rule_defaults() {
915 let rule = MutationRule::new(
916 "rule-1".to_string(),
917 "Order".to_string(),
918 MutationTrigger::Interval {
919 duration_seconds: 60,
920 },
921 MutationOperation::Set {
922 field: "status".to_string(),
923 value: serde_json::json!("processed"),
924 },
925 );
926
927 assert!(rule.enabled);
929 assert!(rule.description.is_none());
930 assert!(rule.condition.is_none());
931 assert!(rule.last_execution.is_none());
932 assert_eq!(rule.execution_count, 0);
933 }
934
935 #[test]
936 fn test_mutation_rule_clone() {
937 let rule = MutationRule::new(
938 "clone-test".to_string(),
939 "Entity".to_string(),
940 MutationTrigger::Interval {
941 duration_seconds: 100,
942 },
943 MutationOperation::Increment {
944 field: "counter".to_string(),
945 amount: 1.0,
946 },
947 );
948
949 let cloned = rule.clone();
950 assert_eq!(rule.id, cloned.id);
951 assert_eq!(rule.entity_name, cloned.entity_name);
952 assert_eq!(rule.enabled, cloned.enabled);
953 }
954
955 #[test]
956 fn test_mutation_rule_debug() {
957 let rule = MutationRule::new(
958 "debug-rule".to_string(),
959 "Test".to_string(),
960 MutationTrigger::Interval {
961 duration_seconds: 10,
962 },
963 MutationOperation::Set {
964 field: "f".to_string(),
965 value: serde_json::json!("v"),
966 },
967 );
968
969 let debug = format!("{:?}", rule);
970 assert!(debug.contains("MutationRule"));
971 assert!(debug.contains("debug-rule"));
972 }
973
974 #[test]
975 fn test_mutation_trigger_interval() {
976 let rule = MutationRule::new(
977 "test-1".to_string(),
978 "User".to_string(),
979 MutationTrigger::Interval {
980 duration_seconds: 3600,
981 },
982 MutationOperation::Set {
983 field: "status".to_string(),
984 value: serde_json::json!("active"),
985 },
986 );
987
988 let now = Utc::now();
989 let next = rule.calculate_next_execution(now).unwrap();
990 let duration = next - now;
991
992 assert!(duration.num_seconds() >= 3599 && duration.num_seconds() <= 3601);
994 }
995
996 #[test]
997 fn test_mutation_rule_calculate_next_execution_disabled() {
998 let mut rule = MutationRule::new(
999 "disabled-rule".to_string(),
1000 "Entity".to_string(),
1001 MutationTrigger::Interval {
1002 duration_seconds: 60,
1003 },
1004 MutationOperation::Set {
1005 field: "f".to_string(),
1006 value: serde_json::json!("v"),
1007 },
1008 );
1009 rule.enabled = false;
1010
1011 let now = Utc::now();
1012 assert!(rule.calculate_next_execution(now).is_none());
1013 }
1014
1015 #[test]
1016 fn test_mutation_rule_calculate_next_execution_field_threshold() {
1017 let rule = MutationRule::new(
1018 "threshold-rule".to_string(),
1019 "Entity".to_string(),
1020 MutationTrigger::FieldThreshold {
1021 field: "value".to_string(),
1022 threshold: serde_json::json!(100),
1023 operator: ComparisonOperator::Gt,
1024 },
1025 MutationOperation::Set {
1026 field: "f".to_string(),
1027 value: serde_json::json!("v"),
1028 },
1029 );
1030
1031 let now = Utc::now();
1033 assert!(rule.calculate_next_execution(now).is_none());
1034 }
1035
1036 #[tokio::test]
1038 async fn test_mutation_rule_manager() {
1039 let manager = MutationRuleManager::new();
1040
1041 let rule = MutationRule::new(
1042 "test-1".to_string(),
1043 "User".to_string(),
1044 MutationTrigger::Interval {
1045 duration_seconds: 3600,
1046 },
1047 MutationOperation::Increment {
1048 field: "count".to_string(),
1049 amount: 1.0,
1050 },
1051 );
1052
1053 manager.add_rule(rule).await.unwrap();
1054
1055 let rules = manager.list_rules().await;
1056 assert_eq!(rules.len(), 1);
1057 assert_eq!(rules[0].id, "test-1");
1058 }
1059
1060 #[test]
1061 fn test_mutation_rule_manager_new() {
1062 let manager = MutationRuleManager::new();
1063 assert!(true);
1065 }
1066
1067 #[test]
1068 fn test_mutation_rule_manager_default() {
1069 let manager = MutationRuleManager::default();
1070 assert!(true);
1072 }
1073
1074 #[tokio::test]
1075 async fn test_mutation_rule_manager_add_and_get_rule() {
1076 let manager = MutationRuleManager::new();
1077
1078 let rule = MutationRule::new(
1079 "get-test".to_string(),
1080 "Order".to_string(),
1081 MutationTrigger::Interval {
1082 duration_seconds: 60,
1083 },
1084 MutationOperation::Set {
1085 field: "status".to_string(),
1086 value: serde_json::json!("done"),
1087 },
1088 );
1089
1090 manager.add_rule(rule).await.unwrap();
1091
1092 let retrieved = manager.get_rule("get-test").await;
1093 assert!(retrieved.is_some());
1094 assert_eq!(retrieved.unwrap().id, "get-test");
1095 }
1096
1097 #[tokio::test]
1098 async fn test_mutation_rule_manager_get_nonexistent() {
1099 let manager = MutationRuleManager::new();
1100 let retrieved = manager.get_rule("nonexistent").await;
1101 assert!(retrieved.is_none());
1102 }
1103
1104 #[tokio::test]
1105 async fn test_mutation_rule_manager_remove_rule() {
1106 let manager = MutationRuleManager::new();
1107
1108 let rule = MutationRule::new(
1109 "remove-test".to_string(),
1110 "Entity".to_string(),
1111 MutationTrigger::Interval {
1112 duration_seconds: 60,
1113 },
1114 MutationOperation::Set {
1115 field: "f".to_string(),
1116 value: serde_json::json!("v"),
1117 },
1118 );
1119
1120 manager.add_rule(rule).await.unwrap();
1121 assert!(manager.get_rule("remove-test").await.is_some());
1122
1123 let removed = manager.remove_rule("remove-test").await;
1124 assert!(removed);
1125 assert!(manager.get_rule("remove-test").await.is_none());
1126 }
1127
1128 #[tokio::test]
1129 async fn test_mutation_rule_manager_remove_nonexistent() {
1130 let manager = MutationRuleManager::new();
1131 let removed = manager.remove_rule("nonexistent").await;
1132 assert!(!removed);
1133 }
1134
1135 #[tokio::test]
1136 async fn test_mutation_rule_manager_list_rules() {
1137 let manager = MutationRuleManager::new();
1138
1139 for i in 1..=3 {
1141 let rule = MutationRule::new(
1142 format!("rule-{}", i),
1143 "Entity".to_string(),
1144 MutationTrigger::Interval {
1145 duration_seconds: 60,
1146 },
1147 MutationOperation::Set {
1148 field: "f".to_string(),
1149 value: serde_json::json!("v"),
1150 },
1151 );
1152 manager.add_rule(rule).await.unwrap();
1153 }
1154
1155 let rules = manager.list_rules().await;
1156 assert_eq!(rules.len(), 3);
1157 }
1158
1159 #[tokio::test]
1160 async fn test_mutation_rule_manager_list_rules_for_entity() {
1161 let manager = MutationRuleManager::new();
1162
1163 let rule1 = MutationRule::new(
1165 "user-rule".to_string(),
1166 "User".to_string(),
1167 MutationTrigger::Interval {
1168 duration_seconds: 60,
1169 },
1170 MutationOperation::Set {
1171 field: "f".to_string(),
1172 value: serde_json::json!("v"),
1173 },
1174 );
1175
1176 let rule2 = MutationRule::new(
1177 "order-rule".to_string(),
1178 "Order".to_string(),
1179 MutationTrigger::Interval {
1180 duration_seconds: 60,
1181 },
1182 MutationOperation::Set {
1183 field: "f".to_string(),
1184 value: serde_json::json!("v"),
1185 },
1186 );
1187
1188 let rule3 = MutationRule::new(
1189 "user-rule-2".to_string(),
1190 "User".to_string(),
1191 MutationTrigger::Interval {
1192 duration_seconds: 120,
1193 },
1194 MutationOperation::Increment {
1195 field: "count".to_string(),
1196 amount: 1.0,
1197 },
1198 );
1199
1200 manager.add_rule(rule1).await.unwrap();
1201 manager.add_rule(rule2).await.unwrap();
1202 manager.add_rule(rule3).await.unwrap();
1203
1204 let user_rules = manager.list_rules_for_entity("User").await;
1205 assert_eq!(user_rules.len(), 2);
1206
1207 let order_rules = manager.list_rules_for_entity("Order").await;
1208 assert_eq!(order_rules.len(), 1);
1209
1210 let product_rules = manager.list_rules_for_entity("Product").await;
1211 assert!(product_rules.is_empty());
1212 }
1213
1214 #[tokio::test]
1215 async fn test_mutation_rule_manager_set_rule_enabled() {
1216 let manager = MutationRuleManager::new();
1217
1218 let rule = MutationRule::new(
1219 "enable-test".to_string(),
1220 "Entity".to_string(),
1221 MutationTrigger::Interval {
1222 duration_seconds: 60,
1223 },
1224 MutationOperation::Set {
1225 field: "f".to_string(),
1226 value: serde_json::json!("v"),
1227 },
1228 );
1229
1230 manager.add_rule(rule).await.unwrap();
1231
1232 manager.set_rule_enabled("enable-test", false).await.unwrap();
1234 let disabled_rule = manager.get_rule("enable-test").await.unwrap();
1235 assert!(!disabled_rule.enabled);
1236 assert!(disabled_rule.next_execution.is_none());
1237
1238 manager.set_rule_enabled("enable-test", true).await.unwrap();
1240 let enabled_rule = manager.get_rule("enable-test").await.unwrap();
1241 assert!(enabled_rule.enabled);
1242 assert!(enabled_rule.next_execution.is_some());
1243 }
1244
1245 #[tokio::test]
1246 async fn test_mutation_rule_manager_set_rule_enabled_nonexistent() {
1247 let manager = MutationRuleManager::new();
1248 let result = manager.set_rule_enabled("nonexistent", true).await;
1249 assert!(result.is_err());
1250 }
1251
1252 #[test]
1254 fn test_comparison_operator_variants() {
1255 let operators = vec![
1256 ComparisonOperator::Gt,
1257 ComparisonOperator::Lt,
1258 ComparisonOperator::Eq,
1259 ComparisonOperator::Gte,
1260 ComparisonOperator::Lte,
1261 ];
1262
1263 for op in operators {
1264 let json = serde_json::to_string(&op).unwrap();
1265 assert!(!json.is_empty());
1266 }
1267 }
1268
1269 #[test]
1270 fn test_comparison_operator_clone() {
1271 let op = ComparisonOperator::Gt;
1272 let cloned = op.clone();
1273 assert!(matches!(cloned, ComparisonOperator::Gt));
1274 }
1275
1276 #[test]
1277 fn test_comparison_operator_debug() {
1278 let op = ComparisonOperator::Lt;
1279 let debug = format!("{:?}", op);
1280 assert!(debug.contains("Lt"));
1281 }
1282
1283 #[test]
1284 fn test_comparison_operator_eq() {
1285 let op1 = ComparisonOperator::Gt;
1286 let op2 = ComparisonOperator::Gt;
1287 let op3 = ComparisonOperator::Lt;
1288 assert_eq!(op1, op2);
1289 assert_ne!(op1, op3);
1290 }
1291
1292 #[test]
1293 fn test_comparison_operator_serialize() {
1294 assert_eq!(serde_json::to_string(&ComparisonOperator::Gt).unwrap(), "\"gt\"");
1295 assert_eq!(serde_json::to_string(&ComparisonOperator::Lt).unwrap(), "\"lt\"");
1296 assert_eq!(serde_json::to_string(&ComparisonOperator::Eq).unwrap(), "\"eq\"");
1297 assert_eq!(serde_json::to_string(&ComparisonOperator::Gte).unwrap(), "\"gte\"");
1298 assert_eq!(serde_json::to_string(&ComparisonOperator::Lte).unwrap(), "\"lte\"");
1299 }
1300}