#![allow(clippy::unwrap_used)]
use iqdb_filter::{FilterEvaluator, MAX_FILTER_DEPTH, MAX_IN_VALUES};
use iqdb_types::{Filter, IqdbError, Metadata, Value};
fn meta_with(pairs: &[(&str, Value)]) -> Metadata {
pairs
.iter()
.map(|(k, v)| ((*k).to_string(), v.clone()))
.collect()
}
fn nested_not(depth: usize) -> Filter {
let mut current = Filter::eq("k", Value::Int(0));
for _ in 0..depth {
current = Filter::not(current);
}
current
}
#[test]
fn neq_absent_is_false() {
let evaluator =
FilterEvaluator::new(Filter::neq("author", Value::String("ada".into()))).unwrap();
let empty_meta = meta_with(&[]);
assert!(!evaluator.evaluate(Some(&empty_meta)));
assert!(!evaluator.evaluate(None));
}
#[test]
fn not_eq_absent_is_true() {
let evaluator = FilterEvaluator::new(Filter::not(Filter::eq(
"author",
Value::String("ada".into()),
)))
.unwrap();
let empty_meta = meta_with(&[]);
assert!(evaluator.evaluate(Some(&empty_meta)));
assert!(evaluator.evaluate(None));
}
#[test]
fn neq_present_field_matches() {
let evaluator =
FilterEvaluator::new(Filter::neq("author", Value::String("ada".into()))).unwrap();
let meta = meta_with(&[("author", Value::String("grace".into()))]);
assert!(evaluator.evaluate(Some(&meta)));
}
#[test]
fn type_mismatch_is_false() {
let evaluator = FilterEvaluator::new(Filter::eq("year", Value::String("2026".into()))).unwrap();
let meta = meta_with(&[("year", Value::Int(2026))]);
assert!(!evaluator.evaluate(Some(&meta)));
}
#[test]
fn depth_at_cap_accepted() {
let filter = nested_not(MAX_FILTER_DEPTH);
assert!(FilterEvaluator::new(filter).is_ok());
}
#[test]
fn depth_over_cap_rejected() {
let filter = nested_not(MAX_FILTER_DEPTH + 1);
let err = FilterEvaluator::new(filter).unwrap_err();
assert_eq!(err, IqdbError::InvalidFilter);
}
#[test]
fn validation_does_not_overflow_on_pathological_input() {
let filter = nested_not(MAX_FILTER_DEPTH * 4);
let err = FilterEvaluator::new(filter).unwrap_err();
assert_eq!(err, IqdbError::InvalidFilter);
}
#[test]
fn wide_and_chain_at_cap_accepted() {
let mut current = Filter::eq("k", Value::Int(0));
for _ in 0..MAX_FILTER_DEPTH {
current = Filter::and(vec![current]);
}
assert!(FilterEvaluator::new(current).is_ok());
}
#[test]
fn in_at_cap_accepted() {
let filter = Filter::is_in("tag", vec![Value::Int(0); MAX_IN_VALUES]);
assert!(FilterEvaluator::new(filter).is_ok());
}
#[test]
fn in_over_cap_rejected() {
let filter = Filter::is_in("tag", vec![Value::Int(0); MAX_IN_VALUES + 1]);
let err = FilterEvaluator::new(filter).unwrap_err();
assert_eq!(err, IqdbError::InvalidFilter);
}
#[test]
fn in_inside_and_still_rejected() {
let filter = Filter::and(vec![
Filter::eq("k", Value::Int(1)),
Filter::is_in("tag", vec![Value::Int(0); MAX_IN_VALUES + 1]),
]);
let err = FilterEvaluator::new(filter).unwrap_err();
assert_eq!(err, IqdbError::InvalidFilter);
}
#[test]
fn evaluate_round_trips_a_real_query() {
let filter = Filter::and(vec![
Filter::eq("published", Value::Bool(true)),
Filter::or(vec![
Filter::gt("year", Value::Int(2000)),
Filter::eq("year", Value::Int(2000)),
]),
]);
let evaluator = FilterEvaluator::new(filter).unwrap();
let matching = meta_with(&[("published", Value::Bool(true)), ("year", Value::Int(2026))]);
let mismatch = meta_with(&[("published", Value::Bool(true)), ("year", Value::Int(1999))]);
assert!(evaluator.evaluate(Some(&matching)));
assert!(!evaluator.evaluate(Some(&mismatch)));
}