use core_policy::{CompareOp, ContextExpr, PolicyError, MAX_EXPR_DEPTH, MAX_EXPR_LENGTH};
use std::collections::BTreeMap;
#[test]
fn kill_depth_mutation() {
let mut expr = ContextExpr::True;
for _ in 0..=MAX_EXPR_DEPTH {
expr = ContextExpr::Not(Box::new(expr));
}
let ctx = BTreeMap::new();
let result = expr.evaluate(&ctx, 0);
assert!(
matches!(result, Err(PolicyError::ExpressionTooDeep { .. })),
"Expected ExpressionTooDeep error for depth {}, got {:?}",
MAX_EXPR_DEPTH + 1,
result
);
}
#[test]
fn kill_depth_increment_and_surgical() {
fn build_nested_and(levels: usize) -> ContextExpr {
let mut expr = ContextExpr::True;
for _ in 0..levels {
expr = ContextExpr::And(Box::new(ContextExpr::True), Box::new(expr));
}
expr
}
let ctx = BTreeMap::new();
let expr_ok = build_nested_and(MAX_EXPR_DEPTH);
let result = expr_ok.evaluate(&ctx, 0);
assert!(
result.is_ok(),
"AND nested {} levels should succeed (depth 0..{})",
MAX_EXPR_DEPTH,
MAX_EXPR_DEPTH
);
let expr_fail = build_nested_and(MAX_EXPR_DEPTH + 1);
let result = expr_fail.evaluate(&ctx, 0);
assert!(
result.is_err(),
"AND nested {} levels should fail (depth would exceed {})",
MAX_EXPR_DEPTH + 1,
MAX_EXPR_DEPTH
);
}
#[test]
fn kill_depth_increment_or_surgical() {
fn build_nested_or(levels: usize) -> ContextExpr {
let mut expr = ContextExpr::False;
for _ in 0..levels {
expr = ContextExpr::Or(Box::new(ContextExpr::False), Box::new(expr));
}
expr
}
let ctx = BTreeMap::new();
let expr_ok = build_nested_or(MAX_EXPR_DEPTH);
let result = expr_ok.evaluate(&ctx, 0);
assert!(
result.is_ok(),
"OR nested {} levels should succeed",
MAX_EXPR_DEPTH
);
let expr_fail = build_nested_or(MAX_EXPR_DEPTH + 1);
let result = expr_fail.evaluate(&ctx, 0);
assert!(
result.is_err(),
"OR nested {} levels should fail",
MAX_EXPR_DEPTH + 1
);
}
#[test]
fn kill_depth_increment_left_branch() {
fn build_left_nested_and(levels: usize) -> ContextExpr {
let mut expr = ContextExpr::True;
for _ in 0..levels {
expr = ContextExpr::And(
Box::new(expr), Box::new(ContextExpr::True), );
}
expr
}
let ctx = BTreeMap::new();
let expr_ok = build_left_nested_and(MAX_EXPR_DEPTH);
assert!(expr_ok.evaluate(&ctx, 0).is_ok());
let expr_fail = build_left_nested_and(MAX_EXPR_DEPTH + 1);
assert!(expr_fail.evaluate(&ctx, 0).is_err());
}
#[test]
fn kill_length_boundary_exact() {
let padding = MAX_EXPR_LENGTH - 4; let input = format!("{:>width$}", "TRUE", width = MAX_EXPR_LENGTH);
assert_eq!(input.len(), MAX_EXPR_LENGTH);
let result = ContextExpr::parse(&input);
assert!(result.is_ok(), "Exact MAX_EXPR_LENGTH should be accepted");
}
#[test]
fn kill_length_boundary_one_over() {
let input = " ".repeat(MAX_EXPR_LENGTH + 1);
let result = ContextExpr::parse(&input);
assert!(result.is_err(), "One over MAX_EXPR_LENGTH must be rejected");
}
#[test]
fn kill_less_than_strict() {
let mut ctx = BTreeMap::new();
ctx.insert("val".to_string(), "b".to_string());
let expr = ContextExpr::Compare {
key: "val".to_string(),
op: CompareOp::LessThan,
value: "b".to_string(),
};
assert_eq!(
expr.evaluate(&ctx, 0).unwrap(),
false,
"b < b must be false"
);
ctx.insert("val".to_string(), "a".to_string());
assert_eq!(expr.evaluate(&ctx, 0).unwrap(), true, "a < b must be true");
}
#[test]
fn kill_greater_than_strict() {
let mut ctx = BTreeMap::new();
ctx.insert("val".to_string(), "b".to_string());
let expr = ContextExpr::Compare {
key: "val".to_string(),
op: CompareOp::GreaterThan,
value: "b".to_string(),
};
assert_eq!(
expr.evaluate(&ctx, 0).unwrap(),
false,
"b > b must be false"
);
ctx.insert("val".to_string(), "c".to_string());
assert_eq!(expr.evaluate(&ctx, 0).unwrap(), true, "c > b must be true");
}
#[test]
fn kill_not_equal_operator() {
let input = r#"role != "admin""#;
let result = ContextExpr::parse(input);
assert!(result.is_ok(), "!= must be tokenized correctly");
let expr = result.unwrap();
let mut ctx = BTreeMap::new();
ctx.insert("role".to_string(), "user".to_string());
assert_eq!(expr.evaluate(&ctx, 0).unwrap(), true);
ctx.insert("role".to_string(), "admin".to_string());
assert_eq!(expr.evaluate(&ctx, 0).unwrap(), false);
}
#[test]
fn kill_less_than_operator_tokenize() {
let input = r#"age < "30""#;
let result = ContextExpr::parse(input);
assert!(result.is_ok(), "< must be tokenized correctly");
let expr = result.unwrap();
let mut ctx = BTreeMap::new();
ctx.insert("age".to_string(), "25".to_string());
assert_eq!(expr.evaluate(&ctx, 0).unwrap(), true);
}
#[test]
fn kill_less_than_or_equal_tokenize() {
let input = r#"age <= "30""#;
let result = ContextExpr::parse(input);
assert!(result.is_ok(), "<= must be tokenized correctly");
let expr = result.unwrap();
let mut ctx = BTreeMap::new();
ctx.insert("age".to_string(), "30".to_string());
assert_eq!(
expr.evaluate(&ctx, 0).unwrap(),
true,
"30 <= 30 must be true"
);
}
#[test]
fn kill_greater_than_or_equal_tokenize() {
let input = r#"age >= "30""#;
let result = ContextExpr::parse(input);
assert!(result.is_ok(), ">= must be tokenized correctly");
let expr = result.unwrap();
let mut ctx = BTreeMap::new();
ctx.insert("age".to_string(), "30".to_string());
assert_eq!(
expr.evaluate(&ctx, 0).unwrap(),
true,
"30 >= 30 must be true"
);
}
#[test]
fn kill_escape_in_string() {
let input = r#"msg == "he said \"hello\"""#;
let result = ContextExpr::parse(input);
assert!(result.is_ok(), "Escaped quotes must work");
let expr = result.unwrap();
let mut ctx = BTreeMap::new();
ctx.insert("msg".to_string(), r#"he said "hello""#.to_string());
assert_eq!(expr.evaluate(&ctx, 0).unwrap(), true);
}
#[test]
fn kill_escape_backslash() {
let input = r#"path == "c:\\folder""#;
let result = ContextExpr::parse(input);
assert!(result.is_ok(), "Escaped backslash must work");
let expr = result.unwrap();
let mut ctx = BTreeMap::new();
ctx.insert("path".to_string(), r#"c:\folder"#.to_string());
assert_eq!(expr.evaluate(&ctx, 0).unwrap(), true);
}
#[test]
fn kill_expect_wrong_token() {
let input = "(TRUE";
let result = ContextExpr::parse(input);
assert!(result.is_err(), "Missing closing paren must fail");
let input = "((TRUE)";
let result = ContextExpr::parse(input);
assert!(result.is_err(), "Unbalanced nested parens must fail");
}
#[test]
fn kill_expect_token_mismatch_surgical() {
let failing_cases = [
"(TRUE",
"((TRUE)",
"(TRUE AND FALSE",
"(TRUE OR (FALSE)",
"((((TRUE))))",
];
for case in ["(TRUE", "((TRUE)", "(TRUE AND FALSE", "(TRUE OR (FALSE)"] {
let result = ContextExpr::parse(case);
assert!(result.is_err(), "'{}' must fail - unbalanced parens", case);
}
let balanced = "(TRUE)";
let result = ContextExpr::parse(balanced);
assert!(result.is_ok(), "'{}' must succeed", balanced);
let expr = result.unwrap();
let ctx = BTreeMap::new();
assert!(
expr.evaluate(&ctx, 0).unwrap(),
"(TRUE) must evaluate to true"
);
let balanced = "((TRUE AND FALSE))";
let result = ContextExpr::parse(balanced);
assert!(result.is_ok(), "'{}' must succeed", balanced);
let expr = result.unwrap();
assert!(
!expr.evaluate(&ctx, 0).unwrap(),
"((TRUE AND FALSE)) must be false"
);
}
#[test]
fn kill_all_comparison_operators() {
let ops = [
("==", CompareOp::Equal),
("!=", CompareOp::NotEqual),
("<", CompareOp::LessThan),
("<=", CompareOp::LessThanOrEqual),
(">", CompareOp::GreaterThan),
(">=", CompareOp::GreaterThanOrEqual),
];
for (op_str, _expected_op) in ops {
let input = format!(r#"key {} "value""#, op_str);
let result = ContextExpr::parse(&input);
assert!(result.is_ok(), "Operator {} must parse correctly", op_str);
}
}
#[test]
fn kill_identifier_as_value() {
let input = "role == admin"; let result = ContextExpr::parse(input);
assert!(result.is_ok(), "Unquoted value must work");
let expr = result.unwrap();
let mut ctx = BTreeMap::new();
ctx.insert("role".to_string(), "admin".to_string());
assert_eq!(expr.evaluate(&ctx, 0).unwrap(), true);
}
#[test]
fn kill_compare_op_display() {
assert_eq!(format!("{}", CompareOp::Equal), "==");
assert_eq!(format!("{}", CompareOp::NotEqual), "!=");
assert_eq!(format!("{}", CompareOp::LessThan), "<");
assert_eq!(format!("{}", CompareOp::LessThanOrEqual), "<=");
assert_eq!(format!("{}", CompareOp::GreaterThan), ">");
assert_eq!(format!("{}", CompareOp::GreaterThanOrEqual), ">=");
}