#![allow(clippy::expect_used, clippy::unwrap_used)]
use std::collections::HashMap;
use std::num::NonZeroUsize;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use beamr::module::ModuleRegistry;
use beamr::scheduler::{Scheduler, SchedulerConfig, SchedulerServices};
use frame_core::capability::{
Capability, CapabilityDenied, CapabilityMutationError, CapabilityRequest, CapabilityScope,
CheckVerdict, Grant, GrantProvenance, NetworkHost, PathRoot, RevokeOutcome,
};
use frame_core::component::{ComponentId, ComponentMeta, SupervisionPolicy};
use frame_core::error::RegistryError;
use frame_core::event::{EventTryReceiveError, LifecycleEventKind, LifecycleState};
use frame_core::registry::ComponentRegistry;
use frame_core::supervision::LifecycleConfig;
const FFI: &[u8] = include_bytes!("fixtures/spike_worker_ffi.beam");
const TIMEOUT: Duration = Duration::from_secs(3);
fn id(name: &str) -> ComponentId {
ComponentId::derive("frame.tests.capability", name)
}
fn request(capability: Capability) -> CapabilityRequest {
CapabilityRequest::new(capability)
}
fn network(host: &str) -> CapabilityRequest {
request(Capability::Network {
host: NetworkHost(host.to_owned()),
})
}
fn provenance(actor: &str, second: u64) -> GrantProvenance {
GrantProvenance {
granted_by: actor.to_owned(),
granted_at: SystemTime::UNIX_EPOCH + Duration::from_secs(second),
}
}
fn scheduler() -> Arc<Scheduler> {
Arc::new(
frame_core::composition::compose_scheduler(
SchedulerConfig {
thread_count: Some(2),
..SchedulerConfig::default()
},
SchedulerServices::minimal(),
Arc::new(ModuleRegistry::new()),
)
.expect("published beamr scheduler starts"),
)
}
fn registry(scheduler: &Arc<Scheduler>) -> Arc<ComponentRegistry> {
Arc::new(ComponentRegistry::new(
Arc::clone(scheduler),
LifecycleConfig {
operation_timeout: TIMEOUT,
},
))
}
fn meta(name: &str, needs: Vec<CapabilityRequest>) -> ComponentMeta {
ComponentMeta {
id: id(name),
name: name.to_owned(),
version: "1.0.0".to_owned(),
requires: Vec::new(),
provides: Vec::new(),
needs,
children: Vec::new(),
supervision: Some(SupervisionPolicy {
max_restarts: 1,
window: Duration::from_secs(30),
}),
}
}
fn denied(verdict: CheckVerdict) -> Option<CapabilityDenied> {
match verdict {
CheckVerdict::Allowed => None,
CheckVerdict::Denied(denial) => Some(denial),
}
}
#[test]
fn registration_rejects_malformed_and_duplicate_needs_without_partial_state() {
let scheduler = scheduler();
let registry = registry(&scheduler);
let malformed = request(Capability::FsRead {
root: PathRoot(PathBuf::from("relative/path")),
});
assert!(matches!(
registry.register(meta("malformed", vec![malformed.clone()]), FFI.to_vec()),
Err(RegistryError::InvalidCapabilityScope { request, .. }) if request == malformed
));
assert_eq!(registry.len().unwrap(), 0);
let pattern = network("*.example.com");
assert!(matches!(
registry.register(meta("pattern", vec![pattern.clone()]), FFI.to_vec()),
Err(RegistryError::InvalidCapabilityScope { request, .. }) if request == pattern
));
assert_eq!(registry.len().unwrap(), 0);
let duplicate = network("api.example.com");
assert!(matches!(
registry.register(
meta("duplicate", vec![duplicate.clone(), duplicate.clone()]),
FFI.to_vec()
),
Err(RegistryError::DuplicateCapabilityNeed { request, .. }) if request == duplicate
));
assert_eq!(registry.len().unwrap(), 0);
scheduler.shutdown();
}
#[test]
fn declared_and_undeclared_absence_deny_typed_and_event_once_each() {
let scheduler = scheduler();
let registry = registry(&scheduler);
let component = id("absence");
let declared = network("api.example.com");
registry
.register(meta("absence", vec![declared.clone()]), FFI.to_vec())
.unwrap();
assert!(
registry
.host_capabilities()
.grants_for(component)
.unwrap()
.is_empty()
);
let events = registry.subscribe(NonZeroUsize::new(4).unwrap()).unwrap();
let checker = registry.capability_checker(component).unwrap();
let declared_denial = denied(checker.check(&declared.capability).unwrap()).unwrap();
assert_eq!(declared_denial.kind, declared.capability.kind());
assert_eq!(declared_denial.scope, declared.capability.scope());
assert!(declared_denial.declared);
let undeclared = network("other.example.com");
let undeclared_denial = denied(checker.check(&undeclared.capability).unwrap()).unwrap();
assert_eq!(undeclared_denial.kind, undeclared.capability.kind());
assert_eq!(undeclared_denial.scope, undeclared.capability.scope());
assert!(!undeclared_denial.declared);
for expected in [declared_denial, undeclared_denial] {
let event = events.recv_timeout(TIMEOUT).unwrap();
assert_eq!(event.kind, LifecycleEventKind::CapabilityDenied(expected));
}
assert_eq!(events.try_recv(), Err(EventTryReceiveError::Empty));
registry.remove(component).unwrap();
scheduler.shutdown();
}
#[test]
fn retained_checker_observes_revoke_on_the_very_next_consuming_act() {
let scheduler = scheduler();
let registry = registry(&scheduler);
let component = id("retained");
let need = network("api.example.com");
registry
.register(meta("retained", vec![need.clone()]), FFI.to_vec())
.unwrap();
let checker = registry.capability_checker(component).unwrap();
registry
.host_capabilities()
.grant(component, need.clone(), provenance("composition", 1))
.unwrap();
assert_eq!(
checker.check(&need.capability).unwrap(),
CheckVerdict::Allowed
);
let consuming_act = || checker.check(&need.capability);
assert!(matches!(
registry
.host_capabilities()
.revoke(component, &need)
.unwrap(),
RevokeOutcome::Revoked(_)
));
let denial = denied(consuming_act().unwrap()).unwrap();
assert!(denial.declared);
assert_eq!(
registry
.host_capabilities()
.revoke(component, &need)
.unwrap(),
RevokeOutcome::NotGranted
);
registry.remove(component).unwrap();
scheduler.shutdown();
}
#[test]
fn grant_for_undeclared_need_is_a_typed_refusal() {
let scheduler = scheduler();
let registry = registry(&scheduler);
let component = id("undeclared-grant");
registry
.register(meta("undeclared-grant", Vec::new()), FFI.to_vec())
.unwrap();
let undeclared = network("api.example.com");
assert!(matches!(
registry.host_capabilities().grant(
component,
undeclared.clone(),
provenance("composition", 1)
),
Err(CapabilityMutationError::UndeclaredNeed {
component_id,
request
}) if component_id == component && request == undeclared
));
registry.remove(component).unwrap();
scheduler.shutdown();
}
#[test]
fn removal_clears_authority_before_same_id_reincarnation() {
let scheduler = scheduler();
let registry = registry(&scheduler);
let component = id("reincarnation");
let need = network("api.example.com");
registry
.register(meta("reincarnation", vec![need.clone()]), FFI.to_vec())
.unwrap();
registry
.host_capabilities()
.grant(component, need.clone(), provenance("composition", 1))
.unwrap();
let old_checker = registry.capability_checker(component).unwrap();
registry.remove(component).unwrap();
assert!(matches!(
old_checker.check(&need.capability).unwrap(),
CheckVerdict::Denied(_)
));
registry
.register(meta("reincarnation", vec![need.clone()]), FFI.to_vec())
.unwrap();
assert!(
registry
.host_capabilities()
.grants_for(component)
.unwrap()
.is_empty()
);
let fresh = registry.capability_checker(component).unwrap();
assert!(matches!(
fresh.check(&need.capability).unwrap(),
CheckVerdict::Denied(CapabilityDenied { declared: true, .. })
));
registry.remove(component).unwrap();
scheduler.shutdown();
}
#[test]
fn full_table_reconstructs_mutations_with_exact_provenance() {
let scheduler = scheduler();
let registry = registry(&scheduler);
let component = id("inspection");
let first = network("one.example.com");
let second = network("two.example.com");
registry
.register(
meta("inspection", vec![first.clone(), second.clone()]),
FFI.to_vec(),
)
.unwrap();
let first_grant = registry
.host_capabilities()
.grant(component, first.clone(), provenance("operator-a", 10))
.unwrap();
let second_grant = registry
.host_capabilities()
.grant(component, second.clone(), provenance("operator-b", 20))
.unwrap();
let revoked = registry
.host_capabilities()
.revoke(component, &first)
.unwrap();
let mut reconstructed = HashMap::<CapabilityRequest, Grant>::new();
reconstructed.insert(first, first_grant);
reconstructed.insert(second.clone(), second_grant.clone());
let row = match revoked {
RevokeOutcome::Revoked(row) => Some(row),
RevokeOutcome::NotGranted => None,
}
.unwrap();
reconstructed.remove(&row.request);
let table = registry.host_capabilities().full_table();
assert_eq!(table.len(), reconstructed.len());
assert_eq!(reconstructed.get(&second), Some(&second_grant));
assert_eq!(table, vec![second_grant]);
registry.remove(component).unwrap();
scheduler.shutdown();
}
#[test]
fn scope_matching_is_path_containment_and_exact_host_without_patterns() {
let scheduler = scheduler();
let registry = registry(&scheduler);
let component = id("scopes");
let fs = request(Capability::FsRead {
root: PathRoot(PathBuf::from("/srv/data")),
});
let host = network("api.example.com");
registry
.register(meta("scopes", vec![fs.clone(), host.clone()]), FFI.to_vec())
.unwrap();
registry
.host_capabilities()
.grant(component, fs, provenance("composition", 1))
.unwrap();
registry
.host_capabilities()
.grant(component, host, provenance("composition", 2))
.unwrap();
let checker = registry.capability_checker(component).unwrap();
assert_eq!(
checker
.check(&Capability::FsRead {
root: PathRoot(PathBuf::from("/srv/data/file.txt")),
})
.unwrap(),
CheckVerdict::Allowed
);
let path_denial = denied(
checker
.check(&Capability::FsRead {
root: PathRoot(PathBuf::from("/srv/database/file.txt")),
})
.unwrap(),
)
.unwrap();
assert!(!path_denial.declared);
let host_denial = denied(
checker
.check(&Capability::Network {
host: NetworkHost("sub.api.example.com".to_owned()),
})
.unwrap(),
)
.unwrap();
assert_eq!(
host_denial.scope,
CapabilityScope::Host("sub.api.example.com".to_owned())
);
assert!(!host_denial.declared);
registry.remove(component).unwrap();
scheduler.shutdown();
}
#[test]
fn early_start_denial_then_grant_retry_succeeds_without_restart() {
let scheduler = scheduler();
let registry = registry(&scheduler);
let component = id("early-start");
let need = network("api.example.com");
registry
.register(meta("early-start", vec![need.clone()]), FFI.to_vec())
.unwrap();
registry.start(component).unwrap();
let events = registry.subscribe(NonZeroUsize::new(2).unwrap()).unwrap();
let checker = registry.capability_checker(component).unwrap();
let denial = denied(checker.check(&need.capability).unwrap()).unwrap();
assert!(denial.declared);
let event = events.recv_timeout(TIMEOUT).unwrap();
assert_eq!(event.kind, LifecycleEventKind::CapabilityDenied(denial));
registry
.host_capabilities()
.grant(component, need.clone(), provenance("boot-composition", 1))
.unwrap();
assert_eq!(
checker.check(&need.capability).unwrap(),
CheckVerdict::Allowed
);
assert_eq!(
registry.status(component).unwrap().unwrap().state,
LifecycleState::Running
);
assert_eq!(events.try_recv(), Err(EventTryReceiveError::Empty));
registry.remove(component).unwrap();
scheduler.shutdown();
}