use super::*;
use kcode_k1_access_types::{FilteredAuthorities, ModelId, UserId, ViewerSubject};
use std::time::{Duration, Instant};
fn tx(value: u32) -> TxId {
let mut bytes = [0; 12];
bytes[..4].copy_from_slice(&value.to_le_bytes());
TxId::from_bytes(bytes)
}
fn access(value: u32) -> AccessId {
AccessId::new(tx(value))
}
fn profile(value: u32) -> ProfileId {
ProfileId::new(tx(value))
}
fn user(value: u32) -> UserId {
UserId::from_tx_id(tx(value))
}
fn group(value: u32) -> GroupId {
GroupId::new(tx(value))
}
fn model(value: u8) -> ModelId {
ModelId::from_bytes([value; 32])
}
fn subsystem(value: &str) -> SubsystemId {
SubsystemId::from_str(value).unwrap()
}
fn target(value: u32, subsystem_value: &str) -> Target {
Target::new(subsystem(subsystem_value), value.to_le_bytes().to_vec())
}
fn context(user_value: u32, model_value: u8) -> AccessContext {
AccessContext::new(
user(user_value),
model(model_value),
FilteredAuthorities::empty(),
)
.unwrap()
}
fn filtered_context(user_value: u32, model_value: u8, authority: Authority) -> AccessContext {
AccessContext::new(
user(user_value),
model(model_value),
FilteredAuthorities::new(vec![authority]).unwrap(),
)
.unwrap()
}
fn policy(
authority: Authority,
editors: Vec<Authority>,
viewers: Vec<ViewerSubject>,
) -> AccessPolicy {
AccessPolicy::new(authority, editors, viewers).unwrap()
}
fn stored(
id: u32,
target_value: u32,
subsystem_value: &str,
revision: u32,
policy: AccessPolicy,
) -> StoredAccess {
StoredAccess::new(
access(id),
target(target_value, subsystem_value),
profile(id + 100),
tx(revision),
policy,
)
}
fn check(
state: &AccessState,
context: &AccessContext,
id: u32,
user_groups: &[GroupId],
model_groups: &[GroupId],
) -> AccessCheck {
state.check(
context,
access(id),
subsystem("alpha"),
user_groups,
model_groups,
Some(tx(8)),
)
}
fn checks(state: &AccessState, ids: &[AccessId]) -> Vec<AccessCheck> {
state.check_many(
&context(1, 1),
ids,
subsystem("alpha"),
&[group(1)],
&[group(1)],
Some(tx(8)),
)
}
fn assert_check(check: &AccessCheck, can_view: bool, can_edit: bool, revision: Option<TxId>) {
assert_eq!((check.can_view(), check.can_edit()), (can_view, can_edit));
assert_eq!(check.target().is_some(), can_view);
assert_eq!(check.access_revision(), revision);
assert_eq!(check.groups_revision(), revision.map(|_| tx(8)));
}
fn assert_concealed(check: &AccessCheck) {
assert_check(check, false, false, None);
}
fn shared(state: &AccessState, first: u32, second: u32) -> bool {
Arc::ptr_eq(
&state.records[&access(first)].policy,
&state.records[&access(second)].policy,
)
}
#[test]
fn initialization_and_mutations_preserve_invariants() {
let original = policy(Authority::User(user(1)), vec![], vec![]);
for records in [
vec![
stored(1, 1, "alpha", 10, original.clone()),
stored(1, 2, "alpha", 11, original.clone()),
],
vec![
stored(1, 1, "alpha", 10, original.clone()),
stored(2, 1, "alpha", 11, original.clone()),
],
] {
assert!(AccessState::new(records).is_err());
}
let mut state = AccessState::new(vec![stored(1, 1, "alpha", 10, original.clone())]).unwrap();
assert_eq!(state.profile_id(access(1)), Some(profile(101)));
assert_eq!(state.profile_id(access(9)), None);
assert!(
state
.create(stored(1, 2, "alpha", 11, original.clone()))
.is_err()
);
assert!(
state
.create(stored(2, 1, "alpha", 11, original.clone()))
.is_err()
);
assert!(state.replace(access(9), tx(12), original).is_err());
let changed_authority = policy(Authority::Group(group(1)), vec![], vec![]);
assert!(state.replace(access(1), tx(12), changed_authority).is_err());
let replacement = policy(
Authority::User(user(1)),
vec![Authority::User(user(2))],
vec![ViewerSubject::Model(model(1))],
);
state
.replace(access(1), tx(12), replacement.clone())
.unwrap();
let record = &state.records[&access(1)];
assert_eq!(record.target, target(1, "alpha"));
assert_eq!(record.profile_id, profile(101));
assert_eq!(record.revision, tx(12));
assert_eq!(record.policy.as_ref(), &replacement);
assert_eq!(state.profile_id(access(1)), Some(profile(101)));
assert_check(
&check(&state, &context(1, 1), 1, &[], &[]),
true,
true,
Some(tx(12)),
);
}
#[test]
fn batches_and_reverse_resolution_preserve_positions() {
let visible = policy(Authority::Group(group(1)), vec![], vec![]);
let hidden = policy(Authority::Group(group(2)), vec![], vec![]);
let editor = policy(
Authority::User(user(2)),
vec![Authority::User(user(1))],
vec![],
);
let viewer = policy(
Authority::User(user(2)),
vec![],
vec![ViewerSubject::User(user(1))],
);
let model_group_viewer = policy(
Authority::User(user(2)),
vec![],
vec![ViewerSubject::Group(group(3))],
);
let state = AccessState::new(vec![
stored(1, 1, "alpha", 11, visible.clone()),
stored(2, 2, "alpha", 12, hidden),
stored(3, 3, "beta", 13, visible),
stored(
4,
4,
"alpha",
14,
policy(Authority::User(user(1)), vec![], vec![]),
),
stored(5, 5, "alpha", 15, editor),
stored(6, 6, "alpha", 16, viewer),
stored(7, 7, "alpha", 17, model_group_viewer),
])
.unwrap();
let ids = [access(1), access(99), access(1), access(2), access(3)];
let results = checks(&state, &ids);
assert_eq!(results.len(), ids.len());
assert_check(&results[0], true, true, Some(tx(11)));
for result in [&results[1], &results[3], &results[4]] {
assert_concealed(result);
}
assert!(checks(&state, &[]).is_empty());
let controller_ids = [
access(1),
access(4),
access(1),
access(5),
access(6),
access(7),
access(99),
];
let actual = state.controller_profile_ids(&context(1, 1), &controller_ids, &[group(1)]);
assert_eq!(
actual,
vec![
Some(profile(101)),
Some(profile(104)),
Some(profile(101)),
None,
None,
None,
None,
]
);
let resolve = |context: &AccessContext, targets: &[Target]| {
state.resolve_visible_targets(
context,
targets,
subsystem("alpha"),
&[group(1)],
&[group(1)],
)
};
let targets = [
target(1, "alpha"),
target(99, "alpha"),
target(1, "alpha"),
target(2, "alpha"),
target(3, "beta"),
];
assert_eq!(
resolve(&context(1, 1), &targets),
vec![Some(access(1)), None, Some(access(1)), None, None]
);
let filtered = filtered_context(1, 1, Authority::Group(group(1)));
assert_eq!(resolve(&filtered, &targets[..1]), vec![None]);
assert!(resolve(&context(1, 1), &[]).is_empty());
assert_eq!(
state.controller_profile_ids(&filtered, &[access(1), access(4)], &[group(1)]),
vec![None, Some(profile(104))]
);
assert!(
state
.controller_profile_ids(&context(1, 1), &[], &[group(1)])
.is_empty()
);
assert!(state.accepts_edit_witness(access(1), Authority::Group(group(1))));
assert!(state.accepts_edit_witness(access(5), Authority::User(user(1))));
assert!(!state.accepts_edit_witness(access(6), Authority::User(user(1))));
assert!(!state.accepts_edit_witness(access(99), Authority::User(user(1))));
}
#[test]
fn policies_are_shared_reused_cleaned_and_scaled() {
let first = policy(Authority::User(user(1)), vec![], vec![]);
let second = policy(
Authority::User(user(1)),
vec![],
vec![ViewerSubject::Model(model(1))],
);
let mut state = AccessState::new(vec![
stored(1, 1, "alpha", 1, first.clone()),
stored(2, 2, "alpha", 2, first.clone()),
stored(3, 3, "alpha", 3, second.clone()),
])
.unwrap();
assert!(shared(&state, 1, 2));
assert!(!shared(&state, 1, 3));
state
.create(stored(4, 4, "alpha", 4, first.clone()))
.unwrap();
assert!(shared(&state, 1, 4));
for id in [1, 2, 4] {
state
.replace(access(id), tx(10 + id), second.clone())
.unwrap();
assert!(shared(&state, id, 3));
assert_eq!(state.profile_id(access(id)), Some(profile(id + 100)));
}
assert!(!state.policies.contains_key(&first));
assert_eq!(state.policies.len(), 1);
let one_policy = policy(Authority::User(user(1)), vec![], vec![]);
let records = (1..=10_000)
.map(|value| stored(value, value, "alpha", 1, one_policy.clone()))
.collect();
let large = AccessState::new(records).unwrap();
assert_eq!(large.policies.len(), 1);
assert_eq!(large.profile_id(access(10_000)), Some(profile(10_100)));
assert!(shared(&large, 1, 10_000));
let ids: Vec<_> = (0..100_000)
.map(|index| access(index % 10_000 + 1))
.collect();
let caller = context(1, 1);
let started = Instant::now();
let profile_ids = large.controller_profile_ids(&caller, &ids, &[]);
assert!(started.elapsed() < Duration::from_secs(10));
assert_eq!(profile_ids.len(), ids.len());
assert_eq!(profile_ids.first(), Some(&Some(profile(101))));
assert_eq!(profile_ids.last(), Some(&Some(profile(10_100))));
}