use core_policy::{CompareOp, ContextExpr, MAX_EXPR_DEPTH, MAX_EXPR_LENGTH};
use proptest::prelude::*;
use std::collections::BTreeMap;
proptest! {
#![proptest_config(ProptestConfig::with_cases(1000))]
#[test]
fn prop_max_length_enforced(extra_len in 1usize..1000) {
let input = "a".repeat(MAX_EXPR_LENGTH + extra_len);
let result = ContextExpr::parse(&input);
prop_assert!(
result.is_err(),
"Input of length {} should be rejected (max: {})",
input.len(),
MAX_EXPR_LENGTH
);
}
#[test]
fn prop_within_length_allowed(len in 1usize..=MAX_EXPR_LENGTH) {
let input = "a".repeat(len);
let result = ContextExpr::parse(&input);
let _ = result;
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(5000))]
#[test]
fn prop_parse_never_panics(input in ".*") {
let _ = ContextExpr::parse(&input);
}
#[test]
fn prop_parse_special_chars_safe(
prefix in "[()\"\\\\AND OR NOT HAS TRUE FALSE == != < <= > >=]*",
middle in "\\PC*", suffix in "[()\"\\\\]*"
) {
let input = format!("{}{}{}", prefix, middle, suffix);
if input.len() <= MAX_EXPR_LENGTH {
let _ = ContextExpr::parse(&input);
}
}
#[test]
fn prop_parse_random_nesting_safe(
open_count in 0usize..200,
close_count in 0usize..200,
inner in "(TRUE|FALSE|role == \"admin\")"
) {
let input = format!(
"{}{}{}",
"(".repeat(open_count),
inner,
")".repeat(close_count)
);
if input.len() <= MAX_EXPR_LENGTH {
let _ = ContextExpr::parse(&input);
}
}
}
fn arbitrary_context() -> impl Strategy<Value = BTreeMap<String, String>> {
prop::collection::btree_map(
"[a-z]{1,10}", "[a-zA-Z0-9]{1,20}", 0..10, )
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(1000))]
#[test]
fn prop_evaluation_deterministic(
expr_str in "(TRUE|FALSE|role == \"admin\"|dept == \"IT\"|HAS role)",
ctx in arbitrary_context()
) {
if let Ok(expr) = ContextExpr::parse(&expr_str) {
let r1 = expr.evaluate(&ctx, 0);
let r2 = expr.evaluate(&ctx, 0);
let r3 = expr.evaluate(&ctx, 0);
prop_assert!(r1.is_ok() == r2.is_ok(), "Determinism: same ok status");
prop_assert!(r2.is_ok() == r3.is_ok(), "Determinism: same ok status");
if let (Ok(v1), Ok(v2), Ok(v3)) = (r1, r2, r3) {
prop_assert_eq!(v1, v2, "Evaluation must be deterministic");
prop_assert_eq!(v2, v3, "Evaluation must be deterministic");
}
}
}
#[test]
fn prop_depth_zero_consistent(
expr_str in "(TRUE|FALSE|role == \"admin\")",
ctx in arbitrary_context()
) {
if let Ok(expr) = ContextExpr::parse(&expr_str) {
let at_zero = expr.evaluate(&ctx, 0);
let at_one = expr.evaluate(&ctx, 1);
if at_zero.is_ok() && at_one.is_ok() {
prop_assert_eq!(
at_zero.unwrap(),
at_one.unwrap(),
"Result should be same regardless of starting depth for simple exprs"
);
}
}
}
}
fn make_nested_expr(depth: usize) -> ContextExpr {
if depth == 0 {
ContextExpr::True
} else {
ContextExpr::Not(Box::new(make_nested_expr(depth - 1)))
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn prop_max_depth_enforced(extra_depth in 1usize..50) {
let expr = make_nested_expr(MAX_EXPR_DEPTH + extra_depth);
let ctx = BTreeMap::new();
let result = expr.evaluate(&ctx, 0);
prop_assert!(
result.is_err(),
"Expression with depth {} should fail (max: {})",
MAX_EXPR_DEPTH + extra_depth,
MAX_EXPR_DEPTH
);
}
#[test]
fn prop_within_depth_allowed(depth in 1usize..=MAX_EXPR_DEPTH) {
let expr = make_nested_expr(depth);
let ctx = BTreeMap::new();
let result = expr.evaluate(&ctx, 0);
prop_assert!(
result.is_ok(),
"Expression with depth {} should succeed (max: {})",
depth,
MAX_EXPR_DEPTH
);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(1000))]
#[test]
fn prop_true_is_true(ctx in arbitrary_context()) {
let expr = ContextExpr::True;
let result = expr.evaluate(&ctx, 0);
prop_assert!(result.is_ok());
prop_assert_eq!(result.unwrap(), true);
}
#[test]
fn prop_false_is_false(ctx in arbitrary_context()) {
let expr = ContextExpr::False;
let result = expr.evaluate(&ctx, 0);
prop_assert!(result.is_ok());
prop_assert_eq!(result.unwrap(), false);
}
#[test]
fn prop_not_true_is_false(ctx in arbitrary_context()) {
let expr = ContextExpr::Not(Box::new(ContextExpr::True));
let result = expr.evaluate(&ctx, 0);
prop_assert!(result.is_ok());
prop_assert_eq!(result.unwrap(), false);
}
#[test]
fn prop_not_false_is_true(ctx in arbitrary_context()) {
let expr = ContextExpr::Not(Box::new(ContextExpr::False));
let result = expr.evaluate(&ctx, 0);
prop_assert!(result.is_ok());
prop_assert_eq!(result.unwrap(), true);
}
#[test]
fn prop_double_negation(b in any::<bool>(), ctx in arbitrary_context()) {
let inner = if b { ContextExpr::True } else { ContextExpr::False };
let expr = ContextExpr::Not(Box::new(ContextExpr::Not(Box::new(inner.clone()))));
let original = inner.evaluate(&ctx, 0).unwrap();
let double_neg = expr.evaluate(&ctx, 0).unwrap();
prop_assert_eq!(original, double_neg, "NOT NOT x must equal x");
}
#[test]
fn prop_and_tt(ctx in arbitrary_context()) {
let expr = ContextExpr::And(
Box::new(ContextExpr::True),
Box::new(ContextExpr::True)
);
prop_assert_eq!(expr.evaluate(&ctx, 0).unwrap(), true);
}
#[test]
fn prop_and_tf(ctx in arbitrary_context()) {
let expr = ContextExpr::And(
Box::new(ContextExpr::True),
Box::new(ContextExpr::False)
);
prop_assert_eq!(expr.evaluate(&ctx, 0).unwrap(), false);
}
#[test]
fn prop_and_ft(ctx in arbitrary_context()) {
let expr = ContextExpr::And(
Box::new(ContextExpr::False),
Box::new(ContextExpr::True)
);
prop_assert_eq!(expr.evaluate(&ctx, 0).unwrap(), false);
}
#[test]
fn prop_and_ff(ctx in arbitrary_context()) {
let expr = ContextExpr::And(
Box::new(ContextExpr::False),
Box::new(ContextExpr::False)
);
prop_assert_eq!(expr.evaluate(&ctx, 0).unwrap(), false);
}
#[test]
fn prop_or_tt(ctx in arbitrary_context()) {
let expr = ContextExpr::Or(
Box::new(ContextExpr::True),
Box::new(ContextExpr::True)
);
prop_assert_eq!(expr.evaluate(&ctx, 0).unwrap(), true);
}
#[test]
fn prop_or_tf(ctx in arbitrary_context()) {
let expr = ContextExpr::Or(
Box::new(ContextExpr::True),
Box::new(ContextExpr::False)
);
prop_assert_eq!(expr.evaluate(&ctx, 0).unwrap(), true);
}
#[test]
fn prop_or_ft(ctx in arbitrary_context()) {
let expr = ContextExpr::Or(
Box::new(ContextExpr::False),
Box::new(ContextExpr::True)
);
prop_assert_eq!(expr.evaluate(&ctx, 0).unwrap(), true);
}
#[test]
fn prop_or_ff(ctx in arbitrary_context()) {
let expr = ContextExpr::Or(
Box::new(ContextExpr::False),
Box::new(ContextExpr::False)
);
prop_assert_eq!(expr.evaluate(&ctx, 0).unwrap(), false);
}
#[test]
fn prop_de_morgan_and(a in any::<bool>(), b in any::<bool>(), ctx in arbitrary_context()) {
let a_expr = if a { ContextExpr::True } else { ContextExpr::False };
let b_expr = if b { ContextExpr::True } else { ContextExpr::False };
let left = ContextExpr::Not(Box::new(ContextExpr::And(
Box::new(a_expr.clone()),
Box::new(b_expr.clone())
)));
let right = ContextExpr::Or(
Box::new(ContextExpr::Not(Box::new(a_expr))),
Box::new(ContextExpr::Not(Box::new(b_expr)))
);
prop_assert_eq!(
left.evaluate(&ctx, 0).unwrap(),
right.evaluate(&ctx, 0).unwrap(),
"De Morgan's Law must hold"
);
}
#[test]
fn prop_de_morgan_or(a in any::<bool>(), b in any::<bool>(), ctx in arbitrary_context()) {
let a_expr = if a { ContextExpr::True } else { ContextExpr::False };
let b_expr = if b { ContextExpr::True } else { ContextExpr::False };
let left = ContextExpr::Not(Box::new(ContextExpr::Or(
Box::new(a_expr.clone()),
Box::new(b_expr.clone())
)));
let right = ContextExpr::And(
Box::new(ContextExpr::Not(Box::new(a_expr))),
Box::new(ContextExpr::Not(Box::new(b_expr)))
);
prop_assert_eq!(
left.evaluate(&ctx, 0).unwrap(),
right.evaluate(&ctx, 0).unwrap(),
"De Morgan's Law must hold"
);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(500))]
#[test]
fn prop_compare_equal_match(value in "[a-z]{1,10}") {
let mut ctx = BTreeMap::new();
ctx.insert("key".to_string(), value.clone());
let expr = ContextExpr::Compare {
key: "key".to_string(),
op: CompareOp::Equal,
value: value.clone(),
};
prop_assert_eq!(expr.evaluate(&ctx, 0).unwrap(), true);
}
#[test]
fn prop_compare_equal_mismatch(
stored in "[a-z]{1,10}",
compared in "[A-Z]{1,10}" ) {
let mut ctx = BTreeMap::new();
ctx.insert("key".to_string(), stored);
let expr = ContextExpr::Compare {
key: "key".to_string(),
op: CompareOp::Equal,
value: compared,
};
prop_assert_eq!(expr.evaluate(&ctx, 0).unwrap(), false);
}
#[test]
fn prop_not_equal_inverse(
stored in "[a-z]{1,5}",
compared in "[a-z]{1,5}"
) {
let mut ctx = BTreeMap::new();
ctx.insert("key".to_string(), stored.clone());
let eq_expr = ContextExpr::Compare {
key: "key".to_string(),
op: CompareOp::Equal,
value: compared.clone(),
};
let neq_expr = ContextExpr::Compare {
key: "key".to_string(),
op: CompareOp::NotEqual,
value: compared,
};
let eq_result = eq_expr.evaluate(&ctx, 0).unwrap();
let neq_result = neq_expr.evaluate(&ctx, 0).unwrap();
prop_assert_eq!(eq_result, !neq_result, "!= must be inverse of ==");
}
#[test]
fn prop_missing_key_false(value in "[a-z]{1,10}") {
let ctx = BTreeMap::new();
let expr = ContextExpr::Compare {
key: "nonexistent".to_string(),
op: CompareOp::Equal,
value,
};
prop_assert_eq!(expr.evaluate(&ctx, 0).unwrap(), false);
}
#[test]
fn prop_has_attribute_correct(
key in "[a-z]{1,5}",
value in "[a-z]{1,10}",
check_existing in any::<bool>()
) {
let mut ctx = BTreeMap::new();
ctx.insert(key.clone(), value);
let check_key = if check_existing { key } else { "other".to_string() };
let expr = ContextExpr::HasAttribute(check_key.clone());
let expected = ctx.contains_key(&check_key);
prop_assert_eq!(expr.evaluate(&ctx, 0).unwrap(), expected);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(500))]
#[test]
fn prop_less_than_correct(
stored in "[a-m]{1,5}", compared in "[n-z]{1,5}" ) {
let mut ctx = BTreeMap::new();
ctx.insert("key".to_string(), stored.clone());
let expr = ContextExpr::Compare {
key: "key".to_string(),
op: CompareOp::LessThan,
value: compared.clone(),
};
let result = expr.evaluate(&ctx, 0).unwrap();
let expected = stored < compared;
prop_assert_eq!(result, expected);
}
#[test]
fn prop_greater_than_correct(
stored in "[n-z]{1,5}", compared in "[a-m]{1,5}" ) {
let mut ctx = BTreeMap::new();
ctx.insert("key".to_string(), stored.clone());
let expr = ContextExpr::Compare {
key: "key".to_string(),
op: CompareOp::GreaterThan,
value: compared.clone(),
};
let result = expr.evaluate(&ctx, 0).unwrap();
let expected = stored > compared;
prop_assert_eq!(result, expected);
}
#[test]
fn prop_lte_includes_equal(value in "[a-z]{1,5}") {
let mut ctx = BTreeMap::new();
ctx.insert("key".to_string(), value.clone());
let expr = ContextExpr::Compare {
key: "key".to_string(),
op: CompareOp::LessThanOrEqual,
value: value.clone(),
};
prop_assert_eq!(expr.evaluate(&ctx, 0).unwrap(), true);
}
#[test]
fn prop_gte_includes_equal(value in "[a-z]{1,5}") {
let mut ctx = BTreeMap::new();
ctx.insert("key".to_string(), value.clone());
let expr = ContextExpr::Compare {
key: "key".to_string(),
op: CompareOp::GreaterThanOrEqual,
value: value.clone(),
};
prop_assert_eq!(expr.evaluate(&ctx, 0).unwrap(), true);
}
}