Skip to main content

scouter_evaluate/tasks/
evaluator.rs

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
13/// Utility for extracting field values from JSON-like structures
14/// Supports: "field", "field.subfield", "field[0]", "field[0].subfield"
15impl FieldEvaluator {
16    /// Extracts the value at the specified field path from the given JSON value
17    /// # Arguments
18    /// * `json` - The JSON value to extract from
19    /// * `field_path` - The dot/bracket notation path to the desired field
20    /// # Returns
21    /// The extracted JSON value or an EvaluationError if the path is invalid
22    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    /// Parses a field path string into segments for navigation
44    /// # Arguments
45    /// * `path` - The field path string
46    /// # Returns
47    /// A vector of PathSegment enums representing the parsed path
48    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                // Array index: [0], [1], etc.
61                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                // Field name: field, subfield, etc.
68                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    /// Main function for evaluating an assertion
91    /// # Arguments
92    /// * `json_value` - The JSON value to evaluate against
93    /// * `assertion` - The assertion to evaluate
94    /// # Returns
95    /// An AssertionResult indicating whether the assertion passed or failed
96    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 assertion result failure with error message
113                    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                // Extract field path from template: "${field.path}" -> "field.path"
151                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    /// Transforms a value based on the comparison operator
160    /// This is mainly used to convert array, string and map types that have
161    /// length to their length for length-based comparisons
162    /// # Arguments
163    /// * `value` - The value to transform
164    /// * `operator` - The comparison operator
165    /// # Returns
166    /// The transformed value or an EvaluationError if transformation fails
167    fn transform_for_comparison(
168        value: &Value,
169        operator: &ComparisonOperator,
170    ) -> Result<Value, EvaluationError> {
171        match operator {
172            // Only HasLength should convert to length
173            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            // Numeric comparisons should require actual numbers
185            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            // All other operators pass through unchanged
196            _ => 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            // Existing operators
207            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            // Type Validation Operators
241            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            // Pattern & Format Validators
249            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            // Numeric Range Operators
257            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            // Collection/Array Operators
264            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            // String Operators
272            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            // Comparison with Tolerance
279            ComparisonOperator::ApproximatelyEquals => {
280                Self::check_approximately_equals(actual, expected)
281            }
282        }
283    }
284
285    // Comparison helpers - all work with references
286    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    // Pattern & Format Validation Helpers
337    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                // ISO 8601 date-time format
377                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    // Numeric Range Helpers
395    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    // Collection/Array Helpers
436    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    // String Helpers
498    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    // Tolerance Comparison Helper
554    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    // Test data matching your StructuredTaskOutput example
584    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        // Test scenarios based on your StructuredTaskOutput example
984        let json = json!({
985            "tasks": ["setup_database", "create_api", "write_tests"],
986            "status": "in_progress"
987        });
988
989        // Test extracting the tasks array
990        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        // Test extracting individual task items
995        let first_task = FieldEvaluator::extract_field_value(&json, "tasks[0]").unwrap();
996        assert_eq!(*first_task, json!("setup_database"));
997
998        // Test extracting status
999        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        // Test with a more complex LLM response structure
1006        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        // Test nested object extraction
1028        let sentiment = FieldEvaluator::extract_field_value(&json, "analysis.sentiment").unwrap();
1029        assert_eq!(*sentiment, json!("positive"));
1030
1031        // Test array of objects
1032        let first_action =
1033            FieldEvaluator::extract_field_value(&json, "recommendations[0].action").unwrap();
1034        assert_eq!(*first_action, json!("increase_investment"));
1035
1036        // Test numeric extraction
1037        let confidence = FieldEvaluator::extract_field_value(&json, "analysis.confidence").unwrap();
1038        assert_eq!(*confidence, json!(0.85));
1039        // Test array element extraction
1040        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    // Format Validation Tests
1550    #[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    // Range Tests
1665    #[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    // Collection Tests
1742    #[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    // String Tests
1838    #[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    // Tolerance Tests
1953    #[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}