#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use std::sync::Arc;
use brink_compiler::{AnalysisOptions, Dialect};
use brink_format::{DefinitionId, StoryData, Value};
use brink_runtime::{DotNetRng, FallbackHandler, RuntimeError, Story, link};
const SRC: &str = "#@private\n\
VAR secret = 5\n\
== start ==\n\
Hi\n\
-> DONE\n\
== hidden ==\n\
#@private\n\
Secret content\n\
-> DONE\n";
fn compiled() -> brink_format::StoryData {
let options = AnalysisOptions {
dialect: Dialect::Brink,
..AnalysisOptions::default()
};
brink_compiler::compile_with_options("story.ink", |_p| Ok(SRC.to_owned()), options)
.unwrap()
.data
}
fn story(data: &brink_format::StoryData) -> Story<DotNetRng> {
let (program, line_tables) = link(data).unwrap();
Story::new(Arc::new(program), line_tables)
}
fn definition_id_for(data: &StoryData, path: &str) -> DefinitionId {
let name_id = data
.name_table
.iter()
.position(|n| n == path)
.unwrap_or_else(|| panic!("no interned name for path {path:?}"));
data.address_paths
.iter()
.find(|ap| ap.path.0 as usize == name_id)
.unwrap_or_else(|| panic!("no address path for {path:?}"))
.target
}
#[test]
fn host_semantic_access_refused_for_private_defs() {
let data = compiled();
assert!(
!data.private_defs.is_empty(),
"the declared module must produce private defs"
);
let mut s = story(&data);
assert!(s.visibility_enforced(), "enforcement is on by default");
assert_eq!(s.variable("secret"), None);
assert!(!s.set_variable("secret", Value::Int(99)));
assert!(matches!(
s.choose_path_string("hidden"),
Err(RuntimeError::PrivateAccess { .. })
));
assert!(matches!(
s.call_function("hidden", &[], &FallbackHandler),
Err(RuntimeError::PrivateAccess { .. })
));
s.choose_path_string("start").unwrap();
}
#[test]
fn persistence_sees_private_state_regardless_of_enforcement() {
let data = compiled();
let s = story(&data);
let save = s.save_state();
assert!(
save.globals.iter().any(|(name, _)| name == "secret"),
"persistence must serialize private globals; got {:?}",
save.globals
);
}
#[test]
fn dev_override_allows_private_access() {
let data = compiled();
let mut s = story(&data);
s.set_visibility_enforcement(false);
assert!(!s.visibility_enforced());
assert_eq!(s.variable("secret"), Some(&Value::Int(5)));
assert!(s.set_variable("secret", Value::Int(42)));
assert_eq!(s.variable("secret"), Some(&Value::Int(42)));
s.choose_path_string("hidden").unwrap();
}
#[test]
fn spawn_flow_by_id_refused_for_private_def() {
let data = compiled();
let hidden_id = definition_id_for(&data, "hidden");
let mut s = story(&data);
assert!(s.visibility_enforced(), "enforcement is on by default");
assert!(matches!(
s.spawn_flow("f", hidden_id),
Err(RuntimeError::PrivateAccess { .. })
));
assert!(s.flow_names().is_empty());
}
#[test]
fn spawn_flow_by_id_allows_public_def() {
let data = compiled();
let start_id = definition_id_for(&data, "start");
let mut s = story(&data);
s.spawn_flow("f", start_id).unwrap();
assert_eq!(s.flow_names(), vec!["f"]);
}
#[test]
fn spawn_flow_by_id_dev_override_allows_private_access() {
let data = compiled();
let hidden_id = definition_id_for(&data, "hidden");
let mut s = story(&data);
s.set_visibility_enforcement(false);
s.spawn_flow("f", hidden_id).unwrap();
assert_eq!(s.flow_names(), vec!["f"]);
}
#[test]
fn spawn_flow_shared_by_id_refused_for_private_def() {
let data = compiled();
let (program, _line_tables) = link(&data).unwrap();
let hidden_idx = program
.find_address("hidden")
.map(|(idx, _)| idx)
.expect("hidden knot resolves to a container");
let mut s = story(&data);
assert!(s.visibility_enforced(), "enforcement is on by default");
assert!(matches!(
s.spawn_flow_shared("f", Some(hidden_idx)),
Err(RuntimeError::PrivateAccess { .. })
));
assert!(s.flow_names().is_empty());
}
#[test]
fn spawn_flow_shared_by_id_allows_public_def() {
let data = compiled();
let (program, _line_tables) = link(&data).unwrap();
let start_idx = program
.find_address("start")
.map(|(idx, _)| idx)
.expect("start knot resolves to a container");
let mut s = story(&data);
s.spawn_flow_shared("f", Some(start_idx)).unwrap();
assert_eq!(s.flow_names(), vec!["f"]);
}
#[test]
fn spawn_flow_shared_by_id_dev_override_allows_private_access() {
let data = compiled();
let (program, _line_tables) = link(&data).unwrap();
let hidden_idx = program
.find_address("hidden")
.map(|(idx, _)| idx)
.expect("hidden knot resolves to a container");
let mut s = story(&data);
s.set_visibility_enforcement(false);
s.spawn_flow_shared("f", Some(hidden_idx)).unwrap();
assert_eq!(s.flow_names(), vec!["f"]);
}