#[cfg(test)]
mod tests {
use crate::filter::dsl::ast::{Cmp, Expr, Value};
use crate::filter::dsl::parser::parse;
#[test]
fn parses_single_comparison() {
let ast = parse("status >= 400").unwrap();
match ast {
Expr::Compare { field, op, value } => {
assert_eq!(field, "status");
assert_eq!(op, Cmp::Ge);
assert_eq!(value, Value::Int(400));
}
other => panic!("unexpected AST {:?}", other),
}
}
#[test]
fn and_binds_tighter_than_or() {
let ast = parse("a = 1 or b = 2 and c = 3").unwrap();
match ast {
Expr::Or(lhs, rhs) => {
assert!(matches!(*lhs, Expr::Compare { .. }));
assert!(matches!(*rhs, Expr::And(_, _)));
}
other => panic!("expected Or at top level, got {:?}", other),
}
}
#[test]
fn parentheses_flip_precedence() {
let ast = parse("(a = 1 or b = 2) and c = 3").unwrap();
assert!(matches!(ast, Expr::And(_, _)));
}
#[test]
fn not_consumes_single_primary() {
let ast = parse("not status = 200").unwrap();
match ast {
Expr::Not(inner) => assert!(matches!(*inner, Expr::Compare { .. })),
other => panic!("expected Not, got {:?}", other),
}
}
#[test]
fn regex_literal_becomes_regex_value() {
let ast = parse("body ~ /oops/").unwrap();
match ast {
Expr::Compare { op, value, .. } => {
assert_eq!(op, Cmp::Match);
assert_eq!(value, Value::Regex("oops".into()));
}
_ => panic!("expected Compare"),
}
}
#[test]
fn trailing_garbage_is_reported() {
let err = parse("status = 200 extra").unwrap_err();
assert!(err.to_string().contains("trailing"));
}
#[test]
fn missing_value_is_reported() {
assert!(parse("status =").is_err());
}
#[test]
fn unbalanced_paren_is_reported() {
assert!(parse("(status = 1").is_err());
}
#[test]
fn empty_input_is_rejected_upstream() {
assert!(parse(" ").is_err());
}
}