use super::*;
use crate::core::types::GlobalLock;
use tempfile::TempDir;
#[test]
fn roundtrip_global_lock_with_outputs() {
let mut lock = new_global_lock("test-config");
lock.outputs
.insert("db_host".to_string(), "10.0.0.5".to_string());
lock.outputs
.insert("api_port".to_string(), "8080".to_string());
let yaml = serde_yaml_ng::to_string(&lock).expect("serialize");
let parsed: GlobalLock = serde_yaml_ng::from_str(&yaml).expect("deserialize");
assert_eq!(parsed.outputs.len(), 2);
assert_eq!(parsed.outputs["db_host"], "10.0.0.5");
assert_eq!(parsed.outputs["api_port"], "8080");
}
#[test]
fn backward_compat_no_outputs_field() {
let yaml = r#"
schema: "1.0"
name: old-config
last_apply: "2024-01-01T00:00:00Z"
generator: forjar 1.0.0
machines: {}
"#;
let lock: GlobalLock = serde_yaml_ng::from_str(yaml).expect("deserialize");
assert!(lock.outputs.is_empty());
}
#[test]
fn empty_outputs_skipped_in_serialization() {
let lock = new_global_lock("test");
let yaml = serde_yaml_ng::to_string(&lock).expect("serialize");
assert!(
!yaml.contains("outputs:"),
"empty outputs should be skipped"
);
}
#[test]
fn persist_and_load_outputs() {
let tmp = TempDir::new().expect("tempdir");
let state_dir = tmp.path();
update_global_lock(state_dir, "myconfig", &[]).expect("update global lock");
let mut outputs = indexmap::IndexMap::new();
outputs.insert("vip_addr".to_string(), "192.168.1.100".to_string());
outputs.insert("cluster_name".to_string(), "prod-east".to_string());
persist_outputs(state_dir, "myconfig", &outputs, false).expect("persist outputs");
let lock = load_global_lock(state_dir)
.expect("load")
.expect("lock exists");
assert_eq!(lock.outputs.len(), 2);
assert_eq!(lock.outputs["vip_addr"], "192.168.1.100");
assert_eq!(lock.outputs["cluster_name"], "prod-east");
}
#[test]
fn persist_outputs_creates_lock() {
let tmp = TempDir::new().expect("tempdir");
let state_dir = tmp.path();
let mut outputs = indexmap::IndexMap::new();
outputs.insert("key".to_string(), "value".to_string());
persist_outputs(state_dir, "new-config", &outputs, false).expect("persist");
let lock = load_global_lock(state_dir).expect("load").expect("exists");
assert_eq!(lock.name, "new-config");
assert_eq!(lock.outputs["key"], "value");
}
#[test]
fn persist_outputs_overwrites_previous() {
let tmp = TempDir::new().expect("tempdir");
let state_dir = tmp.path();
let mut v1 = indexmap::IndexMap::new();
v1.insert("port".to_string(), "8080".to_string());
persist_outputs(state_dir, "cfg", &v1, false).expect("persist v1");
let mut v2 = indexmap::IndexMap::new();
v2.insert("port".to_string(), "9090".to_string());
v2.insert("host".to_string(), "new-host".to_string());
persist_outputs(state_dir, "cfg", &v2, false).expect("persist v2");
let lock = load_global_lock(state_dir).expect("load").expect("exists");
assert_eq!(lock.outputs.len(), 2);
assert_eq!(lock.outputs["port"], "9090");
assert_eq!(lock.outputs["host"], "new-host");
}
#[test]
fn persist_outputs_ephemeral_redacts() {
let tmp = TempDir::new().expect("tempdir");
let state_dir = tmp.path();
let mut outputs = indexmap::IndexMap::new();
outputs.insert("db_password".to_string(), "super-secret".to_string());
outputs.insert("app_port".to_string(), "8080".to_string());
persist_outputs(state_dir, "cfg", &outputs, true).expect("persist ephemeral");
let lock = load_global_lock(state_dir).expect("load").expect("exists");
for (_k, v) in &lock.outputs {
assert!(
crate::core::state::ephemeral::is_ephemeral_marker(v),
"expected ephemeral marker, got: {v}"
);
}
assert!(!lock.outputs["db_password"].contains("super-secret"));
assert!(!lock.outputs["app_port"].contains("8080"));
}
#[test]
fn persist_outputs_non_ephemeral_preserves() {
let tmp = TempDir::new().expect("tempdir");
let state_dir = tmp.path();
let mut outputs = indexmap::IndexMap::new();
outputs.insert("data_dir".to_string(), "/var/data".to_string());
persist_outputs(state_dir, "cfg", &outputs, false).expect("persist");
let lock = load_global_lock(state_dir).expect("load").expect("exists");
assert_eq!(lock.outputs["data_dir"], "/var/data");
}
#[test]
fn ephemeral_drift_detection() {
use crate::core::state::ephemeral;
let secret = "my-db-password-2026";
let marker = ephemeral::redact_to_hash(secret);
assert!(ephemeral::verify_drift(secret, &marker));
assert!(!ephemeral::verify_drift("changed-password", &marker));
}