use active_core::provenance::{collect_provenance, content_hash, LockManifest};
use active_core::{parse, Engine};
use std::collections::HashMap;
fn env_map(pairs: &[(&str, &str)]) -> HashMap<String, String> {
pairs
.iter()
.map(|(k, v)| ((*k).to_string(), (*v).to_string()))
.collect()
}
fn eval_with(src: &str, env: HashMap<String, String>) -> active_core::Value {
let module = parse(src, "lock").expect("must parse");
Engine::new()
.with_env(env)
.evaluate(&module)
.expect("must evaluate")
.0
}
#[test]
fn ambient_environment_is_invisible_under_an_explicit_snapshot() {
let name = "ACTIVE_LOCK_PROBE_AMBIENT";
let prior = std::env::var(name).ok();
std::env::set_var(name, "ambient-should-not-leak");
let src = format!("greeting = env.{name} ?: \"d\"\n");
let value = eval_with(&src, HashMap::new());
let json = active_core::convert::value_to_pretty_json(&value);
assert!(json.contains("\"d\""), "{json}");
assert!(!json.contains("ambient-should-not-leak"), "{json}");
match prior {
Some(value) => std::env::set_var(name, value),
None => std::env::remove_var(name),
}
}
#[test]
fn injected_environment_is_visible() {
let value = eval_with(
"greeting = env.ACTIVE_LOCK_PROBE_SET\n",
env_map(&[("ACTIVE_LOCK_PROBE_SET", "hello")]),
);
let json = active_core::convert::value_to_pretty_json(&value);
assert!(json.contains("\"hello\""), "{json}");
}
#[test]
fn manifest_records_plain_secret_and_unset_vars() {
let module = parse(
"host = env.ACTIVE_LOCK_HOST ?: \"h\"\ntoken = secret.env(\"ACTIVE_LOCK_PW\")\nmissing = env.ACTIVE_LOCK_MISSING_XYZ\n",
"lock",
)
.expect("must parse");
let env = env_map(&[
("ACTIVE_LOCK_HOST", "db.internal"),
("ACTIVE_LOCK_PW", "s3cr3t-lock"),
]);
let provenance = collect_provenance(&module, &env, None, None).expect("provenance");
assert!(provenance.main.is_none());
assert!(provenance.includes.is_empty());
assert!(provenance.profile.is_none());
let by_name = |name: &str| {
provenance
.env
.iter()
.find(|input| input.name == name)
.unwrap_or_else(|| panic!("{name} recorded: {:?}", provenance.env))
.clone()
};
let host = by_name("ACTIVE_LOCK_HOST");
assert!(host.set && !host.secret);
assert_eq!(host.value.as_deref(), Some("db.internal"));
let pw = by_name("ACTIVE_LOCK_PW");
assert!(pw.set && pw.secret);
assert_eq!(pw.value, None);
let hash = pw.value_hash.expect("drift hash");
assert!(hash.starts_with("sha256:"), "{hash}");
let missing = by_name("ACTIVE_LOCK_MISSING_XYZ");
assert!(!missing.set);
let debug = format!("{provenance:?}");
assert!(!debug.contains("s3cr3t-lock"), "{debug}");
}
#[test]
fn transitive_secret_flows_are_secret_bound() {
let module =
parse("let h = env.ACTIVE_LOCK_H\ntoken = secret(h)\n", "lock").expect("must parse");
let env = env_map(&[("ACTIVE_LOCK_H", "s3cr3t-transitive")]);
let provenance = collect_provenance(&module, &env, None, None).expect("provenance");
let entry = provenance
.env
.iter()
.find(|input| input.name == "ACTIVE_LOCK_H")
.expect("recorded");
assert!(entry.set && entry.secret, "{entry:?}");
assert_eq!(entry.value, None);
let debug = format!("{provenance:?}");
assert!(!debug.contains("s3cr3t-transitive"), "{debug}");
}
#[test]
fn manifest_collects_includes_and_main_file() {
let dir = std::env::temp_dir().join("active-lock-tests");
std::fs::create_dir_all(&dir).expect("temp dir");
let lib = dir.join("locklib.active");
let main = dir.join("lockmain.active");
std::fs::write(&lib, "host = \"db\"\n").expect("write lib");
std::fs::write(&main, "include \"locklib.active\"\nport = 1\n").expect("write main");
let module = active_core::parse_file(&main, Default::default()).expect("must parse");
let provenance =
collect_provenance(&module, &HashMap::new(), Some(&main), None).expect("provenance");
assert_eq!(provenance.includes.len(), 1, "{provenance:?}");
assert!(
provenance.includes[0].path.ends_with("locklib.active"),
"{provenance:?}"
);
assert_eq!(provenance.includes[0].sha256.len(), 64);
let main_input = provenance.main.expect("main recorded");
assert!(main_input.path.ends_with("lockmain.active"));
assert_eq!(main_input.sha256.len(), 64);
}
#[test]
fn manifest_records_the_effective_profile() {
let module = parse(
"port = 1\nprofile \"prod\" {\n port = 2\n}\nactive_profile = \"prod\"\n",
"lock",
)
.expect("must parse");
let provenance =
collect_provenance(&module, &HashMap::new(), None, Some("prod")).expect("provenance");
assert_eq!(provenance.profile.as_deref(), Some("prod"));
}
#[test]
fn vanishing_includes_are_hard_errors() {
let dir = std::env::temp_dir().join("active-lock-tests");
std::fs::create_dir_all(&dir).expect("temp dir");
let lib = dir.join("lockgone.active");
let main = dir.join("lockgonemain.active");
std::fs::write(&lib, "host = \"db\"\n").expect("write lib");
std::fs::write(&main, "include \"lockgone.active\"\nport = 1\n").expect("write main");
let module = active_core::parse_file(&main, Default::default()).expect("must parse");
std::fs::remove_file(&lib).expect("delete lib");
let err =
collect_provenance(&module, &HashMap::new(), Some(&main), None).expect_err("must fail");
assert!(err.contains("lockgone.active"), "{err}");
}
#[test]
fn content_hash_is_stable_redacted_and_hex() {
let a = eval_with("port = 1\ntoken = secret(\"s3cr3t-a\")\n", HashMap::new());
let b = eval_with("port = 1\ntoken = secret(\"s3cr3t-b\")\n", HashMap::new());
let c = eval_with("port = 2\n", HashMap::new());
let (ha, hb, hc) = (content_hash(&a), content_hash(&b), content_hash(&c));
assert_eq!(ha, content_hash(&a));
assert_eq!(ha, hb);
assert_ne!(ha, hc);
for hash in [&ha, &hb, &hc] {
assert!(hash.starts_with("sha256:"), "{hash}");
assert_eq!(hash.len(), 7 + 64, "{hash}");
assert!(hash[7..].chars().all(|c| c.is_ascii_hexdigit()), "{hash}");
}
}
#[test]
fn lockfile_round_trips_through_toml() {
let module =
parse("host = env.ACTIVE_LOCK_HOST ?: \"h\"\nport = 1\n", "lock").expect("must parse");
let (value, _) = Engine::new()
.with_env(env_map(&[("ACTIVE_LOCK_HOST", "db")]))
.evaluate(&module)
.expect("must evaluate");
let provenance =
collect_provenance(&module, &env_map(&[("ACTIVE_LOCK_HOST", "db")]), None, None)
.expect("provenance");
let manifest = LockManifest::new(
"active-cli TEST / active-core TEST".to_string(),
&value,
provenance,
);
let rendered = manifest.render();
assert!(rendered.contains("version = 1"), "{rendered}");
assert!(rendered.contains("content_hash = \"sha256:"), "{rendered}");
assert!(rendered.contains("value = \"db\""), "{rendered}");
let reparsed: toml::Value = toml::from_str(&rendered).expect("valid lockfile TOML");
assert_eq!(reparsed["version"], toml::Value::Integer(1));
assert!(reparsed["content_hash"]
.as_str()
.expect("hash")
.starts_with("sha256:"));
}