use secure_authz::{
action::Action,
cache::{CacheKey, DecisionCache},
decision::{Decision, DenyReason},
enforcer::Authorizer,
resource::ResourceRef,
testkit::test_subject,
testkit::MockAuthorizer,
};
use std::time::Duration;
#[tokio::test]
async fn test_mock_authorizer_call_count() {
let mock = MockAuthorizer::allow();
let subject = test_subject("alice", &["editor"]);
let resource = ResourceRef::new("article");
let d1 = mock.authorize(&subject, &Action::Read, &resource).await;
let d2 = mock.authorize(&subject, &Action::Read, &resource).await;
assert!(d1.is_allow());
assert!(d2.is_allow());
assert_eq!(mock.call_count(), 2);
}
#[test]
fn test_cache_bounded_by_size() {
let cache = DecisionCache::new(100, Duration::from_secs(60));
for i in 0..200_usize {
let key = CacheKey {
actor_id: format!("actor_{i}"),
action: "read".to_owned(),
resource_kind: "article".to_owned(),
resource_id: format!("res_{i}"),
policy_version: 1,
tenant_id: None,
};
cache.insert(
key,
Decision::Allow {
obligations: vec![],
},
);
}
}
#[test]
fn test_stale_cache_entry_expires() {
let cache = DecisionCache::new(100, Duration::from_nanos(1));
let key = CacheKey {
actor_id: "alice".to_owned(),
action: "read".to_owned(),
resource_kind: "article".to_owned(),
resource_id: "r1".to_owned(),
policy_version: 1,
tenant_id: None,
};
cache.insert(
key.clone(),
Decision::Allow {
obligations: vec![],
},
);
std::thread::sleep(Duration::from_millis(10));
let result = cache.get(&key);
assert!(result.is_none(), "Expected cache miss after TTL expiry");
}
#[test]
fn test_policy_version_change_invalidates_cache() {
let cache = DecisionCache::new(100, Duration::from_secs(60));
let key_v1 = CacheKey {
actor_id: "alice".to_owned(),
action: "read".to_owned(),
resource_kind: "article".to_owned(),
resource_id: "*".to_owned(),
policy_version: 1,
tenant_id: None,
};
let key_v2 = CacheKey {
policy_version: 2,
..key_v1.clone()
};
cache.insert(
key_v1.clone(),
Decision::Allow {
obligations: vec![],
},
);
assert!(
cache.get(&key_v2).is_none(),
"Expected cache miss for new policy version"
);
assert!(
cache.get(&key_v1).is_some(),
"Expected cache hit for original policy version"
);
}
#[test]
fn test_cache_hit_returns_same_decision() {
let cache = DecisionCache::new(10, Duration::from_secs(60));
let key = CacheKey {
actor_id: "alice".to_owned(),
action: "read".to_owned(),
resource_kind: "article".to_owned(),
resource_id: "*".to_owned(),
policy_version: 1,
tenant_id: None,
};
let decision = Decision::Deny {
reason: DenyReason::InsufficientRole,
};
cache.insert(key.clone(), decision.clone());
let cached = cache.get(&key).unwrap();
assert_eq!(cached, decision);
}