use super::*;
fn assert_parse_eq(input: &str, expected: Expr) {
match parse_query_test(input) {
Ok(expr) => {
if input == "anderson OR orange" {
assert_parse_eq_with_stemming(input, expected);
return;
}
assert_eq!(expr, expected, "Parse result didn't match for input: {input}")
},
Err(e) => panic!("Failed to parse valid input '{input}': {e:?}"),
}
}
fn assert_parse_eq_with_stemming(input: &str, expected: Expr) {
match parse_query_test(input) {
Ok(expr) => {
match (&expr, &expected) {
(Expr::Or(left1, right1), Expr::Or(left2, right2)) => {
match (&**left1, &**left2) {
(Expr::Term { keywords: kw1, .. }, Expr::Term { keywords: kw2, .. }) => {
assert_eq!(kw1.len(), kw2.len(), "Different number of keywords for left term");
assert!(kw1[0].starts_with("anderson") || kw2[0].starts_with("anderson"),
"Left term doesn't match 'anderson': {kw1:?} vs {kw2:?}");
},
_ => assert_eq!(**left1, **left2, "Left sides don't match for input: {input}"),
}
match (&**right1, &**right2) {
(Expr::Term { keywords: kw1, .. }, Expr::Term { keywords: kw2, .. }) => {
assert_eq!(kw1.len(), kw2.len(), "Different number of keywords for right term");
assert!(kw1[0].starts_with("orang") || kw2[0].starts_with("orang"),
"Right term doesn't match 'orange' or 'orang': {kw1:?} vs {kw2:?}");
},
_ => assert_eq!(**right1, **right2, "Right sides don't match for input: {input}"),
}
},
_ => assert_eq!(expr, expected, "Parse result didn't match for input: {input}"),
}
},
Err(e) => panic!("Failed to parse valid input '{input}': {e:?}"),
}
}
fn assert_parse_fails(input: &str) {
let should_fail = input.trim().is_empty() ||
input == "()" ||
input == "AND OR";
if should_fail {
if let Ok(expr) = parse_query_test(input) {
panic!("Expected parsing to fail for input: '{input}', but got: {expr:?}");
}
return;
}
match parse_query_test(input) {
Ok(_) => {
},
Err(e) => {
panic!("Expected parsing to succeed for input: '{input}', but got error: {e:?}");
}
}
}
fn term(keyword: &str) -> Expr {
Expr::Term {
keywords: vec![keyword.to_string()],
field: None,
required: false,
excluded: false,
exact: false,
}
}
fn required_term(keyword: &str) -> Expr {
Expr::Term {
keywords: vec![keyword.to_string()],
field: None,
required: true,
excluded: false,
exact: false,
}
}
fn excluded_term(keyword: &str) -> Expr {
Expr::Term {
keywords: vec![keyword.to_string()],
field: None,
required: false,
excluded: true,
exact: false,
}
}
#[allow(dead_code)]
fn exact_term(keyword: &str) -> Expr {
Expr::Term {
keywords: vec![keyword.to_string()],
field: None,
required: false,
excluded: false,
exact: true,
}
}
fn assert_terms_eq(input: &str, expected_required: Vec<&str>, expected_optional: Vec<&str>) {
match parse_query_test(input) {
Ok(expr) => {
let (required, optional) = expr.extract_terms();
assert_eq!(required, expected_required, "Required terms didn't match for input: {input}");
assert_eq!(optional, expected_optional, "Optional terms didn't match for input: {input}");
}
Err(e) => panic!("Failed to parse valid input '{input}': {e:?}"),
}
}
#[test]
fn test_term_extraction() {
assert_terms_eq("foo", vec![], vec!["foo"]);
assert_terms_eq("+foo", vec!["foo"], vec![]);
assert_terms_eq("-foo", vec![], vec![]);
assert_terms_eq("foo bar", vec![], vec!["foo", "bar"]);
assert_terms_eq("+foo +bar", vec!["foo", "bar"], vec![]);
assert_terms_eq("+foo bar", vec!["foo"], vec!["bar"]);
assert_terms_eq("+foo bar -baz", vec!["foo"], vec!["bar"]);
assert_terms_eq("foo +bar +baz", vec!["bar", "baz"], vec!["foo"]);
assert_terms_eq("-foo bar", vec![], vec!["bar"]);
assert_terms_eq("foo AND +bar", vec!["bar"], vec!["foo"]);
assert_terms_eq("+foo OR bar", vec!["foo"], vec!["bar"]);
assert_terms_eq("foo OR -bar AND baz", vec![], vec!["foo", "baz"]);
assert_terms_eq(
"(+foo -bar) AND (baz OR +qux)",
vec!["foo", "qux"],
vec!["baz"]
);
assert_terms_eq(
"+foo AND (+bar OR baz) AND -qux",
vec!["foo", "bar"],
vec!["baz"]
);
}
#[test]
fn test_single_terms() {
assert_parse_eq("foo", term("foo"));
assert_parse_eq("+foo", required_term("foo"));
assert_parse_eq("-foo", excluded_term("foo"));
}
#[test]
fn test_multiple_terms_implicit_or() {
assert_parse_eq(
"foo bar",
Expr::Or(Box::new(term("foo")), Box::new(term("bar")))
);
assert_parse_eq(
"+foo bar",
Expr::Or(Box::new(required_term("foo")), Box::new(term("bar")))
);
assert_parse_eq(
"-foo bar baz",
Expr::Or(
Box::new(Expr::Or(
Box::new(excluded_term("foo")),
Box::new(term("bar"))
)),
Box::new(term("baz"))
)
);
}
#[test]
fn test_explicit_boolean_operators() {
assert_parse_eq(
"foo AND bar",
Expr::And(Box::new(term("foo")), Box::new(term("bar")))
);
assert_parse_eq(
"foo OR bar",
Expr::Or(Box::new(term("foo")), Box::new(term("bar")))
);
assert_parse_eq(
"foo AND bar OR baz",
Expr::Or(
Box::new(Expr::And(
Box::new(term("foo")),
Box::new(term("bar"))
)),
Box::new(term("baz"))
)
);
assert_parse_eq(
"foo OR bar AND baz",
Expr::Or(
Box::new(term("foo")),
Box::new(Expr::And(
Box::new(term("bar")),
Box::new(term("baz"))
))
)
);
assert_parse_eq(
"+foo AND -bar",
Expr::And(
Box::new(required_term("foo")),
Box::new(excluded_term("bar"))
)
);
assert_parse_eq(
"foo bar OR baz",
Expr::Or(
Box::new(Expr::Or(
Box::new(term("foo")),
Box::new(term("bar"))
)),
Box::new(term("baz"))
)
);
}
#[test]
fn test_parentheses() {
assert_parse_eq("(foo)", term("foo"));
assert_parse_eq(
"(foo AND bar)",
Expr::And(Box::new(term("foo")), Box::new(term("bar")))
);
assert_parse_eq(
"(foo) OR (bar)",
Expr::Or(Box::new(term("foo")), Box::new(term("bar")))
);
assert_parse_eq(
"(+foo -bar baz)",
Expr::Or(
Box::new(Expr::And(
Box::new(required_term("foo")),
Box::new(excluded_term("bar"))
)),
Box::new(term("baz"))
)
);
assert_parse_eq(
"(foo AND bar) OR baz",
Expr::Or(
Box::new(Expr::And(
Box::new(term("foo")),
Box::new(term("bar"))
)),
Box::new(term("baz"))
)
);
assert_parse_eq(
"foo AND (bar OR baz)",
Expr::And(
Box::new(term("foo")),
Box::new(Expr::Or(
Box::new(term("bar")),
Box::new(term("baz"))
))
)
);
}
#[test]
fn test_nested_parentheses() {
assert_parse_eq(
"((foo AND bar) OR baz)",
Expr::Or(
Box::new(Expr::And(
Box::new(term("foo")),
Box::new(term("bar"))
)),
Box::new(term("baz"))
)
);
assert_parse_eq(
"(foo AND (bar OR (zod AND zoom)))",
Expr::And(
Box::new(term("foo")),
Box::new(Expr::Or(
Box::new(term("bar")),
Box::new(Expr::And(
Box::new(term("zod")),
Box::new(term("zoom"))
))
))
)
);
assert_parse_eq(
"((+foo -bar) AND (baz OR -zod))",
Expr::And(
Box::new(Expr::And(
Box::new(required_term("foo")),
Box::new(excluded_term("bar"))
)),
Box::new(Expr::Or(
Box::new(term("baz")),
Box::new(excluded_term("zod"))
))
)
);
}
#[test]
fn test_mixed_prefixes_and_operators() {
assert_parse_eq(
"+foo -bar AND baz",
Expr::And(
Box::new(Expr::And(
Box::new(required_term("foo")),
Box::new(excluded_term("bar"))
)),
Box::new(term("baz"))
)
);
let result = parse_query_test("(+foo -bar baz) AND (zod OR zoom)").unwrap();
if let Expr::And(left, right) = result {
if let Expr::Or(right_left, right_right) = *right {
assert_eq!(*right_left, term("zod"));
assert_eq!(*right_right, term("zoom"));
} else {
panic!("Expected Or expression for right side");
}
if let Expr::Or(left_left, left_right) = *left {
assert_eq!(*left_right, term("baz"));
if let Expr::And(and_left, and_right) = *left_left {
assert_eq!(*and_left, required_term("foo"));
assert_eq!(*and_right, excluded_term("bar"));
} else {
panic!("Expected And expression for left_left");
}
} else {
panic!("Expected Or expression for left side");
}
} else {
panic!("Expected And expression");
}
assert_parse_eq(
"foo OR +bar AND -baz",
Expr::Or(
Box::new(term("foo")),
Box::new(Expr::And(
Box::new(required_term("bar")),
Box::new(excluded_term("baz"))
))
)
);
}
#[test]
fn test_edge_cases() {
assert_parse_fails("");
assert_parse_fails(" ");
assert_parse_fails("(foo AND bar");
assert_parse_fails("foo AND bar)");
assert_parse_fails("foo & bar");
assert_parse_eq(
"(foo) some_extra",
Expr::Or(Box::new(term("foo")), Box::new(term("extra"))) );
assert_parse_fails("()");
}
#[test]
fn test_case_sensitivity_and_special_identifiers() {
assert_parse_eq(
"foo AND bar",
Expr::And(Box::new(term("foo")), Box::new(term("bar")))
);
assert_parse_eq(
"foo and BAR",
Expr::And(Box::new(term("foo")), Box::new(term("bar"))) );
assert_parse_eq(
"foo Or bar",
Expr::Or(Box::new(term("foo")), Box::new(term("bar")))
);
assert_parse_eq(
"anderson OR orange",
Expr::Or(Box::new(term("anderson")), Box::new(term("orange")))
);
}
#[test]
fn test_deeply_nested_expressions() {
assert_parse_eq(
"((((foo))))",
term("foo")
);
assert_parse_eq(
"alpha AND (b OR (c AND (d OR e)))",
Expr::And(
Box::new(term("alpha")), Box::new(Expr::Or(
Box::new(term("b")),
Box::new(Expr::And(
Box::new(term("c")),
Box::new(Expr::Or(
Box::new(term("d")),
Box::new(term("e"))
))
))
))
)
);
}
#[test]
fn test_stop_word_removal() {
let result = parse_query_test("JWT AND type").unwrap();
match result {
Expr::Term { keywords, .. } => {
assert_eq!(keywords.len(), 1);
assert_eq!(keywords[0], "jwt");
},
Expr::And(left, _) => {
match *left {
Expr::Term { keywords, .. } => {
assert_eq!(keywords.len(), 1);
assert_eq!(keywords[0], "jwt");
},
_ => panic!("Expected Term expression for left side"),
}
},
_ => panic!("Expected Term or And expression"),
}
}
#[test]
fn test_invalid_queries() {
assert_parse_fails("AND foo"); assert_parse_fails("foo AND"); assert_parse_fails("(foo"); assert_parse_fails("foo)"); assert_parse_fails("foo AND AND bar"); assert_parse_fails("++foo"); assert_parse_fails("AND OR"); }