//! Integration-level tests for the filter DSL: parse, compile, evaluate.
use ripgrab::filter::dsl::{evaluate, parse_and_compile, EvalContext};
fn ctx(pairs: &[(&str, &str)]) -> EvalContext {
let mut c = EvalContext::new();
for (k, v) in pairs {
c.set(k, *v);
}
c
}
#[test]
fn full_pipeline_simple() {
let f = parse_and_compile("status >= 400 and host = \"api\"").unwrap();
assert!(evaluate(&f, &ctx(&[("status", "503"), ("host", "api")])));
assert!(!evaluate(&f, &ctx(&[("status", "200"), ("host", "api")])));
assert!(!evaluate(&f, &ctx(&[("status", "503"), ("host", "web")])));
}
#[test]
fn regex_escape_handling() {
let f = parse_and_compile(r#"body ~ /failed\s+to/"#).unwrap();
assert!(evaluate(&f, &ctx(&[("body", "failed to connect")])));
assert!(!evaluate(&f, &ctx(&[("body", "failedto connect")])));
}
#[test]
fn unterminated_regex_is_syntax_error() {
let err = parse_and_compile(r#"body ~ /abc"#).unwrap_err();
assert!(err.to_string().contains("unterminated"));
}
#[test]
fn precedence_with_or_and_not() {
let f = parse_and_compile(
"not status = 200 or (host = \"api\" and status >= 500)",
)
.unwrap();
assert!(evaluate(&f, &ctx(&[("status", "404"), ("host", "web")])));
assert!(evaluate(&f, &ctx(&[("status", "503"), ("host", "api")])));
assert!(!evaluate(&f, &ctx(&[("status", "200"), ("host", "web")])));
}
#[test]
fn integer_coercion_failures_are_falsy() {
let f = parse_and_compile("status >= 400").unwrap();
assert!(!evaluate(&f, &ctx(&[("status", "not-a-number")])));
}
#[test]
fn regex_on_integer_is_type_mismatch() {
let err = parse_and_compile("status ~ 1").unwrap_err();
assert!(err.to_string().contains("integer"));
}
#[test]
fn empty_context_only_satisfies_negatives() {
let f = parse_and_compile("not status = 200").unwrap();
assert!(evaluate(&f, &ctx(&[])));
}
#[test]
fn multiple_fields_all_required() {
let f = parse_and_compile(
"host = \"api\" and route ~ /orders/ and status = 200",
)
.unwrap();
assert!(evaluate(
&f,
&ctx(&[("host", "api"), ("route", "/orders/123"), ("status", "200")]),
));
}