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::internal(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::internal(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::internal(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::internal("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::internal(format!(
470 "Invalid character in expression: {}",
471 ch
472 )));
473 }
474 }
475 }
476
477 if !current_num.is_empty() {
479 let num: f64 = current_num
480 .parse()
481 .map_err(|_| Error::internal(format!("Invalid number: {}", current_num)))?;
482
483 match last_op {
484 '+' => result += num,
485 '-' => result -= num,
486 '*' => result *= num,
487 '/' => {
488 if num == 0.0 {
489 return Err(Error::internal("Division by zero".to_string()));
490 }
491 result /= num;
492 }
493 _ => result = num, }
495 }
496
497 Ok(result)
498 }
499
500 fn evaluate_condition(condition: &str, record: &Value) -> Result<bool> {
509 if let Ok(selector) = jsonpath::Selector::new(condition) {
515 let results: Vec<_> = selector.find(record).collect();
517 if !results.is_empty() {
518 for result in results {
520 match result {
521 Value::Bool(b) => {
522 if *b {
523 return Ok(true);
524 }
525 }
526 Value::Null => continue,
527 Value::String(s) => {
528 if !s.is_empty() {
529 return Ok(true);
530 }
531 }
532 Value::Number(n) => {
533 if n.as_f64().map(|f| f != 0.0).unwrap_or(false) {
534 return Ok(true);
535 }
536 }
537 _ => return Ok(true), }
539 }
540 }
541 return Ok(false);
542 }
543
544 if condition.contains("==") {
547 let parts: Vec<&str> = condition.split("==").map(|s| s.trim()).collect();
548 if parts.len() == 2 {
549 let path = parts[0].trim();
550 let expected = parts[1].trim().trim_matches('\'').trim_matches('"');
551
552 if let Ok(selector) = jsonpath::Selector::new(path) {
553 let results: Vec<_> = selector.find(record).collect();
554 for result in results {
555 match result {
556 Value::String(s) if s == expected => return Ok(true),
557 Value::Number(n) => {
558 if let Ok(expected_num) = expected.parse::<f64>() {
559 if n.as_f64().map(|f| f == expected_num).unwrap_or(false) {
560 return Ok(true);
561 }
562 }
563 }
564 _ => {}
565 }
566 }
567 }
568 }
569 } else if condition.contains(">") {
570 let parts: Vec<&str> = condition.split(">").map(|s| s.trim()).collect();
571 if parts.len() == 2 {
572 let path = parts[0].trim();
573 if let Ok(expected_num) = parts[1].trim().parse::<f64>() {
574 if let Ok(selector) = jsonpath::Selector::new(path) {
575 let results: Vec<_> = selector.find(record).collect();
576 for result in results {
577 if let Value::Number(n) = result {
578 if n.as_f64().map(|f| f > expected_num).unwrap_or(false) {
579 return Ok(true);
580 }
581 }
582 }
583 }
584 }
585 }
586 } else if condition.contains("<") {
587 let parts: Vec<&str> = condition.split("<").map(|s| s.trim()).collect();
588 if parts.len() == 2 {
589 let path = parts[0].trim();
590 if let Ok(expected_num) = parts[1].trim().parse::<f64>() {
591 if let Ok(selector) = jsonpath::Selector::new(path) {
592 let results: Vec<_> = selector.find(record).collect();
593 for result in results {
594 if let Value::Number(n) = result {
595 if n.as_f64().map(|f| f < expected_num).unwrap_or(false) {
596 return Ok(true);
597 }
598 }
599 }
600 }
601 }
602 }
603 }
604
605 warn!("Could not evaluate condition '{}', treating as false", condition);
607 Ok(false)
608 }
609
610 async fn execute_rule(
612 &self,
613 rule_id: &str,
614 database: &dyn crate::database::VirtualDatabase,
615 registry: &crate::entities::EntityRegistry,
616 ) -> Result<()> {
617 let now = time_travel_now();
618
619 let rule = {
621 let rules = self.rules.read().await;
622 rules
623 .get(rule_id)
624 .ok_or_else(|| Error::internal(format!("Mutation rule '{}' not found", rule_id)))?
625 .clone()
626 };
627
628 let entity = registry
630 .get(&rule.entity_name)
631 .ok_or_else(|| Error::internal(format!("Entity '{}' not found", rule.entity_name)))?;
632
633 let table_name = entity.table_name();
634
635 let query = format!("SELECT * FROM {}", table_name);
637 let records = database.query(&query, &[]).await?;
638
639 let pk_field = entity.schema.primary_key.first().map(|s| s.as_str()).unwrap_or("id");
641
642 for record in records {
643 if let Some(ref condition) = rule.condition {
645 let record_value =
647 Value::Object(record.iter().map(|(k, v)| (k.clone(), v.clone())).collect());
648
649 if !MutationRuleManager::evaluate_condition(condition, &record_value)? {
653 debug!("Condition '{}' not met for record, skipping", condition);
654 continue;
655 }
656 }
657
658 let pk_value = record
660 .get(pk_field)
661 .ok_or_else(|| Error::internal(format!("Primary key '{}' not found", pk_field)))?;
662
663 match &rule.operation {
665 MutationOperation::Set { field, value } => {
666 let update_query =
667 format!("UPDATE {} SET {} = ? WHERE {} = ?", table_name, field, pk_field);
668 database.execute(&update_query, &[value.clone(), pk_value.clone()]).await?;
669 }
670 MutationOperation::Increment { field, amount } => {
671 if let Some(current) = record.get(field) {
673 let new_value = if let Some(num) = current.as_f64() {
674 Value::Number(
675 serde_json::Number::from_f64(num + amount)
676 .unwrap_or_else(|| serde_json::Number::from(0)),
677 )
678 } else if let Some(num) = current.as_i64() {
679 Value::Number(serde_json::Number::from(num + *amount as i64))
680 } else {
681 continue; };
683
684 let update_query = format!(
685 "UPDATE {} SET {} = ? WHERE {} = ?",
686 table_name, field, pk_field
687 );
688 database.execute(&update_query, &[new_value, pk_value.clone()]).await?;
689 }
690 }
691 MutationOperation::Decrement { field, amount } => {
692 if let Some(current) = record.get(field) {
694 let new_value = if let Some(num) = current.as_f64() {
695 Value::Number(
696 serde_json::Number::from_f64(num - amount)
697 .unwrap_or_else(|| serde_json::Number::from(0)),
698 )
699 } else if let Some(num) = current.as_i64() {
700 Value::Number(serde_json::Number::from(num - *amount as i64))
701 } else {
702 continue; };
704
705 let update_query = format!(
706 "UPDATE {} SET {} = ? WHERE {} = ?",
707 table_name, field, pk_field
708 );
709 database.execute(&update_query, &[new_value, pk_value.clone()]).await?;
710 }
711 }
712 MutationOperation::Transform { field, expression } => {
713 let transformed_value =
715 Self::evaluate_transformation_expression(expression, &record)?;
716
717 let update_query =
718 format!("UPDATE {} SET {} = ? WHERE {} = ?", table_name, field, pk_field);
719 database.execute(&update_query, &[transformed_value, pk_value.clone()]).await?;
720 }
721 MutationOperation::UpdateStatus { status } => {
722 let update_query =
723 format!("UPDATE {} SET status = ? WHERE {} = ?", table_name, pk_field);
724 database
725 .execute(&update_query, &[Value::String(status.clone()), pk_value.clone()])
726 .await?;
727 }
728 }
729 }
730
731 {
733 let mut rules = self.rules.write().await;
734 if let Some(rule) = rules.get_mut(rule_id) {
735 rule.last_execution = Some(now);
736 rule.execution_count += 1;
737
738 rule.next_execution = rule.calculate_next_execution(now);
740 }
741 }
742
743 info!("Executed mutation rule '{}' on entity '{}'", rule_id, rule.entity_name);
744 Ok(())
745 }
746}
747
748impl Default for MutationRuleManager {
749 fn default() -> Self {
750 Self::new()
751 }
752}
753
754#[cfg(test)]
755mod tests {
756 use super::*;
757
758 #[test]
760 fn test_mutation_trigger_interval_serialize() {
761 let trigger = MutationTrigger::Interval {
762 duration_seconds: 3600,
763 };
764 let json = serde_json::to_string(&trigger).unwrap();
765 assert!(json.contains("interval"));
766 assert!(json.contains("3600"));
767 }
768
769 #[test]
770 fn test_mutation_trigger_at_time_serialize() {
771 let trigger = MutationTrigger::AtTime {
772 hour: 9,
773 minute: 30,
774 };
775 let json = serde_json::to_string(&trigger).unwrap();
776 assert!(json.contains("attime"));
777 assert!(json.contains("\"hour\":9"));
778 }
779
780 #[test]
781 fn test_mutation_trigger_field_threshold_serialize() {
782 let trigger = MutationTrigger::FieldThreshold {
783 field: "age".to_string(),
784 threshold: serde_json::json!(100),
785 operator: ComparisonOperator::Gt,
786 };
787 let json = serde_json::to_string(&trigger).unwrap();
788 assert!(json.contains("fieldthreshold"));
789 assert!(json.contains("age"));
790 }
791
792 #[test]
793 fn test_mutation_trigger_clone() {
794 let trigger = MutationTrigger::Interval {
795 duration_seconds: 60,
796 };
797 let cloned = trigger.clone();
798 match cloned {
799 MutationTrigger::Interval { duration_seconds } => {
800 assert_eq!(duration_seconds, 60);
801 }
802 _ => panic!("Expected Interval variant"),
803 }
804 }
805
806 #[test]
807 fn test_mutation_trigger_debug() {
808 let trigger = MutationTrigger::Interval {
809 duration_seconds: 120,
810 };
811 let debug = format!("{:?}", trigger);
812 assert!(debug.contains("Interval"));
813 }
814
815 #[test]
817 fn test_mutation_operation_set() {
818 let op = MutationOperation::Set {
819 field: "status".to_string(),
820 value: serde_json::json!("active"),
821 };
822 let json = serde_json::to_string(&op).unwrap();
823 assert!(json.contains("set"));
824 assert!(json.contains("status"));
825 }
826
827 #[test]
828 fn test_mutation_operation_increment() {
829 let op = MutationOperation::Increment {
830 field: "count".to_string(),
831 amount: 5.0,
832 };
833 let json = serde_json::to_string(&op).unwrap();
834 assert!(json.contains("increment"));
835 assert!(json.contains("count"));
836 }
837
838 #[test]
839 fn test_mutation_operation_decrement() {
840 let op = MutationOperation::Decrement {
841 field: "balance".to_string(),
842 amount: 10.5,
843 };
844 let json = serde_json::to_string(&op).unwrap();
845 assert!(json.contains("decrement"));
846 assert!(json.contains("balance"));
847 }
848
849 #[test]
850 fn test_mutation_operation_transform() {
851 let op = MutationOperation::Transform {
852 field: "value".to_string(),
853 expression: "{{value}} * 2".to_string(),
854 };
855 let json = serde_json::to_string(&op).unwrap();
856 assert!(json.contains("transform"));
857 }
858
859 #[test]
860 fn test_mutation_operation_update_status() {
861 let op = MutationOperation::UpdateStatus {
862 status: "completed".to_string(),
863 };
864 let json = serde_json::to_string(&op).unwrap();
865 assert!(json.contains("updatestatus"));
866 assert!(json.contains("completed"));
867 }
868
869 #[test]
870 fn test_mutation_operation_clone() {
871 let op = MutationOperation::Set {
872 field: "test".to_string(),
873 value: serde_json::json!(42),
874 };
875 let cloned = op.clone();
876 match cloned {
877 MutationOperation::Set { field, value } => {
878 assert_eq!(field, "test");
879 assert_eq!(value, serde_json::json!(42));
880 }
881 _ => panic!("Expected Set variant"),
882 }
883 }
884
885 #[test]
887 fn test_mutation_rule_creation() {
888 let rule = MutationRule::new(
889 "test-1".to_string(),
890 "User".to_string(),
891 MutationTrigger::Interval {
892 duration_seconds: 3600,
893 },
894 MutationOperation::Increment {
895 field: "count".to_string(),
896 amount: 1.0,
897 },
898 );
899
900 assert_eq!(rule.id, "test-1");
901 assert_eq!(rule.entity_name, "User");
902 assert!(rule.enabled);
903 assert!(rule.description.is_none());
904 assert!(rule.condition.is_none());
905 assert!(rule.last_execution.is_none());
906 assert_eq!(rule.execution_count, 0);
907 }
908
909 #[test]
910 fn test_mutation_rule_defaults() {
911 let rule = MutationRule::new(
912 "rule-1".to_string(),
913 "Order".to_string(),
914 MutationTrigger::Interval {
915 duration_seconds: 60,
916 },
917 MutationOperation::Set {
918 field: "status".to_string(),
919 value: serde_json::json!("processed"),
920 },
921 );
922
923 assert!(rule.enabled);
925 assert!(rule.description.is_none());
926 assert!(rule.condition.is_none());
927 assert!(rule.last_execution.is_none());
928 assert_eq!(rule.execution_count, 0);
929 }
930
931 #[test]
932 fn test_mutation_rule_clone() {
933 let rule = MutationRule::new(
934 "clone-test".to_string(),
935 "Entity".to_string(),
936 MutationTrigger::Interval {
937 duration_seconds: 100,
938 },
939 MutationOperation::Increment {
940 field: "counter".to_string(),
941 amount: 1.0,
942 },
943 );
944
945 let cloned = rule.clone();
946 assert_eq!(rule.id, cloned.id);
947 assert_eq!(rule.entity_name, cloned.entity_name);
948 assert_eq!(rule.enabled, cloned.enabled);
949 }
950
951 #[test]
952 fn test_mutation_rule_debug() {
953 let rule = MutationRule::new(
954 "debug-rule".to_string(),
955 "Test".to_string(),
956 MutationTrigger::Interval {
957 duration_seconds: 10,
958 },
959 MutationOperation::Set {
960 field: "f".to_string(),
961 value: serde_json::json!("v"),
962 },
963 );
964
965 let debug = format!("{:?}", rule);
966 assert!(debug.contains("MutationRule"));
967 assert!(debug.contains("debug-rule"));
968 }
969
970 #[test]
971 fn test_mutation_trigger_interval() {
972 let rule = MutationRule::new(
973 "test-1".to_string(),
974 "User".to_string(),
975 MutationTrigger::Interval {
976 duration_seconds: 3600,
977 },
978 MutationOperation::Set {
979 field: "status".to_string(),
980 value: serde_json::json!("active"),
981 },
982 );
983
984 let now = Utc::now();
985 let next = rule.calculate_next_execution(now).unwrap();
986 let duration = next - now;
987
988 assert!(duration.num_seconds() >= 3599 && duration.num_seconds() <= 3601);
990 }
991
992 #[test]
993 fn test_mutation_rule_calculate_next_execution_disabled() {
994 let mut rule = MutationRule::new(
995 "disabled-rule".to_string(),
996 "Entity".to_string(),
997 MutationTrigger::Interval {
998 duration_seconds: 60,
999 },
1000 MutationOperation::Set {
1001 field: "f".to_string(),
1002 value: serde_json::json!("v"),
1003 },
1004 );
1005 rule.enabled = false;
1006
1007 let now = Utc::now();
1008 assert!(rule.calculate_next_execution(now).is_none());
1009 }
1010
1011 #[test]
1012 fn test_mutation_rule_calculate_next_execution_field_threshold() {
1013 let rule = MutationRule::new(
1014 "threshold-rule".to_string(),
1015 "Entity".to_string(),
1016 MutationTrigger::FieldThreshold {
1017 field: "value".to_string(),
1018 threshold: serde_json::json!(100),
1019 operator: ComparisonOperator::Gt,
1020 },
1021 MutationOperation::Set {
1022 field: "f".to_string(),
1023 value: serde_json::json!("v"),
1024 },
1025 );
1026
1027 let now = Utc::now();
1029 assert!(rule.calculate_next_execution(now).is_none());
1030 }
1031
1032 #[tokio::test]
1034 async fn test_mutation_rule_manager() {
1035 let manager = MutationRuleManager::new();
1036
1037 let rule = MutationRule::new(
1038 "test-1".to_string(),
1039 "User".to_string(),
1040 MutationTrigger::Interval {
1041 duration_seconds: 3600,
1042 },
1043 MutationOperation::Increment {
1044 field: "count".to_string(),
1045 amount: 1.0,
1046 },
1047 );
1048
1049 manager.add_rule(rule).await.unwrap();
1050
1051 let rules = manager.list_rules().await;
1052 assert_eq!(rules.len(), 1);
1053 assert_eq!(rules[0].id, "test-1");
1054 }
1055
1056 #[tokio::test]
1057 async fn test_mutation_rule_manager_new() {
1058 let manager = MutationRuleManager::new();
1059 let rules = manager.list_rules().await;
1060 assert!(rules.is_empty());
1061 }
1062
1063 #[tokio::test]
1064 async fn test_mutation_rule_manager_default() {
1065 let manager = MutationRuleManager::default();
1066 let rules = manager.list_rules().await;
1067 assert!(rules.is_empty());
1068 }
1069
1070 #[tokio::test]
1071 async fn test_mutation_rule_manager_add_and_get_rule() {
1072 let manager = MutationRuleManager::new();
1073
1074 let rule = MutationRule::new(
1075 "get-test".to_string(),
1076 "Order".to_string(),
1077 MutationTrigger::Interval {
1078 duration_seconds: 60,
1079 },
1080 MutationOperation::Set {
1081 field: "status".to_string(),
1082 value: serde_json::json!("done"),
1083 },
1084 );
1085
1086 manager.add_rule(rule).await.unwrap();
1087
1088 let retrieved = manager.get_rule("get-test").await;
1089 assert!(retrieved.is_some());
1090 assert_eq!(retrieved.unwrap().id, "get-test");
1091 }
1092
1093 #[tokio::test]
1094 async fn test_mutation_rule_manager_get_nonexistent() {
1095 let manager = MutationRuleManager::new();
1096 let retrieved = manager.get_rule("nonexistent").await;
1097 assert!(retrieved.is_none());
1098 }
1099
1100 #[tokio::test]
1101 async fn test_mutation_rule_manager_remove_rule() {
1102 let manager = MutationRuleManager::new();
1103
1104 let rule = MutationRule::new(
1105 "remove-test".to_string(),
1106 "Entity".to_string(),
1107 MutationTrigger::Interval {
1108 duration_seconds: 60,
1109 },
1110 MutationOperation::Set {
1111 field: "f".to_string(),
1112 value: serde_json::json!("v"),
1113 },
1114 );
1115
1116 manager.add_rule(rule).await.unwrap();
1117 assert!(manager.get_rule("remove-test").await.is_some());
1118
1119 let removed = manager.remove_rule("remove-test").await;
1120 assert!(removed);
1121 assert!(manager.get_rule("remove-test").await.is_none());
1122 }
1123
1124 #[tokio::test]
1125 async fn test_mutation_rule_manager_remove_nonexistent() {
1126 let manager = MutationRuleManager::new();
1127 let removed = manager.remove_rule("nonexistent").await;
1128 assert!(!removed);
1129 }
1130
1131 #[tokio::test]
1132 async fn test_mutation_rule_manager_list_rules() {
1133 let manager = MutationRuleManager::new();
1134
1135 for i in 1..=3 {
1137 let rule = MutationRule::new(
1138 format!("rule-{}", i),
1139 "Entity".to_string(),
1140 MutationTrigger::Interval {
1141 duration_seconds: 60,
1142 },
1143 MutationOperation::Set {
1144 field: "f".to_string(),
1145 value: serde_json::json!("v"),
1146 },
1147 );
1148 manager.add_rule(rule).await.unwrap();
1149 }
1150
1151 let rules = manager.list_rules().await;
1152 assert_eq!(rules.len(), 3);
1153 }
1154
1155 #[tokio::test]
1156 async fn test_mutation_rule_manager_list_rules_for_entity() {
1157 let manager = MutationRuleManager::new();
1158
1159 let rule1 = MutationRule::new(
1161 "user-rule".to_string(),
1162 "User".to_string(),
1163 MutationTrigger::Interval {
1164 duration_seconds: 60,
1165 },
1166 MutationOperation::Set {
1167 field: "f".to_string(),
1168 value: serde_json::json!("v"),
1169 },
1170 );
1171
1172 let rule2 = MutationRule::new(
1173 "order-rule".to_string(),
1174 "Order".to_string(),
1175 MutationTrigger::Interval {
1176 duration_seconds: 60,
1177 },
1178 MutationOperation::Set {
1179 field: "f".to_string(),
1180 value: serde_json::json!("v"),
1181 },
1182 );
1183
1184 let rule3 = MutationRule::new(
1185 "user-rule-2".to_string(),
1186 "User".to_string(),
1187 MutationTrigger::Interval {
1188 duration_seconds: 120,
1189 },
1190 MutationOperation::Increment {
1191 field: "count".to_string(),
1192 amount: 1.0,
1193 },
1194 );
1195
1196 manager.add_rule(rule1).await.unwrap();
1197 manager.add_rule(rule2).await.unwrap();
1198 manager.add_rule(rule3).await.unwrap();
1199
1200 let user_rules = manager.list_rules_for_entity("User").await;
1201 assert_eq!(user_rules.len(), 2);
1202
1203 let order_rules = manager.list_rules_for_entity("Order").await;
1204 assert_eq!(order_rules.len(), 1);
1205
1206 let product_rules = manager.list_rules_for_entity("Product").await;
1207 assert!(product_rules.is_empty());
1208 }
1209
1210 #[tokio::test]
1211 async fn test_mutation_rule_manager_set_rule_enabled() {
1212 let manager = MutationRuleManager::new();
1213
1214 let rule = MutationRule::new(
1215 "enable-test".to_string(),
1216 "Entity".to_string(),
1217 MutationTrigger::Interval {
1218 duration_seconds: 60,
1219 },
1220 MutationOperation::Set {
1221 field: "f".to_string(),
1222 value: serde_json::json!("v"),
1223 },
1224 );
1225
1226 manager.add_rule(rule).await.unwrap();
1227
1228 manager.set_rule_enabled("enable-test", false).await.unwrap();
1230 let disabled_rule = manager.get_rule("enable-test").await.unwrap();
1231 assert!(!disabled_rule.enabled);
1232 assert!(disabled_rule.next_execution.is_none());
1233
1234 manager.set_rule_enabled("enable-test", true).await.unwrap();
1236 let enabled_rule = manager.get_rule("enable-test").await.unwrap();
1237 assert!(enabled_rule.enabled);
1238 assert!(enabled_rule.next_execution.is_some());
1239 }
1240
1241 #[tokio::test]
1242 async fn test_mutation_rule_manager_set_rule_enabled_nonexistent() {
1243 let manager = MutationRuleManager::new();
1244 let result = manager.set_rule_enabled("nonexistent", true).await;
1245 assert!(result.is_err());
1246 }
1247
1248 #[test]
1250 fn test_comparison_operator_variants() {
1251 let operators = vec![
1252 ComparisonOperator::Gt,
1253 ComparisonOperator::Lt,
1254 ComparisonOperator::Eq,
1255 ComparisonOperator::Gte,
1256 ComparisonOperator::Lte,
1257 ];
1258
1259 for op in operators {
1260 let json = serde_json::to_string(&op).unwrap();
1261 assert!(!json.is_empty());
1262 }
1263 }
1264
1265 #[test]
1266 fn test_comparison_operator_clone() {
1267 let op = ComparisonOperator::Gt;
1268 let cloned = op;
1269 assert!(matches!(cloned, ComparisonOperator::Gt));
1270 }
1271
1272 #[test]
1273 fn test_comparison_operator_debug() {
1274 let op = ComparisonOperator::Lt;
1275 let debug = format!("{:?}", op);
1276 assert!(debug.contains("Lt"));
1277 }
1278
1279 #[test]
1280 fn test_comparison_operator_eq() {
1281 let op1 = ComparisonOperator::Gt;
1282 let op2 = ComparisonOperator::Gt;
1283 let op3 = ComparisonOperator::Lt;
1284 assert_eq!(op1, op2);
1285 assert_ne!(op1, op3);
1286 }
1287
1288 #[test]
1289 fn test_comparison_operator_serialize() {
1290 assert_eq!(serde_json::to_string(&ComparisonOperator::Gt).unwrap(), "\"gt\"");
1291 assert_eq!(serde_json::to_string(&ComparisonOperator::Lt).unwrap(), "\"lt\"");
1292 assert_eq!(serde_json::to_string(&ComparisonOperator::Eq).unwrap(), "\"eq\"");
1293 assert_eq!(serde_json::to_string(&ComparisonOperator::Gte).unwrap(), "\"gte\"");
1294 assert_eq!(serde_json::to_string(&ComparisonOperator::Lte).unwrap(), "\"lte\"");
1295 }
1296}