use crate::manifest::{
ManifestError, ManifestName, ManifestValue, RESERVED_VAR_NAMES, register_manifest_vars,
};
use minijinja::Environment;
use proptest::prelude::*;
use serde_json::{Map, Value, json};
fn builtin_globals() -> Vec<String> {
Environment::new()
.globals()
.map(|(name, _)| name.to_owned())
.collect()
}
fn global_names(jinja: &Environment<'_>) -> Vec<String> {
let mut names: Vec<String> = jinja.globals().map(|(name, _)| name.to_owned()).collect();
names.sort();
names
}
fn reserved_name() -> impl Strategy<Value = &'static str> {
proptest::sample::select(RESERVED_VAR_NAMES.to_vec())
}
fn safe_key() -> impl Strategy<Value = String> {
"[a-z][a-z_]{0,7}".prop_filter("must not collide with an existing global", |key| {
!RESERVED_VAR_NAMES.contains(&key.as_str()) && !builtin_globals().contains(key)
})
}
fn var_value() -> impl Strategy<Value = Value> {
prop_oneof![
any::<bool>().prop_map(Value::from),
any::<i32>().prop_map(Value::from),
"[a-z ]{0,12}".prop_map(Value::from),
proptest::collection::vec("[a-z]{1,4}", 0..3).prop_map(|items| json!(items)),
]
}
fn safe_vars() -> impl Strategy<Value = Map<String, Value>> {
proptest::collection::hash_map(safe_key(), var_value(), 0..6)
.prop_map(|entries| entries.into_iter().collect())
}
fn doc_with_vars(vars: Map<String, Value>) -> ManifestValue {
Value::Object(Map::from_iter([("vars".to_owned(), Value::Object(vars))]))
}
fn register(vars: Map<String, Value>) -> (Environment<'static>, Result<(), ManifestError>) {
let mut jinja = Environment::new();
let result = register_manifest_vars(
&doc_with_vars(vars),
&mut jinja,
&ManifestName::new("Netsukefile"),
);
(jinja, result)
}
proptest! {
#[test]
fn reserved_key_anywhere_in_the_map_is_rejected(
mut vars in safe_vars(),
reserved in reserved_name(),
value in var_value(),
) {
vars.insert(reserved.to_owned(), value);
let (_, result) = register(vars);
prop_assert!(result.is_err(), "reserved key `{reserved}` should be rejected");
}
#[test]
fn rejected_vars_leave_the_environment_untouched(
mut vars in safe_vars(),
reserved in reserved_name(),
value in var_value(),
) {
vars.insert(reserved.to_owned(), value);
let (jinja, result) = register(vars);
prop_assert!(result.is_err());
let mut baseline = builtin_globals();
baseline.sort();
prop_assert_eq!(global_names(&jinja), baseline);
}
#[test]
fn accepted_vars_register_every_key(vars in safe_vars()) {
let mut expected = builtin_globals();
expected.extend(vars.keys().cloned());
expected.sort();
let (jinja, result) = register(vars);
result.map_err(|e| TestCaseError::fail(e.to_string()))?;
prop_assert_eq!(global_names(&jinja), expected);
}
}