Skip to main content

entrenar/integrity/behavioral/
violation.rs

1//! Metamorphic violation types for behavioral testing
2//!
3//! Defines violation types and metamorphic relation categories
4//! used in behavioral integrity verification.
5
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8
9/// A metamorphic violation detected during behavioral testing
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11pub struct MetamorphicViolation {
12    /// Unique identifier for this violation
13    pub id: String,
14
15    /// Type of metamorphic relation violated
16    pub relation_type: MetamorphicRelationType,
17
18    /// Description of the violation
19    pub description: String,
20
21    /// Input that caused the violation
22    pub input_description: String,
23
24    /// Expected behavior
25    pub expected: String,
26
27    /// Actual behavior observed
28    pub actual: String,
29
30    /// Severity of the violation (0.0 - 1.0, higher = more severe)
31    pub severity: f64,
32
33    /// When the violation was detected
34    pub detected_at: DateTime<Utc>,
35}
36
37impl MetamorphicViolation {
38    /// Create a new metamorphic violation
39    pub fn new(
40        id: impl Into<String>,
41        relation_type: MetamorphicRelationType,
42        description: impl Into<String>,
43        input_description: impl Into<String>,
44        expected: impl Into<String>,
45        actual: impl Into<String>,
46        severity: f64,
47    ) -> Self {
48        Self {
49            id: id.into(),
50            relation_type,
51            description: description.into(),
52            input_description: input_description.into(),
53            expected: expected.into(),
54            actual: actual.into(),
55            severity: severity.clamp(0.0, 1.0),
56            detected_at: Utc::now(),
57        }
58    }
59
60    /// Check if this is a critical violation (severity >= 0.8)
61    pub fn is_critical(&self) -> bool {
62        self.severity >= 0.8
63    }
64
65    /// Check if this is a warning-level violation (severity >= 0.5)
66    pub fn is_warning(&self) -> bool {
67        self.severity >= 0.5
68    }
69}
70
71/// Types of metamorphic relations
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
73pub enum MetamorphicRelationType {
74    /// Additive: f(x + c) should relate to f(x) in predictable way
75    Additive,
76    /// Multiplicative: f(k * x) should relate to f(x)
77    Multiplicative,
78    /// Permutation: f(permute(x)) should relate to f(x)
79    Permutation,
80    /// Composition: f(g(x)) should relate to g(f(x)) or similar
81    Composition,
82    /// Negation: f(-x) should relate to f(x)
83    Negation,
84    /// Inclusion: f(x ⊂ y) implies relation between f(x) and f(y)
85    Inclusion,
86    /// Identity: f(x) should equal f(x) across invocations
87    Identity,
88    /// Custom relation type
89    Custom,
90}
91
92impl std::fmt::Display for MetamorphicRelationType {
93    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94        match self {
95            Self::Additive => write!(f, "additive"),
96            Self::Multiplicative => write!(f, "multiplicative"),
97            Self::Permutation => write!(f, "permutation"),
98            Self::Composition => write!(f, "composition"),
99            Self::Negation => write!(f, "negation"),
100            Self::Inclusion => write!(f, "inclusion"),
101            Self::Identity => write!(f, "identity"),
102            Self::Custom => write!(f, "custom"),
103        }
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn test_metamorphic_violation_new() {
113        let violation = MetamorphicViolation::new(
114            "v1",
115            MetamorphicRelationType::Additive,
116            "Test violation",
117            "input x=5",
118            "f(x+1) = f(x) + 1",
119            "f(x+1) = f(x) + 2",
120            0.7,
121        );
122
123        assert_eq!(violation.id, "v1");
124        assert_eq!(violation.relation_type, MetamorphicRelationType::Additive);
125        assert_eq!(violation.description, "Test violation");
126        assert!((violation.severity - 0.7).abs() < 1e-6);
127    }
128
129    #[test]
130    fn test_metamorphic_violation_severity_clamped() {
131        let high = MetamorphicViolation::new(
132            "v1",
133            MetamorphicRelationType::Identity,
134            "desc",
135            "input",
136            "exp",
137            "act",
138            1.5, // should be clamped to 1.0
139        );
140        assert!((high.severity - 1.0).abs() < 1e-6);
141
142        let low = MetamorphicViolation::new(
143            "v2",
144            MetamorphicRelationType::Identity,
145            "desc",
146            "input",
147            "exp",
148            "act",
149            -0.5, // should be clamped to 0.0
150        );
151        assert!((low.severity - 0.0).abs() < 1e-6);
152    }
153
154    #[test]
155    fn test_is_critical() {
156        let critical = MetamorphicViolation::new(
157            "v1",
158            MetamorphicRelationType::Identity,
159            "desc",
160            "input",
161            "exp",
162            "act",
163            0.8,
164        );
165        assert!(critical.is_critical());
166
167        let non_critical = MetamorphicViolation::new(
168            "v2",
169            MetamorphicRelationType::Identity,
170            "desc",
171            "input",
172            "exp",
173            "act",
174            0.79,
175        );
176        assert!(!non_critical.is_critical());
177    }
178
179    #[test]
180    fn test_is_warning() {
181        let warning = MetamorphicViolation::new(
182            "v1",
183            MetamorphicRelationType::Identity,
184            "desc",
185            "input",
186            "exp",
187            "act",
188            0.5,
189        );
190        assert!(warning.is_warning());
191
192        let non_warning = MetamorphicViolation::new(
193            "v2",
194            MetamorphicRelationType::Identity,
195            "desc",
196            "input",
197            "exp",
198            "act",
199            0.49,
200        );
201        assert!(!non_warning.is_warning());
202    }
203
204    #[test]
205    fn test_relation_type_display() {
206        assert_eq!(MetamorphicRelationType::Additive.to_string(), "additive");
207        assert_eq!(MetamorphicRelationType::Multiplicative.to_string(), "multiplicative");
208        assert_eq!(MetamorphicRelationType::Permutation.to_string(), "permutation");
209        assert_eq!(MetamorphicRelationType::Composition.to_string(), "composition");
210        assert_eq!(MetamorphicRelationType::Negation.to_string(), "negation");
211        assert_eq!(MetamorphicRelationType::Inclusion.to_string(), "inclusion");
212        assert_eq!(MetamorphicRelationType::Identity.to_string(), "identity");
213        assert_eq!(MetamorphicRelationType::Custom.to_string(), "custom");
214    }
215
216    #[test]
217    fn test_relation_type_eq() {
218        assert_eq!(MetamorphicRelationType::Additive, MetamorphicRelationType::Additive);
219        assert_ne!(MetamorphicRelationType::Additive, MetamorphicRelationType::Multiplicative);
220    }
221
222    #[test]
223    fn test_relation_type_hash() {
224        use std::collections::HashSet;
225        let mut set = HashSet::new();
226        set.insert(MetamorphicRelationType::Additive);
227        set.insert(MetamorphicRelationType::Additive);
228        assert_eq!(set.len(), 1);
229        set.insert(MetamorphicRelationType::Identity);
230        assert_eq!(set.len(), 2);
231    }
232
233    #[test]
234    fn test_violation_serde() {
235        let violation = MetamorphicViolation::new(
236            "v1",
237            MetamorphicRelationType::Additive,
238            "Test violation",
239            "input",
240            "expected",
241            "actual",
242            0.7,
243        );
244
245        let json = serde_json::to_string(&violation).expect("JSON serialization should succeed");
246        let deserialized: MetamorphicViolation =
247            serde_json::from_str(&json).expect("JSON deserialization should succeed");
248        assert_eq!(violation.id, deserialized.id);
249        assert_eq!(violation.relation_type, deserialized.relation_type);
250    }
251
252    #[test]
253    fn test_violation_clone() {
254        let violation = MetamorphicViolation::new(
255            "v1",
256            MetamorphicRelationType::Additive,
257            "Test",
258            "input",
259            "expected",
260            "actual",
261            0.5,
262        );
263        let cloned = violation.clone();
264        assert_eq!(violation, cloned);
265    }
266}