src/filter/dsl/eval/eval_test.rs

#[cfg(test)]
mod tests {
    use crate::filter::dsl::{parse_and_compile, EvalContext, evaluate};

    fn ctx(pairs: &[(&str, &str)]) -> EvalContext {
        let mut ctx = EvalContext::new();
        for (k, v) in pairs {
            ctx.set(k, *v);
        }
        ctx
    }

    #[test]
    fn integer_comparison_passes() {
        let filter = parse_and_compile("status >= 400").unwrap();
        assert!(evaluate(&filter, &ctx(&[("status", "503")])));
        assert!(!evaluate(&filter, &ctx(&[("status", "200")])));
    }

    #[test]
    fn integer_comparison_missing_field_is_false() {
        let filter = parse_and_compile("status >= 400").unwrap();
        assert!(!evaluate(&filter, &ctx(&[])));
    }

    #[test]
    fn and_short_circuits() {
        let filter = parse_and_compile("status = 200 and host = \"api\"").unwrap();
        assert!(!evaluate(&filter, &ctx(&[("status", "500"), ("host", "api")])));
        assert!(evaluate(&filter, &ctx(&[("status", "200"), ("host", "api")])));
    }

    #[test]
    fn or_matches_either_side() {
        let filter = parse_and_compile("status = 500 or status = 503").unwrap();
        assert!(evaluate(&filter, &ctx(&[("status", "503")])));
        assert!(!evaluate(&filter, &ctx(&[("status", "200")])));
    }

    #[test]
    fn not_inverts_result() {
        let filter = parse_and_compile("not status = 200").unwrap();
        assert!(!evaluate(&filter, &ctx(&[("status", "200")])));
        assert!(evaluate(&filter, &ctx(&[("status", "301")])));
    }

    #[test]
    fn regex_operator_matches_substring() {
        let filter = parse_and_compile("body ~ /failed to/").unwrap();
        assert!(evaluate(
            &filter,
            &ctx(&[("body", "request failed to authenticate")]),
        ));
        assert!(!evaluate(&filter, &ctx(&[("body", "ok")])));
    }

    #[test]
    fn string_compare_is_lexical() {
        let filter = parse_and_compile("host = \"api.example.com\"").unwrap();
        assert!(evaluate(&filter, &ctx(&[("host", "api.example.com")])));
        assert!(!evaluate(&filter, &ctx(&[("host", "api.example.net")])));
    }

    #[test]
    fn mixed_boolean_precedence() {
        let filter = parse_and_compile(
            "status >= 400 and (host = \"api\" or host = \"web\")",
        )
        .unwrap();
        let hits = ctx(&[("status", "500"), ("host", "web")]);
        let misses = ctx(&[("status", "500"), ("host", "db")]);
        assert!(evaluate(&filter, &hits));
        assert!(!evaluate(&filter, &misses));
    }
}