1use crate::error::EvaluationError;
2use regex::Regex;
3use scouter_types::genai::ValueExt;
4use scouter_types::genai::{traits::TaskAccessor, AssertionResult, ComparisonOperator};
5use serde_json::Value;
6use std::sync::OnceLock;
7
8const REGEX_FIELD_PARSE_PATTERN: &str = r"[a-zA-Z_][a-zA-Z0-9_]*|\[[0-9]+\]";
9static PATH_REGEX: OnceLock<Regex> = OnceLock::new();
10
11pub struct FieldEvaluator;
12
13impl FieldEvaluator {
16 pub fn extract_field_value<'a>(
23 json: &'a Value,
24 field_path: &str,
25 ) -> Result<&'a Value, EvaluationError> {
26 let path_segments = Self::parse_field_path(field_path)?;
27 let mut current_value = json;
28
29 for segment in path_segments {
30 current_value = match segment {
31 PathSegment::Field(field_name) => current_value
32 .get(&field_name)
33 .ok_or_else(|| EvaluationError::FieldNotFound(field_name))?,
34 PathSegment::Index(index) => current_value
35 .get(index)
36 .ok_or_else(|| EvaluationError::IndexNotFound(index))?,
37 };
38 }
39
40 Ok(current_value)
41 }
42
43 fn parse_field_path(path: &str) -> Result<Vec<PathSegment>, EvaluationError> {
49 let regex = PATH_REGEX.get_or_init(|| {
50 Regex::new(REGEX_FIELD_PARSE_PATTERN)
51 .expect("Invalid regex pattern in REGEX_FIELD_PARSE_PATTERN")
52 });
53
54 let mut segments = Vec::new();
55
56 for capture in regex.find_iter(path) {
57 let segment_str = capture.as_str();
58
59 if segment_str.starts_with('[') && segment_str.ends_with(']') {
60 let index_str = &segment_str[1..segment_str.len() - 1];
62 let index: usize = index_str
63 .parse()
64 .map_err(|_| EvaluationError::InvalidArrayIndex(index_str.to_string()))?;
65 segments.push(PathSegment::Index(index));
66 } else {
67 segments.push(PathSegment::Field(segment_str.to_string()));
69 }
70 }
71
72 if segments.is_empty() {
73 return Err(EvaluationError::EmptyFieldPath);
74 }
75
76 Ok(segments)
77 }
78}
79
80#[derive(Debug, Clone, PartialEq)]
81enum PathSegment {
82 Field(String),
83 Index(usize),
84}
85
86#[derive(Debug, Clone)]
87pub struct AssertionEvaluator;
88
89impl AssertionEvaluator {
90 pub fn evaluate_assertion<T: TaskAccessor>(
97 json_value: &Value,
98 assertion: &T,
99 ) -> Result<AssertionResult, EvaluationError> {
100 let actual_value: &Value = if let Some(field_path) = assertion.field_path() {
101 FieldEvaluator::extract_field_value(json_value, field_path)?
102 } else {
103 json_value
104 };
105
106 let expected = Self::resolve_expected_value(json_value, assertion.expected_value())?;
107
108 let comparable_actual =
109 match Self::transform_for_comparison(actual_value, assertion.operator()) {
110 Ok(val) => val,
111 Err(err) => {
112 return Ok(AssertionResult::new(
114 false,
115 (*actual_value).clone(),
116 format!(
117 "✗ Assertion '{}' failed during transformation: {}",
118 assertion.id(),
119 err
120 ),
121 expected.clone(),
122 ));
123 }
124 };
125
126 let passed = Self::compare_values(&comparable_actual, assertion.operator(), expected)?;
127 let messages = if passed {
128 format!("✓ Assertion '{}' passed", assertion.id())
129 } else {
130 format!(
131 "✗ Assertion '{}' failed: expected {}, got {}",
132 assertion.id(),
133 serde_json::to_string(expected).unwrap_or_default(),
134 serde_json::to_string(&comparable_actual).unwrap_or_default()
135 )
136 };
137
138 let assertion_result =
139 AssertionResult::new(passed, (*actual_value).clone(), messages, expected.clone());
140
141 Ok(assertion_result)
142 }
143
144 fn resolve_expected_value<'a>(
145 context: &'a Value,
146 expected: &'a Value,
147 ) -> Result<&'a Value, EvaluationError> {
148 match expected {
149 Value::String(s) if s.starts_with("${") && s.ends_with("}") => {
150 let field_path = &s[2..s.len() - 1];
152 let resolved = FieldEvaluator::extract_field_value(context, field_path)?;
153 Ok(resolved)
154 }
155 _ => Ok(expected),
156 }
157 }
158
159 fn transform_for_comparison(
168 value: &Value,
169 operator: &ComparisonOperator,
170 ) -> Result<Value, EvaluationError> {
171 match operator {
172 ComparisonOperator::HasLengthEqual
174 | ComparisonOperator::HasLengthGreaterThan
175 | ComparisonOperator::HasLengthLessThan
176 | ComparisonOperator::HasLengthGreaterThanOrEqual
177 | ComparisonOperator::HasLengthLessThanOrEqual => {
178 if let Some(len) = value.to_length() {
179 Ok(Value::Number(len.into()))
180 } else {
181 Err(EvaluationError::CannotGetLength(format!("{:?}", value)))
182 }
183 }
184 ComparisonOperator::LessThan
186 | ComparisonOperator::LessThanOrEqual
187 | ComparisonOperator::GreaterThan
188 | ComparisonOperator::GreaterThanOrEqual => {
189 if value.is_number() {
190 Ok(value.clone())
191 } else {
192 Err(EvaluationError::CannotCompareNonNumericValues)
193 }
194 }
195 _ => Ok(value.clone()),
197 }
198 }
199
200 fn compare_values(
201 actual: &Value,
202 operator: &ComparisonOperator,
203 expected: &Value,
204 ) -> Result<bool, EvaluationError> {
205 match operator {
206 ComparisonOperator::Equals => Ok(actual == expected),
208 ComparisonOperator::NotEqual => Ok(actual != expected),
209 ComparisonOperator::GreaterThan => {
210 Self::compare_numeric(actual, expected, |a, b| a > b)
211 }
212 ComparisonOperator::GreaterThanOrEqual => {
213 Self::compare_numeric(actual, expected, |a, b| a >= b)
214 }
215 ComparisonOperator::LessThan => Self::compare_numeric(actual, expected, |a, b| a < b),
216 ComparisonOperator::LessThanOrEqual => {
217 Self::compare_numeric(actual, expected, |a, b| a <= b)
218 }
219 ComparisonOperator::HasLengthEqual => {
220 Self::compare_numeric(actual, expected, |a, b| a == b)
221 }
222 ComparisonOperator::HasLengthGreaterThan => {
223 Self::compare_numeric(actual, expected, |a, b| a > b)
224 }
225 ComparisonOperator::HasLengthLessThan => {
226 Self::compare_numeric(actual, expected, |a, b| a < b)
227 }
228 ComparisonOperator::HasLengthGreaterThanOrEqual => {
229 Self::compare_numeric(actual, expected, |a, b| a >= b)
230 }
231 ComparisonOperator::HasLengthLessThanOrEqual => {
232 Self::compare_numeric(actual, expected, |a, b| a <= b)
233 }
234 ComparisonOperator::Contains => Self::check_contains(actual, expected),
235 ComparisonOperator::NotContains => Ok(!Self::check_contains(actual, expected)?),
236 ComparisonOperator::StartsWith => Self::check_starts_with(actual, expected),
237 ComparisonOperator::EndsWith => Self::check_ends_with(actual, expected),
238 ComparisonOperator::Matches => Self::check_regex_match(actual, expected),
239
240 ComparisonOperator::IsNumeric => Ok(actual.is_number()),
242 ComparisonOperator::IsString => Ok(actual.is_string()),
243 ComparisonOperator::IsBoolean => Ok(actual.is_boolean()),
244 ComparisonOperator::IsNull => Ok(actual.is_null()),
245 ComparisonOperator::IsArray => Ok(actual.is_array()),
246 ComparisonOperator::IsObject => Ok(actual.is_object()),
247
248 ComparisonOperator::IsEmail => Self::check_is_email(actual),
250 ComparisonOperator::IsUrl => Self::check_is_url(actual),
251 ComparisonOperator::IsUuid => Self::check_is_uuid(actual),
252 ComparisonOperator::IsIso8601 => Self::check_is_iso8601(actual),
253 ComparisonOperator::IsJson => Self::check_is_json(actual),
254 ComparisonOperator::MatchesRegex => Self::check_regex_match(actual, expected),
255
256 ComparisonOperator::InRange => Self::check_in_range(actual, expected),
258 ComparisonOperator::NotInRange => Ok(!Self::check_in_range(actual, expected)?),
259 ComparisonOperator::IsPositive => Self::check_is_positive(actual),
260 ComparisonOperator::IsNegative => Self::check_is_negative(actual),
261 ComparisonOperator::IsZero => Self::check_is_zero(actual),
262
263 ComparisonOperator::ContainsAll => Self::check_contains_all(actual, expected),
265 ComparisonOperator::ContainsAny => Self::check_contains_any(actual, expected),
266 ComparisonOperator::ContainsNone => Self::check_contains_none(actual, expected),
267 ComparisonOperator::IsEmpty => Self::check_is_empty(actual),
268 ComparisonOperator::IsNotEmpty => Ok(!Self::check_is_empty(actual)?),
269 ComparisonOperator::HasUniqueItems => Self::check_has_unique_items(actual),
270
271 ComparisonOperator::IsAlphabetic => Self::check_is_alphabetic(actual),
273 ComparisonOperator::IsAlphanumeric => Self::check_is_alphanumeric(actual),
274 ComparisonOperator::IsLowerCase => Self::check_is_lowercase(actual),
275 ComparisonOperator::IsUpperCase => Self::check_is_uppercase(actual),
276 ComparisonOperator::ContainsWord => Self::check_contains_word(actual, expected),
277
278 ComparisonOperator::ApproximatelyEquals => {
280 Self::check_approximately_equals(actual, expected)
281 }
282 }
283 }
284
285 fn compare_numeric<F>(
287 actual: &Value,
288 expected: &Value,
289 comparator: F,
290 ) -> Result<bool, EvaluationError>
291 where
292 F: Fn(f64, f64) -> bool,
293 {
294 let actual_num = actual
295 .as_numeric()
296 .ok_or(EvaluationError::CannotCompareNonNumericValues)?;
297 let expected_num = expected
298 .as_numeric()
299 .ok_or(EvaluationError::CannotCompareNonNumericValues)?;
300
301 Ok(comparator(actual_num, expected_num))
302 }
303
304 fn check_contains(actual: &Value, expected: &Value) -> Result<bool, EvaluationError> {
305 match (actual, expected) {
306 (Value::String(s), Value::String(substr)) => Ok(s.contains(substr)),
307 (Value::Array(arr), expected_item) => Ok(arr.contains(expected_item)),
308 _ => Err(EvaluationError::InvalidContainsOperation),
309 }
310 }
311
312 fn check_starts_with(actual: &Value, expected: &Value) -> Result<bool, EvaluationError> {
313 match (actual, expected) {
314 (Value::String(s), Value::String(prefix)) => Ok(s.starts_with(prefix)),
315 _ => Err(EvaluationError::InvalidStartsWithOperation),
316 }
317 }
318
319 fn check_ends_with(actual: &Value, expected: &Value) -> Result<bool, EvaluationError> {
320 match (actual, expected) {
321 (Value::String(s), Value::String(suffix)) => Ok(s.ends_with(suffix)),
322 _ => Err(EvaluationError::InvalidEndsWithOperation),
323 }
324 }
325
326 fn check_regex_match(actual: &Value, expected: &Value) -> Result<bool, EvaluationError> {
327 match (actual, expected) {
328 (Value::String(s), Value::String(pattern)) => {
329 let regex = Regex::new(pattern)?;
330 Ok(regex.is_match(s))
331 }
332 _ => Err(EvaluationError::InvalidRegexOperation),
333 }
334 }
335
336 fn check_is_email(actual: &Value) -> Result<bool, EvaluationError> {
338 match actual {
339 Value::String(s) => {
340 let email_regex = Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
341 .map_err(EvaluationError::RegexError)?;
342 Ok(email_regex.is_match(s))
343 }
344 _ => Err(EvaluationError::InvalidEmailOperation),
345 }
346 }
347
348 fn check_is_url(actual: &Value) -> Result<bool, EvaluationError> {
349 match actual {
350 Value::String(s) => {
351 let url_regex = Regex::new(
352 r"^https?://[a-zA-Z0-9][a-zA-Z0-9-]*(\.[a-zA-Z0-9][a-zA-Z0-9-]*)*(/.*)?$",
353 )
354 .map_err(EvaluationError::RegexError)?;
355 Ok(url_regex.is_match(s))
356 }
357 _ => Err(EvaluationError::InvalidUrlOperation),
358 }
359 }
360
361 fn check_is_uuid(actual: &Value) -> Result<bool, EvaluationError> {
362 match actual {
363 Value::String(s) => {
364 let uuid_regex = Regex::new(
365 r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
366 ).map_err(EvaluationError::RegexError)?;
367 Ok(uuid_regex.is_match(s))
368 }
369 _ => Err(EvaluationError::InvalidUuidOperation),
370 }
371 }
372
373 fn check_is_iso8601(actual: &Value) -> Result<bool, EvaluationError> {
374 match actual {
375 Value::String(s) => {
376 let iso_regex = Regex::new(
378 r"^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?)?$",
379 )
380 .map_err(EvaluationError::RegexError)?;
381 Ok(iso_regex.is_match(s))
382 }
383 _ => Err(EvaluationError::InvalidIso8601Operation),
384 }
385 }
386
387 fn check_is_json(actual: &Value) -> Result<bool, EvaluationError> {
388 match actual {
389 Value::String(s) => Ok(serde_json::from_str::<Value>(s).is_ok()),
390 _ => Err(EvaluationError::InvalidJsonOperation),
391 }
392 }
393
394 fn check_in_range(actual: &Value, expected: &Value) -> Result<bool, EvaluationError> {
396 let actual_num = actual
397 .as_numeric()
398 .ok_or(EvaluationError::CannotCompareNonNumericValues)?;
399
400 match expected {
401 Value::Array(range) if range.len() == 2 => {
402 let min = range[0]
403 .as_numeric()
404 .ok_or(EvaluationError::InvalidRangeFormat)?;
405 let max = range[1]
406 .as_numeric()
407 .ok_or(EvaluationError::InvalidRangeFormat)?;
408 Ok(actual_num >= min && actual_num <= max)
409 }
410 _ => Err(EvaluationError::InvalidRangeFormat),
411 }
412 }
413
414 fn check_is_positive(actual: &Value) -> Result<bool, EvaluationError> {
415 let num = actual
416 .as_numeric()
417 .ok_or(EvaluationError::CannotCompareNonNumericValues)?;
418 Ok(num > 0.0)
419 }
420
421 fn check_is_negative(actual: &Value) -> Result<bool, EvaluationError> {
422 let num = actual
423 .as_numeric()
424 .ok_or(EvaluationError::CannotCompareNonNumericValues)?;
425 Ok(num < 0.0)
426 }
427
428 fn check_is_zero(actual: &Value) -> Result<bool, EvaluationError> {
429 let num = actual
430 .as_numeric()
431 .ok_or(EvaluationError::CannotCompareNonNumericValues)?;
432 Ok(num == 0.0)
433 }
434
435 fn check_contains_all(actual: &Value, expected: &Value) -> Result<bool, EvaluationError> {
437 match (actual, expected) {
438 (Value::Array(arr), Value::Array(required)) => {
439 Ok(required.iter().all(|item| arr.contains(item)))
440 }
441 _ => Err(EvaluationError::InvalidContainsAllOperation),
442 }
443 }
444
445 fn check_contains_any(actual: &Value, expected: &Value) -> Result<bool, EvaluationError> {
446 match (actual, expected) {
447 (Value::Array(arr), Value::Array(candidates)) => {
448 Ok(candidates.iter().any(|item| arr.contains(item)))
449 }
450 (Value::String(s), Value::Array(keywords)) => Ok(keywords.iter().any(|keyword| {
451 if let Value::String(kw) = keyword {
452 s.contains(kw)
453 } else {
454 false
455 }
456 })),
457 _ => Err(EvaluationError::InvalidContainsAnyOperation),
458 }
459 }
460
461 fn check_contains_none(actual: &Value, expected: &Value) -> Result<bool, EvaluationError> {
462 match (actual, expected) {
463 (Value::Array(arr), Value::Array(forbidden)) => {
464 Ok(!forbidden.iter().any(|item| arr.contains(item)))
465 }
466 _ => Err(EvaluationError::InvalidContainsNoneOperation),
467 }
468 }
469
470 fn check_is_empty(actual: &Value) -> Result<bool, EvaluationError> {
471 match actual {
472 Value::String(s) => Ok(s.is_empty()),
473 Value::Null => Ok(true),
474 Value::Array(arr) => Ok(arr.is_empty()),
475 Value::Object(obj) => Ok(obj.is_empty()),
476 _ => Err(EvaluationError::InvalidEmptyOperation),
477 }
478 }
479
480 fn check_has_unique_items(actual: &Value) -> Result<bool, EvaluationError> {
481 match actual {
482 Value::Array(arr) => {
483 let mut seen = std::collections::HashSet::new();
484 for item in arr {
485 let json_str = serde_json::to_string(item)
486 .map_err(|_| EvaluationError::InvalidUniqueItemsOperation)?;
487 if !seen.insert(json_str) {
488 return Ok(false);
489 }
490 }
491 Ok(true)
492 }
493 _ => Err(EvaluationError::InvalidUniqueItemsOperation),
494 }
495 }
496
497 fn check_is_alphabetic(actual: &Value) -> Result<bool, EvaluationError> {
499 match actual {
500 Value::String(s) => Ok(s.chars().all(|c| c.is_alphabetic())),
501 _ => Err(EvaluationError::InvalidAlphabeticOperation),
502 }
503 }
504
505 fn check_is_alphanumeric(actual: &Value) -> Result<bool, EvaluationError> {
506 match actual {
507 Value::String(s) => Ok(s.chars().all(|c| c.is_alphanumeric())),
508 _ => Err(EvaluationError::InvalidAlphanumericOperation),
509 }
510 }
511
512 fn check_is_lowercase(actual: &Value) -> Result<bool, EvaluationError> {
513 match actual {
514 Value::String(s) => {
515 let has_letters = s.chars().any(|c| c.is_alphabetic());
516 if !has_letters {
517 return Err(EvaluationError::InvalidCaseOperation);
518 }
519 Ok(s.chars()
520 .filter(|c| c.is_alphabetic())
521 .all(|c| c.is_lowercase()))
522 }
523 _ => Err(EvaluationError::InvalidCaseOperation),
524 }
525 }
526
527 fn check_is_uppercase(actual: &Value) -> Result<bool, EvaluationError> {
528 match actual {
529 Value::String(s) => {
530 let has_letters = s.chars().any(|c| c.is_alphabetic());
531 if !has_letters {
532 return Err(EvaluationError::InvalidCaseOperation);
533 }
534 Ok(s.chars()
535 .filter(|c| c.is_alphabetic())
536 .all(|c| c.is_uppercase()))
537 }
538 _ => Err(EvaluationError::InvalidCaseOperation),
539 }
540 }
541
542 fn check_contains_word(actual: &Value, expected: &Value) -> Result<bool, EvaluationError> {
543 match (actual, expected) {
544 (Value::String(s), Value::String(word)) => {
545 let word_regex = Regex::new(&format!(r"\b{}\b", regex::escape(word)))
546 .map_err(EvaluationError::RegexError)?;
547 Ok(word_regex.is_match(s))
548 }
549 _ => Err(EvaluationError::InvalidContainsWordOperation),
550 }
551 }
552
553 fn check_approximately_equals(
555 actual: &Value,
556 expected: &Value,
557 ) -> Result<bool, EvaluationError> {
558 match expected {
559 Value::Array(arr) if arr.len() == 2 => {
560 let actual_num = actual
561 .as_numeric()
562 .ok_or(EvaluationError::CannotCompareNonNumericValues)?;
563 let expected_num = arr[0]
564 .as_numeric()
565 .ok_or(EvaluationError::InvalidToleranceFormat)?;
566 let tolerance = arr[1]
567 .as_numeric()
568 .ok_or(EvaluationError::InvalidToleranceFormat)?;
569
570 Ok((actual_num - expected_num).abs() <= tolerance)
571 }
572 _ => Err(EvaluationError::InvalidToleranceFormat),
573 }
574 }
575}
576#[cfg(test)]
577mod tests {
578 use super::*;
579 use scouter_types::genai::AssertionTask;
580 use scouter_types::genai::EvaluationTaskType;
581 use serde_json::json;
582
583 fn get_test_json() -> Value {
585 json!({
586 "tasks": ["task1", "task2", "task3"],
587 "status": "in_progress",
588 "metadata": {
589 "created_by": "user_123",
590 "priority": "high",
591 "tags": ["urgent", "backend"],
592 "nested": {
593 "deep": {
594 "value": "found_it"
595 }
596 }
597 },
598 "counts": {
599 "total": 42,
600 "completed": 15
601 },
602 "empty_array": [],
603 "single_item": ["only_one"]
604 })
605 }
606
607 fn priority_assertion() -> AssertionTask {
608 AssertionTask {
609 id: "priority_check".to_string(),
610 field_path: Some("metadata.priority".to_string()),
611 operator: ComparisonOperator::Equals,
612 expected_value: Value::String("high".to_string()),
613 description: Some("Check if priority is high".to_string()),
614 task_type: EvaluationTaskType::Assertion,
615 depends_on: vec![],
616 result: None,
617 condition: false,
618 }
619 }
620
621 fn match_assertion() -> AssertionTask {
622 AssertionTask {
623 id: "status_match".to_string(),
624 field_path: Some("status".to_string()),
625 operator: ComparisonOperator::Matches,
626 expected_value: Value::String(r"^in_.*$".to_string()),
627 description: Some("Status should start with 'in_'".to_string()),
628 task_type: EvaluationTaskType::Assertion,
629 depends_on: vec![],
630 result: None,
631 condition: false,
632 }
633 }
634
635 fn length_assertion() -> AssertionTask {
636 AssertionTask {
637 id: "tasks_length".to_string(),
638 field_path: Some("tasks".to_string()),
639 operator: ComparisonOperator::HasLengthEqual,
640 expected_value: Value::Number(3.into()),
641 description: Some("There should be 3 tasks".to_string()),
642 task_type: EvaluationTaskType::Assertion,
643 depends_on: vec![],
644 result: None,
645 condition: false,
646 }
647 }
648
649 fn length_assertion_greater() -> AssertionTask {
650 AssertionTask {
651 id: "tasks_length_gte".to_string(),
652 field_path: Some("tasks".to_string()),
653 operator: ComparisonOperator::HasLengthGreaterThanOrEqual,
654 expected_value: Value::Number(2.into()),
655 description: Some("There should be more than 2 tasks".to_string()),
656 task_type: EvaluationTaskType::Assertion,
657 depends_on: vec![],
658 result: None,
659 condition: false,
660 }
661 }
662
663 fn length_assertion_less() -> AssertionTask {
664 AssertionTask {
665 id: "tasks_length_lte".to_string(),
666 field_path: Some("tasks".to_string()),
667 operator: ComparisonOperator::HasLengthLessThanOrEqual,
668 expected_value: Value::Number(5.into()),
669 description: Some("There should be less than 5 tasks".to_string()),
670 task_type: EvaluationTaskType::Assertion,
671 depends_on: vec![],
672 result: None,
673 condition: false,
674 }
675 }
676
677 fn contains_assertion() -> AssertionTask {
678 AssertionTask {
679 id: "tags_contains".to_string(),
680 field_path: Some("metadata.tags".to_string()),
681 operator: ComparisonOperator::Contains,
682 expected_value: Value::String("backend".to_string()),
683 description: Some("Tags should contain 'backend'".to_string()),
684 task_type: EvaluationTaskType::Assertion,
685 depends_on: vec![],
686 result: None,
687 condition: false,
688 }
689 }
690
691 fn not_equal_assertion() -> AssertionTask {
692 AssertionTask {
693 id: "status_not_equal".to_string(),
694 field_path: Some("status".to_string()),
695 operator: ComparisonOperator::NotEqual,
696 expected_value: Value::String("completed".to_string()),
697 description: Some("Status should not be completed".to_string()),
698 task_type: EvaluationTaskType::Assertion,
699 depends_on: vec![],
700 result: None,
701 condition: false,
702 }
703 }
704
705 fn greater_than_assertion() -> AssertionTask {
706 AssertionTask {
707 id: "total_greater".to_string(),
708 field_path: Some("counts.total".to_string()),
709 operator: ComparisonOperator::GreaterThan,
710 expected_value: Value::Number(40.into()),
711 description: Some("Total should be greater than 40".to_string()),
712 task_type: EvaluationTaskType::Assertion,
713 depends_on: vec![],
714 result: None,
715 condition: false,
716 }
717 }
718
719 fn less_than_assertion() -> AssertionTask {
720 AssertionTask {
721 id: "completed_less".to_string(),
722 field_path: Some("counts.completed".to_string()),
723 operator: ComparisonOperator::LessThan,
724 expected_value: Value::Number(20.into()),
725 description: Some("Completed should be less than 20".to_string()),
726 task_type: EvaluationTaskType::Assertion,
727 depends_on: vec![],
728 result: None,
729 condition: false,
730 }
731 }
732
733 fn not_contains_assertion() -> AssertionTask {
734 AssertionTask {
735 id: "tags_not_contains".to_string(),
736 field_path: Some("metadata.tags".to_string()),
737 operator: ComparisonOperator::NotContains,
738 expected_value: Value::String("frontend".to_string()),
739 description: Some("Tags should not contain 'frontend'".to_string()),
740 task_type: EvaluationTaskType::Assertion,
741 depends_on: vec![],
742 result: None,
743 condition: false,
744 }
745 }
746
747 fn starts_with_assertion() -> AssertionTask {
748 AssertionTask {
749 id: "status_starts_with".to_string(),
750 field_path: Some("status".to_string()),
751 operator: ComparisonOperator::StartsWith,
752 expected_value: Value::String("in_".to_string()),
753 description: Some("Status should start with 'in_'".to_string()),
754 task_type: EvaluationTaskType::Assertion,
755 depends_on: vec![],
756 result: None,
757 condition: false,
758 }
759 }
760
761 fn ends_with_assertion() -> AssertionTask {
762 AssertionTask {
763 id: "status_ends_with".to_string(),
764 field_path: Some("status".to_string()),
765 operator: ComparisonOperator::EndsWith,
766 expected_value: Value::String("_progress".to_string()),
767 description: Some("Status should end with '_progress'".to_string()),
768 task_type: EvaluationTaskType::Assertion,
769 depends_on: vec![],
770 result: None,
771 condition: false,
772 }
773 }
774
775 #[test]
776 fn test_parse_field_path_simple_field() {
777 let segments = FieldEvaluator::parse_field_path("status").unwrap();
778 assert_eq!(segments, vec![PathSegment::Field("status".to_string())]);
779 }
780
781 #[test]
782 fn test_parse_field_path_nested_field() {
783 let segments = FieldEvaluator::parse_field_path("metadata.created_by").unwrap();
784 assert_eq!(
785 segments,
786 vec![
787 PathSegment::Field("metadata".to_string()),
788 PathSegment::Field("created_by".to_string())
789 ]
790 );
791 }
792
793 #[test]
794 fn test_parse_field_path_array_index() {
795 let segments = FieldEvaluator::parse_field_path("tasks[0]").unwrap();
796 assert_eq!(
797 segments,
798 vec![
799 PathSegment::Field("tasks".to_string()),
800 PathSegment::Index(0)
801 ]
802 );
803 }
804
805 #[test]
806 fn test_parse_field_path_complex() {
807 let segments = FieldEvaluator::parse_field_path("metadata.tags[1]").unwrap();
808 assert_eq!(
809 segments,
810 vec![
811 PathSegment::Field("metadata".to_string()),
812 PathSegment::Field("tags".to_string()),
813 PathSegment::Index(1)
814 ]
815 );
816 }
817
818 #[test]
819 fn test_parse_field_path_deep_nested() {
820 let segments = FieldEvaluator::parse_field_path("metadata.nested.deep.value").unwrap();
821 assert_eq!(
822 segments,
823 vec![
824 PathSegment::Field("metadata".to_string()),
825 PathSegment::Field("nested".to_string()),
826 PathSegment::Field("deep".to_string()),
827 PathSegment::Field("value".to_string())
828 ]
829 );
830 }
831
832 #[test]
833 fn test_parse_field_path_underscore_field() {
834 let segments = FieldEvaluator::parse_field_path("created_by").unwrap();
835 assert_eq!(segments, vec![PathSegment::Field("created_by".to_string())]);
836 }
837
838 #[test]
839 fn test_parse_field_path_empty_string() {
840 let result = FieldEvaluator::parse_field_path("");
841 assert!(result.is_err());
842 assert!(result.unwrap_err().to_string().contains("Empty field path"));
843 }
844
845 #[test]
846 fn test_extract_simple_field() {
847 let json = get_test_json();
848 let result = FieldEvaluator::extract_field_value(&json, "status").unwrap();
849 assert_eq!(*result, json!("in_progress"));
850 }
851
852 #[test]
853 fn test_extract_array_field() {
854 let json = get_test_json();
855 let result = FieldEvaluator::extract_field_value(&json, "tasks").unwrap();
856 assert_eq!(*result, json!(["task1", "task2", "task3"]));
857 }
858
859 #[test]
860 fn test_extract_array_element() {
861 let json = get_test_json();
862 let result = FieldEvaluator::extract_field_value(&json, "tasks[0]").unwrap();
863 assert_eq!(*result, json!("task1"));
864
865 let result = FieldEvaluator::extract_field_value(&json, "tasks[2]").unwrap();
866 assert_eq!(*result, json!("task3"));
867 }
868
869 #[test]
870 fn test_extract_nested_field() {
871 let json = get_test_json();
872 let result = FieldEvaluator::extract_field_value(&json, "metadata.created_by").unwrap();
873 assert_eq!(*result, json!("user_123"));
874
875 let result = FieldEvaluator::extract_field_value(&json, "metadata.priority").unwrap();
876 assert_eq!(*result, json!("high"));
877 }
878
879 #[test]
880 fn test_extract_nested_array_element() {
881 let json = get_test_json();
882 let result = FieldEvaluator::extract_field_value(&json, "metadata.tags[0]").unwrap();
883 assert_eq!(*result, json!("urgent"));
884
885 let result = FieldEvaluator::extract_field_value(&json, "metadata.tags[1]").unwrap();
886 assert_eq!(*result, json!("backend"));
887 }
888
889 #[test]
890 fn test_extract_deep_nested_field() {
891 let json = get_test_json();
892 let result =
893 FieldEvaluator::extract_field_value(&json, "metadata.nested.deep.value").unwrap();
894 assert_eq!(*result, json!("found_it"));
895 }
896
897 #[test]
898 fn test_extract_numeric_field() {
899 let json = get_test_json();
900 let result = FieldEvaluator::extract_field_value(&json, "counts.total").unwrap();
901 assert_eq!(*result, json!(42));
902
903 let result = FieldEvaluator::extract_field_value(&json, "counts.completed").unwrap();
904 assert_eq!(*result, json!(15));
905 }
906
907 #[test]
908 fn test_extract_empty_array() {
909 let json = get_test_json();
910 let result = FieldEvaluator::extract_field_value(&json, "empty_array").unwrap();
911 assert_eq!(*result, json!([]));
912 }
913
914 #[test]
915 fn test_extract_single_item_array() {
916 let json = get_test_json();
917 let result = FieldEvaluator::extract_field_value(&json, "single_item[0]").unwrap();
918 assert_eq!(*result, json!("only_one"));
919 }
920
921 #[test]
922 fn test_extract_nonexistent_field() {
923 let json = get_test_json();
924 let result = FieldEvaluator::extract_field_value(&json, "nonexistent");
925 assert!(result.is_err());
926 assert!(result
927 .unwrap_err()
928 .to_string()
929 .contains("Field 'nonexistent' not found"));
930 }
931
932 #[test]
933 fn test_extract_nonexistent_nested_field() {
934 let json = get_test_json();
935 let result = FieldEvaluator::extract_field_value(&json, "metadata.nonexistent");
936 assert!(result.is_err());
937 assert!(result
938 .unwrap_err()
939 .to_string()
940 .contains("Field 'nonexistent' not found"));
941 }
942
943 #[test]
944 fn test_extract_array_index_out_of_bounds() {
945 let json = get_test_json();
946 let result = FieldEvaluator::extract_field_value(&json, "tasks[99]");
947 assert!(result.is_err());
948 assert!(result
949 .unwrap_err()
950 .to_string()
951 .contains("Index 99 not found"));
952 }
953
954 #[test]
955 fn test_extract_array_index_on_non_array() {
956 let json = get_test_json();
957 let result = FieldEvaluator::extract_field_value(&json, "status[0]");
958 assert!(result.is_err());
959 assert!(result
960 .unwrap_err()
961 .to_string()
962 .contains("Index 0 not found"));
963 }
964
965 #[test]
966 fn test_extract_field_on_array_element() {
967 let json = json!({
968 "users": [
969 {"name": "Alice", "age": 30},
970 {"name": "Bob", "age": 25}
971 ]
972 });
973
974 let result = FieldEvaluator::extract_field_value(&json, "users[0].name").unwrap();
975 assert_eq!(*result, json!("Alice"));
976
977 let result = FieldEvaluator::extract_field_value(&json, "users[1].age").unwrap();
978 assert_eq!(*result, json!(25));
979 }
980
981 #[test]
982 fn test_structured_task_output_scenarios() {
983 let json = json!({
985 "tasks": ["setup_database", "create_api", "write_tests"],
986 "status": "in_progress"
987 });
988
989 let tasks = FieldEvaluator::extract_field_value(&json, "tasks").unwrap();
991 assert!(tasks.is_array());
992 assert_eq!(tasks.as_array().unwrap().len(), 3);
993
994 let first_task = FieldEvaluator::extract_field_value(&json, "tasks[0]").unwrap();
996 assert_eq!(*first_task, json!("setup_database"));
997
998 let status = FieldEvaluator::extract_field_value(&json, "status").unwrap();
1000 assert_eq!(*status, json!("in_progress"));
1001 }
1002
1003 #[test]
1004 fn test_real_world_llm_response_structure() {
1005 let json = json!({
1007 "analysis": {
1008 "sentiment": "positive",
1009 "confidence": 0.85,
1010 "keywords": ["innovation", "growth", "success"]
1011 },
1012 "recommendations": [
1013 {
1014 "action": "increase_investment",
1015 "priority": "high",
1016 "estimated_impact": 0.75
1017 },
1018 {
1019 "action": "expand_team",
1020 "priority": "medium",
1021 "estimated_impact": 0.60
1022 }
1023 ],
1024 "summary": "Overall positive outlook with strong growth potential"
1025 });
1026
1027 let sentiment = FieldEvaluator::extract_field_value(&json, "analysis.sentiment").unwrap();
1029 assert_eq!(*sentiment, json!("positive"));
1030
1031 let first_action =
1033 FieldEvaluator::extract_field_value(&json, "recommendations[0].action").unwrap();
1034 assert_eq!(*first_action, json!("increase_investment"));
1035
1036 let confidence = FieldEvaluator::extract_field_value(&json, "analysis.confidence").unwrap();
1038 assert_eq!(*confidence, json!(0.85));
1039 let first_keyword =
1041 FieldEvaluator::extract_field_value(&json, "analysis.keywords[0]").unwrap();
1042 assert_eq!(*first_keyword, json!("innovation"));
1043 }
1044
1045 #[test]
1046 fn test_assertion_equals_pass() {
1047 let json = get_test_json();
1048 let assertion = priority_assertion();
1049 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1050
1051 assert!(result.passed);
1052 assert_eq!(result.actual, json!("high"));
1053 assert!(result.message.contains("passed"));
1054 }
1055
1056 #[test]
1057 fn test_assertion_equals_fail() {
1058 let json = get_test_json();
1059 let mut assertion = priority_assertion();
1060 assertion.expected_value = Value::String("low".to_string());
1061
1062 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1063
1064 assert!(!result.passed);
1065 assert_eq!(result.actual, json!("high"));
1066 assert!(result.message.contains("failed"));
1067 }
1068
1069 #[test]
1070 fn test_assertion_not_equal_pass() {
1071 let json = get_test_json();
1072 let assertion = not_equal_assertion();
1073 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1074
1075 assert!(result.passed);
1076 assert_eq!(result.actual, json!("in_progress"));
1077 }
1078
1079 #[test]
1080 fn test_assertion_not_equal_fail() {
1081 let json = get_test_json();
1082 let mut assertion = not_equal_assertion();
1083 assertion.expected_value = Value::String("in_progress".to_string());
1084
1085 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1086
1087 assert!(!result.passed);
1088 }
1089
1090 #[test]
1091 fn test_assertion_greater_than_pass() {
1092 let json = get_test_json();
1093 let assertion = greater_than_assertion();
1094 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1095
1096 assert!(result.passed);
1097 assert_eq!(result.actual, json!(42));
1098 }
1099
1100 #[test]
1101 fn test_assertion_greater_than_fail() {
1102 let json = get_test_json();
1103 let mut assertion = greater_than_assertion();
1104 assertion.expected_value = Value::Number(50.into());
1105
1106 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1107
1108 assert!(!result.passed);
1109 }
1110
1111 #[test]
1112 fn test_assertion_greater_than_or_equal_pass() {
1113 let json = get_test_json();
1114 let assertion = length_assertion_greater();
1115 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1116
1117 assert!(result.passed);
1118 }
1119
1120 #[test]
1121 fn test_assertion_greater_than_or_equal_equal_case() {
1122 let json = get_test_json();
1123 let mut assertion = length_assertion_greater();
1124 assertion.expected_value = Value::Number(3.into());
1125
1126 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1127
1128 assert!(result.passed);
1129 }
1130
1131 #[test]
1132 fn test_assertion_less_than_pass() {
1133 let json = get_test_json();
1134 let assertion = less_than_assertion();
1135 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1136
1137 assert!(result.passed);
1138 assert_eq!(result.actual, json!(15));
1139 }
1140
1141 #[test]
1142 fn test_assertion_less_than_fail() {
1143 let json = get_test_json();
1144 let mut assertion = less_than_assertion();
1145 assertion.expected_value = Value::Number(10.into());
1146
1147 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1148
1149 assert!(!result.passed);
1150 }
1151
1152 #[test]
1153 fn test_assertion_less_than_or_equal_pass() {
1154 let json = get_test_json();
1155 let assertion = length_assertion_less();
1156 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1157
1158 assert!(result.passed);
1159 }
1160
1161 #[test]
1162 fn test_assertion_less_than_or_equal_equal_case() {
1163 let json = get_test_json();
1164 let mut assertion = length_assertion_less();
1165 assertion.expected_value = Value::Number(3.into());
1166
1167 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1168
1169 assert!(result.passed);
1170 }
1171
1172 #[test]
1173 fn test_assertion_has_length_pass() {
1174 let json = get_test_json();
1175 let assertion = length_assertion();
1176 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1177
1178 assert!(result.passed);
1179 }
1180
1181 #[test]
1182 fn test_assertion_has_length_fail() {
1183 let json = get_test_json();
1184 let mut assertion = length_assertion();
1185 assertion.expected_value = Value::Number(5.into());
1186
1187 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1188
1189 assert!(!result.passed);
1190 }
1191
1192 #[test]
1193 fn test_assertion_has_length_string() {
1194 let json = json!({"name": "test_user"});
1195 let assertion = AssertionTask {
1196 id: "name_length".to_string(),
1197 field_path: Some("name".to_string()),
1198 operator: ComparisonOperator::HasLengthEqual,
1199 expected_value: Value::Number(9.into()),
1200 description: Some("Name should have 9 characters".to_string()),
1201 task_type: EvaluationTaskType::Assertion,
1202 depends_on: vec![],
1203 result: None,
1204 condition: false,
1205 };
1206
1207 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1208
1209 assert!(result.passed);
1210 }
1211
1212 #[test]
1213 fn test_assertion_contains_array_pass() {
1214 let json = get_test_json();
1215 let assertion = contains_assertion();
1216 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1217
1218 assert!(result.passed);
1219 }
1220
1221 #[test]
1222 fn test_assertion_contains_array_fail() {
1223 let json = get_test_json();
1224 let mut assertion = contains_assertion();
1225 assertion.expected_value = Value::String("frontend".to_string());
1226
1227 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1228
1229 assert!(!result.passed);
1230 }
1231
1232 #[test]
1233 fn test_assertion_contains_string_pass() {
1234 let json = get_test_json();
1235 let assertion = AssertionTask {
1236 id: "status_contains_prog".to_string(),
1237 field_path: Some("status".to_string()),
1238 operator: ComparisonOperator::Contains,
1239 expected_value: Value::String("progress".to_string()),
1240 description: Some("Status should contain 'progress'".to_string()),
1241 task_type: EvaluationTaskType::Assertion,
1242 depends_on: vec![],
1243 result: None,
1244 condition: false,
1245 };
1246
1247 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1248
1249 assert!(result.passed);
1250 }
1251
1252 #[test]
1253 fn test_assertion_not_contains_pass() {
1254 let json = get_test_json();
1255 let assertion = not_contains_assertion();
1256 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1257
1258 assert!(result.passed);
1259 }
1260
1261 #[test]
1262 fn test_assertion_not_contains_fail() {
1263 let json = get_test_json();
1264 let mut assertion = not_contains_assertion();
1265 assertion.expected_value = Value::String("backend".to_string());
1266
1267 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1268
1269 assert!(!result.passed);
1270 }
1271
1272 #[test]
1273 fn test_assertion_starts_with_pass() {
1274 let json = get_test_json();
1275 let assertion = starts_with_assertion();
1276 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1277
1278 assert!(result.passed);
1279 }
1280
1281 #[test]
1282 fn test_assertion_starts_with_fail() {
1283 let json = get_test_json();
1284 let mut assertion = starts_with_assertion();
1285 assertion.expected_value = Value::String("completed".to_string());
1286
1287 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1288
1289 assert!(!result.passed);
1290 }
1291
1292 #[test]
1293 fn test_assertion_ends_with_pass() {
1294 let json = get_test_json();
1295 let assertion = ends_with_assertion();
1296 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1297
1298 assert!(result.passed);
1299 }
1300
1301 #[test]
1302 fn test_assertion_ends_with_fail() {
1303 let json = get_test_json();
1304 let mut assertion = ends_with_assertion();
1305 assertion.expected_value = Value::String("_pending".to_string());
1306
1307 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1308
1309 assert!(!result.passed);
1310 }
1311
1312 #[test]
1313 fn test_assertion_matches_pass() {
1314 let json = get_test_json();
1315 let assertion = match_assertion();
1316 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1317
1318 assert!(result.passed);
1319 }
1320
1321 #[test]
1322 fn test_assertion_matches_fail() {
1323 let json = get_test_json();
1324 let mut assertion = match_assertion();
1325 assertion.expected_value = Value::String(r"^completed.*$".to_string());
1326
1327 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1328
1329 assert!(!result.passed);
1330 }
1331
1332 #[test]
1333 fn test_assertion_matches_complex_regex() {
1334 let json = get_test_json();
1335 let assertion = AssertionTask {
1336 id: "user_format".to_string(),
1337 field_path: Some("metadata.created_by".to_string()),
1338 operator: ComparisonOperator::Matches,
1339 expected_value: Value::String(r"^user_\d+$".to_string()),
1340 description: Some("User ID should match format user_###".to_string()),
1341 task_type: EvaluationTaskType::Assertion,
1342 depends_on: vec![],
1343 result: None,
1344 condition: false,
1345 };
1346
1347 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1348
1349 assert!(result.passed);
1350 }
1351
1352 #[test]
1353 fn test_assertion_no_field_path_evaluates_root() {
1354 let json = json!({"status": "active"});
1355 let assertion = AssertionTask {
1356 id: "root_check".to_string(),
1357 field_path: None,
1358 operator: ComparisonOperator::Equals,
1359 expected_value: json!({"status": "active"}),
1360 description: Some("Check entire root object".to_string()),
1361 task_type: EvaluationTaskType::Assertion,
1362 depends_on: vec![],
1363 result: None,
1364 condition: false,
1365 };
1366
1367 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1368
1369 assert!(result.passed);
1370 }
1371
1372 #[test]
1373 fn test_assertion_empty_array_length() {
1374 let json = get_test_json();
1375 let assertion = AssertionTask {
1376 id: "empty_array_length".to_string(),
1377 field_path: Some("empty_array".to_string()),
1378 operator: ComparisonOperator::HasLengthEqual,
1379 expected_value: Value::Number(0.into()),
1380 description: Some("Empty array should have length 0".to_string()),
1381 task_type: EvaluationTaskType::Assertion,
1382 depends_on: vec![],
1383 result: None,
1384 condition: false,
1385 };
1386
1387 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1388
1389 assert!(result.passed);
1390 }
1391
1392 #[test]
1393 fn test_assertion_numeric_comparison_with_floats() {
1394 let json = json!({"score": 85.5});
1395 let assertion = AssertionTask {
1396 id: "score_check".to_string(),
1397 field_path: Some("score".to_string()),
1398 operator: ComparisonOperator::GreaterThanOrEqual,
1399 expected_value: json!(85.0),
1400 description: Some("Score should be at least 85".to_string()),
1401 task_type: EvaluationTaskType::Assertion,
1402 depends_on: vec![],
1403 result: None,
1404 condition: false,
1405 };
1406
1407 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1408
1409 assert!(result.passed);
1410 }
1411
1412 #[test]
1413 fn test_assertion_error_field_not_found() {
1414 let json = get_test_json();
1415 let assertion = AssertionTask {
1416 id: "missing_field".to_string(),
1417 field_path: Some("nonexistent.field".to_string()),
1418 operator: ComparisonOperator::Equals,
1419 expected_value: Value::String("value".to_string()),
1420 description: Some("Should fail with field not found".to_string()),
1421 task_type: EvaluationTaskType::Assertion,
1422 depends_on: vec![],
1423 result: None,
1424 condition: false,
1425 };
1426
1427 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion);
1428
1429 assert!(result.is_err());
1430 assert!(result.unwrap_err().to_string().contains("not found"));
1431 }
1432
1433 #[test]
1434 fn test_assertion_error_invalid_regex() {
1435 let json = get_test_json();
1436 let assertion = AssertionTask {
1437 id: "bad_regex".to_string(),
1438 field_path: Some("status".to_string()),
1439 operator: ComparisonOperator::Matches,
1440 expected_value: Value::String("[invalid(".to_string()),
1441 description: Some("Invalid regex pattern".to_string()),
1442 task_type: EvaluationTaskType::Assertion,
1443 depends_on: vec![],
1444 result: None,
1445 condition: false,
1446 };
1447
1448 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion);
1449
1450 assert!(result.is_err());
1451 }
1452
1453 #[test]
1454 fn test_assertion_error_type_mismatch_starts_with() {
1455 let json = get_test_json();
1456 let assertion = AssertionTask {
1457 id: "type_mismatch".to_string(),
1458 field_path: Some("counts.total".to_string()),
1459 operator: ComparisonOperator::StartsWith,
1460 expected_value: Value::String("4".to_string()),
1461 description: Some("Cannot use StartsWith on number".to_string()),
1462 task_type: EvaluationTaskType::Assertion,
1463 depends_on: vec![],
1464 result: None,
1465 condition: false,
1466 };
1467
1468 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion);
1469
1470 assert!(result.is_err());
1471 }
1472
1473 #[test]
1474 fn test_assertion_error_type_mismatch_numeric_comparison() {
1475 let json = json!({"value": "not_a_number"});
1476 let assertion = AssertionTask {
1477 id: "numeric_on_string".to_string(),
1478 field_path: Some("value".to_string()),
1479 operator: ComparisonOperator::GreaterThan,
1480 expected_value: Value::Number(10.into()),
1481 description: Some("Cannot compare string with number".to_string()),
1482 task_type: EvaluationTaskType::Assertion,
1483 depends_on: vec![],
1484 result: None,
1485 condition: false,
1486 };
1487
1488 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1489 assert!(!result.passed);
1490 }
1491
1492 #[test]
1493 fn test_is_numeric_pass() {
1494 let json = json!({"value": 42});
1495 let assertion = AssertionTask {
1496 id: "type_check".to_string(),
1497 field_path: Some("value".to_string()),
1498 operator: ComparisonOperator::IsNumeric,
1499 expected_value: Value::Bool(true),
1500 description: Some("Value should be numeric".to_string()),
1501 task_type: EvaluationTaskType::Assertion,
1502 depends_on: vec![],
1503 result: None,
1504 condition: false,
1505 };
1506
1507 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1508 assert!(result.passed);
1509 }
1510
1511 #[test]
1512 fn test_is_string_pass() {
1513 let json = json!({"value": "hello"});
1514 let assertion = AssertionTask {
1515 id: "type_check".to_string(),
1516 field_path: Some("value".to_string()),
1517 operator: ComparisonOperator::IsString,
1518 expected_value: Value::Bool(true),
1519 description: None,
1520 task_type: EvaluationTaskType::Assertion,
1521 depends_on: vec![],
1522 result: None,
1523 condition: false,
1524 };
1525
1526 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1527 assert!(result.passed);
1528 }
1529
1530 #[test]
1531 fn test_is_array_pass() {
1532 let json = json!({"value": [1, 2, 3]});
1533 let assertion = AssertionTask {
1534 id: "type_check".to_string(),
1535 field_path: Some("value".to_string()),
1536 operator: ComparisonOperator::IsArray,
1537 expected_value: Value::Bool(true),
1538 description: None,
1539 task_type: EvaluationTaskType::Assertion,
1540 depends_on: vec![],
1541 result: None,
1542 condition: false,
1543 };
1544
1545 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1546 assert!(result.passed);
1547 }
1548
1549 #[test]
1551 fn test_is_email_pass() {
1552 let json = json!({"email": "user@example.com"});
1553 let assertion = AssertionTask {
1554 id: "email_check".to_string(),
1555 field_path: Some("email".to_string()),
1556 operator: ComparisonOperator::IsEmail,
1557 expected_value: Value::Bool(true),
1558 description: None,
1559 task_type: EvaluationTaskType::Assertion,
1560 depends_on: vec![],
1561 result: None,
1562 condition: false,
1563 };
1564
1565 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1566 assert!(result.passed);
1567 }
1568
1569 #[test]
1570 fn test_is_email_fail() {
1571 let json = json!({"email": "not-an-email"});
1572 let assertion = AssertionTask {
1573 id: "email_check".to_string(),
1574 field_path: Some("email".to_string()),
1575 operator: ComparisonOperator::IsEmail,
1576 expected_value: Value::Bool(true),
1577 description: None,
1578 task_type: EvaluationTaskType::Assertion,
1579 depends_on: vec![],
1580 result: None,
1581 condition: false,
1582 };
1583
1584 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1585 assert!(!result.passed);
1586 }
1587
1588 #[test]
1589 fn test_is_url_pass() {
1590 let json = json!({"url": "https://example.com"});
1591 let assertion = AssertionTask {
1592 id: "url_check".to_string(),
1593 field_path: Some("url".to_string()),
1594 operator: ComparisonOperator::IsUrl,
1595 expected_value: Value::Bool(true),
1596 description: None,
1597 task_type: EvaluationTaskType::Assertion,
1598 depends_on: vec![],
1599 result: None,
1600 condition: false,
1601 };
1602
1603 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1604 assert!(result.passed);
1605 }
1606
1607 #[test]
1608 fn test_is_uuid_pass() {
1609 let json = json!({"id": "550e8400-e29b-41d4-a716-446655440000"});
1610 let assertion = AssertionTask {
1611 id: "uuid_check".to_string(),
1612 field_path: Some("id".to_string()),
1613 operator: ComparisonOperator::IsUuid,
1614 expected_value: Value::Bool(true),
1615 description: None,
1616 task_type: EvaluationTaskType::Assertion,
1617 depends_on: vec![],
1618 result: None,
1619 condition: false,
1620 };
1621
1622 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1623 assert!(result.passed);
1624 }
1625
1626 #[test]
1627 fn test_is_iso8601_pass() {
1628 let json = json!({"timestamp": "2024-01-05T10:30:00Z"});
1629 let assertion = AssertionTask {
1630 id: "iso_check".to_string(),
1631 field_path: Some("timestamp".to_string()),
1632 operator: ComparisonOperator::IsIso8601,
1633 expected_value: Value::Bool(true),
1634 description: None,
1635 task_type: EvaluationTaskType::Assertion,
1636 depends_on: vec![],
1637 result: None,
1638 condition: false,
1639 };
1640
1641 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1642 assert!(result.passed);
1643 }
1644
1645 #[test]
1646 fn test_is_json_pass() {
1647 let json = json!({"data": r#"{"key": "value"}"#});
1648 let assertion = AssertionTask {
1649 id: "json_check".to_string(),
1650 field_path: Some("data".to_string()),
1651 operator: ComparisonOperator::IsJson,
1652 expected_value: Value::Bool(true),
1653 description: None,
1654 task_type: EvaluationTaskType::Assertion,
1655 depends_on: vec![],
1656 result: None,
1657 condition: false,
1658 };
1659
1660 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1661 assert!(result.passed);
1662 }
1663
1664 #[test]
1666 fn test_in_range_pass() {
1667 let json = json!({"score": 75});
1668 let assertion = AssertionTask {
1669 id: "range_check".to_string(),
1670 field_path: Some("score".to_string()),
1671 operator: ComparisonOperator::InRange,
1672 expected_value: json!([0, 100]),
1673 description: None,
1674 task_type: EvaluationTaskType::Assertion,
1675 depends_on: vec![],
1676 result: None,
1677 condition: false,
1678 };
1679
1680 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1681 assert!(result.passed);
1682 }
1683
1684 #[test]
1685 fn test_in_range_fail() {
1686 let json = json!({"score": 150});
1687 let assertion = AssertionTask {
1688 id: "range_check".to_string(),
1689 field_path: Some("score".to_string()),
1690 operator: ComparisonOperator::InRange,
1691 expected_value: json!([0, 100]),
1692 description: None,
1693 task_type: EvaluationTaskType::Assertion,
1694 depends_on: vec![],
1695 result: None,
1696 condition: false,
1697 };
1698
1699 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1700 assert!(!result.passed);
1701 }
1702
1703 #[test]
1704 fn test_is_positive_pass() {
1705 let json = json!({"value": 42});
1706 let assertion = AssertionTask {
1707 id: "positive_check".to_string(),
1708 field_path: Some("value".to_string()),
1709 operator: ComparisonOperator::IsPositive,
1710 expected_value: Value::Bool(true),
1711 description: None,
1712 task_type: EvaluationTaskType::Assertion,
1713 depends_on: vec![],
1714 result: None,
1715 condition: false,
1716 };
1717
1718 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1719 assert!(result.passed);
1720 }
1721
1722 #[test]
1723 fn test_is_negative_pass() {
1724 let json = json!({"value": -42});
1725 let assertion = AssertionTask {
1726 id: "negative_check".to_string(),
1727 field_path: Some("value".to_string()),
1728 operator: ComparisonOperator::IsNegative,
1729 expected_value: Value::Bool(true),
1730 description: None,
1731 task_type: EvaluationTaskType::Assertion,
1732 depends_on: vec![],
1733 result: None,
1734 condition: false,
1735 };
1736
1737 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1738 assert!(result.passed);
1739 }
1740
1741 #[test]
1743 fn test_contains_all_pass() {
1744 let json = json!({"tags": ["rust", "python", "javascript", "go"]});
1745 let assertion = AssertionTask {
1746 id: "contains_all_check".to_string(),
1747 field_path: Some("tags".to_string()),
1748 operator: ComparisonOperator::ContainsAll,
1749 expected_value: json!(["rust", "python"]),
1750 description: None,
1751 task_type: EvaluationTaskType::Assertion,
1752 depends_on: vec![],
1753 result: None,
1754 condition: false,
1755 };
1756
1757 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1758 assert!(result.passed);
1759 }
1760
1761 #[test]
1762 fn test_contains_any_pass() {
1763 let json = json!({"tags": ["rust", "python"]});
1764 let assertion = AssertionTask {
1765 id: "contains_any_check".to_string(),
1766 field_path: Some("tags".to_string()),
1767 operator: ComparisonOperator::ContainsAny,
1768 expected_value: json!(["python", "java", "c++"]),
1769 description: None,
1770 task_type: EvaluationTaskType::Assertion,
1771 depends_on: vec![],
1772 result: None,
1773 condition: false,
1774 };
1775
1776 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1777 assert!(result.passed);
1778 }
1779
1780 #[test]
1781 fn test_is_empty_pass() {
1782 let json = json!({"list": []});
1783 let assertion = AssertionTask {
1784 id: "empty_check".to_string(),
1785 field_path: Some("list".to_string()),
1786 operator: ComparisonOperator::IsEmpty,
1787 expected_value: Value::Bool(true),
1788 description: None,
1789 task_type: EvaluationTaskType::Assertion,
1790 depends_on: vec![],
1791 result: None,
1792 condition: false,
1793 };
1794
1795 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1796 assert!(result.passed);
1797 }
1798
1799 #[test]
1800 fn test_has_unique_items_pass() {
1801 let json = json!({"items": [1, 2, 3, 4]});
1802 let assertion = AssertionTask {
1803 id: "unique_check".to_string(),
1804 field_path: Some("items".to_string()),
1805 operator: ComparisonOperator::HasUniqueItems,
1806 expected_value: Value::Bool(true),
1807 description: None,
1808 task_type: EvaluationTaskType::Assertion,
1809 depends_on: vec![],
1810 result: None,
1811 condition: false,
1812 };
1813
1814 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1815 assert!(result.passed);
1816 }
1817
1818 #[test]
1819 fn test_has_unique_items_fail() {
1820 let json = json!({"items": [1, 2, 2, 3]});
1821 let assertion = AssertionTask {
1822 id: "unique_check".to_string(),
1823 field_path: Some("items".to_string()),
1824 operator: ComparisonOperator::HasUniqueItems,
1825 expected_value: Value::Bool(true),
1826 description: None,
1827 task_type: EvaluationTaskType::Assertion,
1828 depends_on: vec![],
1829 result: None,
1830 condition: false,
1831 };
1832
1833 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1834 assert!(!result.passed);
1835 }
1836
1837 #[test]
1839 fn test_is_alphabetic_pass() {
1840 let json = json!({"text": "HelloWorld"});
1841 let assertion = AssertionTask {
1842 id: "alpha_check".to_string(),
1843 field_path: Some("text".to_string()),
1844 operator: ComparisonOperator::IsAlphabetic,
1845 expected_value: Value::Bool(true),
1846 description: None,
1847 task_type: EvaluationTaskType::Assertion,
1848 depends_on: vec![],
1849 result: None,
1850 condition: false,
1851 };
1852
1853 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1854 assert!(result.passed);
1855 }
1856
1857 #[test]
1858 fn test_is_alphanumeric_pass() {
1859 let json = json!({"text": "Hello123"});
1860 let assertion = AssertionTask {
1861 id: "alphanum_check".to_string(),
1862 field_path: Some("text".to_string()),
1863 operator: ComparisonOperator::IsAlphanumeric,
1864 expected_value: Value::Bool(true),
1865 description: None,
1866 task_type: EvaluationTaskType::Assertion,
1867 depends_on: vec![],
1868 result: None,
1869 condition: false,
1870 };
1871
1872 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1873 assert!(result.passed);
1874 }
1875
1876 #[test]
1877 fn test_is_lowercase_pass() {
1878 let json = json!({"text": "hello world"});
1879 let assertion = AssertionTask {
1880 id: "lowercase_check".to_string(),
1881 field_path: Some("text".to_string()),
1882 operator: ComparisonOperator::IsLowerCase,
1883 expected_value: Value::Bool(true),
1884 description: None,
1885 task_type: EvaluationTaskType::Assertion,
1886 depends_on: vec![],
1887 result: None,
1888 condition: false,
1889 };
1890
1891 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1892 assert!(result.passed);
1893 }
1894
1895 #[test]
1896 fn test_is_uppercase_pass() {
1897 let json = json!({"text": "HELLO WORLD"});
1898 let assertion = AssertionTask {
1899 id: "uppercase_check".to_string(),
1900 field_path: Some("text".to_string()),
1901 operator: ComparisonOperator::IsUpperCase,
1902 expected_value: Value::Bool(true),
1903 description: None,
1904 task_type: EvaluationTaskType::Assertion,
1905 depends_on: vec![],
1906 result: None,
1907 condition: false,
1908 };
1909
1910 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1911 assert!(result.passed);
1912 }
1913
1914 #[test]
1915 fn test_contains_word_pass() {
1916 let json = json!({"text": "The quick brown fox"});
1917 let assertion = AssertionTask {
1918 id: "word_check".to_string(),
1919 field_path: Some("text".to_string()),
1920 operator: ComparisonOperator::ContainsWord,
1921 expected_value: Value::String("quick".to_string()),
1922 description: None,
1923 task_type: EvaluationTaskType::Assertion,
1924 depends_on: vec![],
1925 result: None,
1926 condition: false,
1927 };
1928
1929 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1930 assert!(result.passed);
1931 }
1932
1933 #[test]
1934 fn test_contains_word_fail() {
1935 let json = json!({"text": "The quickly brown fox"});
1936 let assertion = AssertionTask {
1937 id: "word_check".to_string(),
1938 field_path: Some("text".to_string()),
1939 operator: ComparisonOperator::ContainsWord,
1940 expected_value: Value::String("quick".to_string()),
1941 description: None,
1942 task_type: EvaluationTaskType::Assertion,
1943 depends_on: vec![],
1944 result: None,
1945 condition: false,
1946 };
1947
1948 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1949 assert!(!result.passed);
1950 }
1951
1952 #[test]
1954 fn test_approximately_equals_pass() {
1955 let json = json!({"value": 100.5});
1956 let assertion = AssertionTask {
1957 id: "approx_check".to_string(),
1958 field_path: Some("value".to_string()),
1959 operator: ComparisonOperator::ApproximatelyEquals,
1960 expected_value: json!([100.0, 1.0]),
1961 description: None,
1962 task_type: EvaluationTaskType::Assertion,
1963 depends_on: vec![],
1964 result: None,
1965 condition: false,
1966 };
1967
1968 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1969 assert!(result.passed);
1970 }
1971
1972 #[test]
1973 fn test_approximately_equals_fail() {
1974 let json = json!({"value": 102.0});
1975 let assertion = AssertionTask {
1976 id: "approx_check".to_string(),
1977 field_path: Some("value".to_string()),
1978 operator: ComparisonOperator::ApproximatelyEquals,
1979 expected_value: json!([100.0, 1.0]),
1980 description: None,
1981 task_type: EvaluationTaskType::Assertion,
1982 depends_on: vec![],
1983 result: None,
1984 condition: false,
1985 };
1986
1987 let result = AssertionEvaluator::evaluate_assertion(&json, &assertion).unwrap();
1988 assert!(!result.passed);
1989 }
1990}