#![doc = include_str!("../Documentation.md")]
use std::sync::Arc;
use kcode_k1_access_launch_nodes::{
AccessContext, AccessId, AccessPolicy, Authority, K1AccessLaunchNodes, ProfileId, TargetId,
TargetName, TxId,
};
use serde::Deserialize;
const INVALID: &str = "invalid SetLaunchNode arguments";
#[derive(Clone)]
pub struct SetLaunchNodeKtool {
launch_nodes: Arc<K1AccessLaunchNodes>,
}
impl SetLaunchNodeKtool {
pub fn new(launch_nodes: Arc<K1AccessLaunchNodes>) -> Self {
Self { launch_nodes }
}
pub fn launch(
&self,
context: &AccessContext,
profile_id: ProfileId,
policy: AccessPolicy,
arguments: &str,
) -> Result<String, String> {
let parsed = parse_arguments(arguments)?;
let target_name = TargetName::new(parsed.target).map_err(|_| invalid())?;
let node = parsed
.node_id
.parse::<TxId>()
.map(AccessId::new)
.map_err(|_| invalid())?;
let target = TargetId::new(Authority::User(context.user()), target_name);
self.launch_nodes
.set(context, profile_id, policy, target, node)?;
Ok("success".to_owned())
}
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct Arguments {
target: String,
node_id: String,
}
fn parse_arguments(arguments: &str) -> Result<Arguments, String> {
serde_json::from_str(arguments).map_err(|_| invalid())
}
fn invalid() -> String {
INVALID.to_owned()
}
#[cfg(test)]
mod tests {
use super::*;
use kcode_k1_access::{
FilteredAuthorities, K1Access, ModelId, SubsystemId, Target, UserId, ViewerSubject,
};
use kcode_k1_groups::K1Groups;
use kcode_k1_launch_nodes::LaunchNodes;
use kcode_k1_peering::K1Peering;
use kcode_k1_txn_ordering::K1TxnOrdering;
fn tx(value: u8) -> TxId {
TxId::from_bytes([value; 12])
}
#[test]
fn strict_arguments_reject_every_malformed_shape() {
let valid = r#"{"target":"Harness","node_id":"000000000000000000000001"}"#;
assert!(parse_arguments(valid).is_ok());
for arguments in [
"",
"{}",
"[]",
r#"{"target":"Harness"}"#,
r#"{"node_id":"000000000000000000000001"}"#,
r#"{"target":"Harness","node_id":"000000000000000000000001","extra":true}"#,
] {
assert_eq!(parse_arguments(arguments).err(), Some(INVALID.to_owned()));
}
}
#[test]
fn real_stack_create_and_update_use_the_context_user() {
let root = tempfile::tempdir().unwrap();
let ordering = Arc::new(K1TxnOrdering::open(&root.path().join("ordering")).unwrap());
let peering =
Arc::new(K1Peering::open(&root.path().join("peering"), ordering.clone()).unwrap());
let groups = Arc::new(
K1Groups::open(
&root.path().join("groups"),
ordering.clone(),
peering.clone(),
)
.unwrap(),
);
let access = Arc::new(
K1Access::open(
&root.path().join("access"),
ordering.clone(),
peering.clone(),
groups.clone(),
)
.unwrap(),
);
let bindings = Arc::new(
LaunchNodes::open(&root.path().join("launch-nodes"), ordering, peering).unwrap(),
);
let facade = Arc::new(K1AccessLaunchNodes::open(access.clone(), groups, bindings).unwrap());
let tool = SetLaunchNodeKtool::new(facade.clone());
let user = UserId::from_tx_id(tx(1));
let model = ModelId::from_bytes([2; 32]);
let context =
AccessContext::new(user, model, FilteredAuthorities::new(Vec::new()).unwrap()).unwrap();
let profile = ProfileId::new(tx(3));
let policy = AccessPolicy::new(
Authority::User(user),
Vec::new(),
vec![ViewerSubject::Model(model)],
)
.unwrap();
let subsystem = SubsystemId::from_str("k1-kmap").unwrap();
let first = access
.create(
&context,
Target::new(subsystem, vec![4; 12]),
profile,
policy.clone(),
)
.unwrap()
.access_id();
let second = access
.create(
&context,
Target::new(subsystem, vec![5; 12]),
profile,
policy.clone(),
)
.unwrap()
.access_id();
let target = TargetId::new(
Authority::User(user),
TargetName::new("Harness".to_owned()).unwrap(),
);
let first_arguments = format!(r#"{{"target":"Harness","node_id":"{}"}}"#, first.txid());
assert_eq!(
tool.launch(&context, profile, policy.clone(), &first_arguments),
Ok("success".to_owned())
);
assert_eq!(facade.lookup(&context, &target), Ok(first));
let second_arguments = format!(r#"{{"target":"Harness","node_id":"{}"}}"#, second.txid());
assert_eq!(
tool.launch(&context, profile, policy, &second_arguments),
Ok("success".to_owned())
);
assert_eq!(facade.lookup(&context, &target), Ok(second));
}
#[test]
fn launch_rejects_invalid_target_and_node_before_mutation() {
let invalid_target = Arguments {
target: " ".to_owned(),
node_id: "000000000000000000000001".to_owned(),
};
assert!(TargetName::new(invalid_target.target).is_err());
assert!("00000000000000000000000A".parse::<TxId>().is_err());
}
#[test]
fn complete_package_stays_below_the_managed_limit() {
let files = [
include_str!("../Cargo.toml"),
include_str!("../Documentation.md"),
include_str!("lib.rs"),
];
let count = files
.iter()
.flat_map(|file| file.lines())
.filter(|line| !line.trim().is_empty())
.count();
assert!(count < 500, "complete package has {count} nonblank lines");
}
}