entrenar/integrity/behavioral/
violation.rs1use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11pub struct MetamorphicViolation {
12 pub id: String,
14
15 pub relation_type: MetamorphicRelationType,
17
18 pub description: String,
20
21 pub input_description: String,
23
24 pub expected: String,
26
27 pub actual: String,
29
30 pub severity: f64,
32
33 pub detected_at: DateTime<Utc>,
35}
36
37impl MetamorphicViolation {
38 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 pub fn is_critical(&self) -> bool {
62 self.severity >= 0.8
63 }
64
65 pub fn is_warning(&self) -> bool {
67 self.severity >= 0.5
68 }
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
73pub enum MetamorphicRelationType {
74 Additive,
76 Multiplicative,
78 Permutation,
80 Composition,
82 Negation,
84 Inclusion,
86 Identity,
88 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, );
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, );
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}