ldap-acis 0.2.1

LDAP Access Control Instructions (ACI) system built on acls-rs
Documentation
//! Property-based tests verifying algebraic laws for all ordering implementations.

use ldap_acis::*;
use proptest::prelude::*;

use acls_rs::algebra::{BoundedJoinSemilattice, BoundedMeetSemilattice};
use acls_rs::prelude::*;

// ────────────────────────────── Generators ──────────────────────────────

fn arb_scope() -> impl Strategy<Value = Scope> {
    prop_oneof![
        Just(Scope::Base),
        Just(Scope::OneLevel),
        Just(Scope::Subtree),
    ]
}

fn arb_operation_type() -> impl Strategy<Value = OperationType> {
    prop_oneof![
        Just(OperationType::Bind),
        Just(OperationType::Search),
        Just(OperationType::Compare),
        Just(OperationType::Read),
        Just(OperationType::Add),
        Just(OperationType::Delete),
        Just(OperationType::Modify),
        Just(OperationType::ModifyDn),
        Just(OperationType::SelfWrite),
        Just(OperationType::All),
    ]
}

fn arb_operation_set() -> impl Strategy<Value = OperationSet> {
    prop::collection::vec(arb_operation_type(), 0..6)
        .prop_map(|ops| ops.into_iter().collect::<OperationSet>())
}

fn arb_attr_name() -> impl Strategy<Value = String> {
    prop_oneof![
        Just("cn".to_string()),
        Just("sn".to_string()),
        Just("uid".to_string()),
        Just("mail".to_string()),
        Just("givenname".to_string()),
        Just("memberof".to_string()),
    ]
}

fn arb_attribute_set() -> impl Strategy<Value = AttributeSet> {
    prop_oneof![
        Just(AttributeSet::All),
        prop::collection::btree_set(arb_attr_name(), 0..4).prop_map(AttributeSet::Include),
        prop::collection::btree_set(arb_attr_name(), 1..4).prop_map(AttributeSet::Exclude),
    ]
}

fn arb_target_filter_leaf() -> impl Strategy<Value = TargetFilter> {
    prop_oneof![
        Just(TargetFilter::All),
        arb_attr_name().prop_map(TargetFilter::ObjectClass),
        arb_attr_name().prop_map(TargetFilter::HasAttribute),
        Just(TargetFilter::DnPattern(
            "uid=*,dc=example,dc=com".to_string()
        )),
    ]
}

fn arb_target_filter() -> impl Strategy<Value = TargetFilter> {
    arb_target_filter_leaf().prop_recursive(2, 8, 4, |inner| {
        prop_oneof![
            prop::collection::vec(inner.clone(), 1..4).prop_map(TargetFilter::And),
            prop::collection::vec(inner.clone(), 1..4).prop_map(TargetFilter::Or),
            inner.prop_map(|f| TargetFilter::Not(Box::new(f))),
        ]
    })
}

fn arb_bind_rule_leaf() -> impl Strategy<Value = BindRule> {
    prop_oneof![
        Just(BindRule::Anyone),
        Just(BindRule::Authenticated),
        Just(BindRule::SelfUser),
        Just(BindRule::ParentDn),
        arb_attr_name().prop_map(|s| BindRule::UserDn(format!("uid={},dc=example,dc=com", s))),
        arb_attr_name().prop_map(|s| BindRule::GroupDn(format!("cn={},dc=example,dc=com", s))),
        arb_attr_name().prop_map(BindRule::Ip),
    ]
}

fn arb_dn_scope() -> impl Strategy<Value = DnScope> {
    let dn_strategy = prop_oneof![
        Just(None),
        Just(Some("dc=example,dc=com".to_string())),
        Just(Some("ou=people,dc=example,dc=com".to_string())),
        Just(Some("uid=alice,ou=people,dc=example,dc=com".to_string())),
        Just(Some("ou=groups,dc=example,dc=com".to_string())),
        Just(Some("cn=admins,ou=groups,dc=example,dc=com".to_string())),
    ];
    (dn_strategy, arb_scope()).prop_map(|(dn, scope)| DnScope::new(dn, scope))
}

fn arb_bind_rule() -> impl Strategy<Value = BindRule> {
    arb_bind_rule_leaf().prop_recursive(2, 8, 4, |inner| {
        prop_oneof![
            prop::collection::vec(inner.clone(), 1..4).prop_map(BindRule::And),
            prop::collection::vec(inner.clone(), 1..4).prop_map(BindRule::Or),
            inner.prop_map(|r| BindRule::Not(Box::new(r))),
        ]
    })
}

// ────────────────────────────── PartialOrd Laws ──────────────────────────────
// These must hold for ALL types with PartialOrd.

macro_rules! partial_ord_laws {
    ($mod_name:ident, $strategy:expr) => {
        mod $mod_name {
            use super::*;

            proptest! {
                #[test]
                fn reflexivity(a in $strategy) {
                    prop_assert_eq!(a.partial_cmp(&a), Some(std::cmp::Ordering::Equal));
                }

                #[test]
                fn antisymmetry(a in $strategy, b in $strategy) {
                    if let (Some(ab), Some(ba)) = (a.partial_cmp(&b), b.partial_cmp(&a)) {
                        if ab == std::cmp::Ordering::Less && ba == std::cmp::Ordering::Less {
                            prop_assert!(false, "antisymmetry violated: a<b and b<a");
                        }
                        if ab == std::cmp::Ordering::Greater && ba == std::cmp::Ordering::Greater {
                            prop_assert!(false, "antisymmetry violated: a>b and b>a");
                        }
                        if ab == std::cmp::Ordering::Equal {
                            prop_assert_eq!(ba, std::cmp::Ordering::Equal);
                        }
                    }
                }

                #[test]
                fn consistency(a in $strategy, b in $strategy) {
                    // If a.partial_cmp(b) = Some(x), then b.partial_cmp(a) = Some(x.reverse())
                    if let Some(ab) = a.partial_cmp(&b) {
                        if let Some(ba) = b.partial_cmp(&a) {
                            prop_assert_eq!(ab.reverse(), ba,
                                "partial_cmp not consistent with reverse");
                        }
                    }
                }
            }
        }
    };
}

partial_ord_laws!(scope_partial_ord, arb_scope());
partial_ord_laws!(operation_type_partial_ord, arb_operation_type());
partial_ord_laws!(operation_set_partial_ord, arb_operation_set());
partial_ord_laws!(attribute_set_partial_ord, arb_attribute_set());
partial_ord_laws!(target_filter_partial_ord, arb_target_filter());
partial_ord_laws!(dn_scope_partial_ord, arb_dn_scope());
partial_ord_laws!(bind_rule_partial_ord, arb_bind_rule());

// ────────────────────────────── Lattice Laws ──────────────────────────────
// These hold for types with full lattice traits (Scope, OperationSet, AttributeSet).

macro_rules! lattice_laws {
    ($mod_name:ident, $strategy:expr, $type:ty) => {
        mod $mod_name {
            use super::*;

            proptest! {
                #[test]
                fn meet_idempotent(a in $strategy) {
                    prop_assert_eq!(a.clone().meet(a.clone()), a);
                }

                #[test]
                fn join_idempotent(a in $strategy) {
                    prop_assert_eq!(a.clone().join(a.clone()), a);
                }

                #[test]
                fn meet_commutative(a in $strategy, b in $strategy) {
                    prop_assert_eq!(a.clone().meet(b.clone()), b.meet(a));
                }

                #[test]
                fn join_commutative(a in $strategy, b in $strategy) {
                    prop_assert_eq!(a.clone().join(b.clone()), b.join(a));
                }

                #[test]
                fn meet_associative(a in $strategy, b in $strategy, c in $strategy) {
                    let lhs = a.clone().meet(b.clone()).meet(c.clone());
                    let rhs = a.meet(b.meet(c));
                    prop_assert_eq!(lhs, rhs);
                }

                #[test]
                fn join_associative(a in $strategy, b in $strategy, c in $strategy) {
                    let lhs = a.clone().join(b.clone()).join(c.clone());
                    let rhs = a.join(b.join(c));
                    prop_assert_eq!(lhs, rhs);
                }

                #[test]
                fn join_identity(a in $strategy) {
                    let id = <$type>::identity();
                    prop_assert_eq!(a.clone().combine(id.clone()), a.clone());
                    prop_assert_eq!(id.combine(a.clone()), a);
                }

                #[test]
                fn bounded_top(a in $strategy) {
                    prop_assert_eq!(a.clone().meet(<$type>::top()), a);
                }

                #[test]
                fn bounded_bottom(a in $strategy) {
                    prop_assert_eq!(a.clone().join(<$type>::bottom()), a);
                }

                #[test]
                fn absorption_meet_join(a in $strategy, b in $strategy) {
                    let result = a.clone().meet(a.clone().join(b));
                    prop_assert_eq!(result, a);
                }

                #[test]
                fn absorption_join_meet(a in $strategy, b in $strategy) {
                    let result = a.clone().join(a.clone().meet(b));
                    prop_assert_eq!(result, a);
                }
            }
        }
    };
}

lattice_laws!(scope_lattice, arb_scope(), Scope);
lattice_laws!(operation_set_lattice, arb_operation_set(), OperationSet);
lattice_laws!(attribute_set_lattice, arb_attribute_set(), AttributeSet);