use bashkit::{Bash, ExecutionLimits};
#[tokio::test]
async fn final_env_disabled_by_default() {
let mut bash = Bash::builder().build();
let result = bash.exec("export FOO=bar").await.unwrap();
assert!(result.final_env.is_none());
}
#[tokio::test]
async fn final_env_captures_exported_variables() {
let limits = ExecutionLimits::new().capture_final_env(true);
let mut bash = Bash::builder().limits(limits).build();
let result = bash.exec("export FOO=bar && export BAZ=qux").await.unwrap();
let env = result.final_env.expect("final_env should be Some");
assert_eq!(env.get("FOO").map(|s| s.as_str()), Some("bar"));
assert_eq!(env.get("BAZ").map(|s| s.as_str()), Some("qux"));
}
#[tokio::test]
async fn final_env_captures_plain_variables() {
let limits = ExecutionLimits::new().capture_final_env(true);
let mut bash = Bash::builder().limits(limits).build();
let result = bash.exec("X=hello").await.unwrap();
let env = result.final_env.expect("final_env should be Some");
assert_eq!(env.get("X").map(|s| s.as_str()), Some("hello"));
}
#[tokio::test]
async fn final_env_reflects_mutations() {
let limits = ExecutionLimits::new().capture_final_env(true);
let mut bash = Bash::builder().limits(limits).build();
bash.exec("VAR=initial").await.unwrap();
let result = bash.exec("VAR=updated").await.unwrap();
let env = result.final_env.expect("final_env should be Some");
assert_eq!(env.get("VAR").map(|s| s.as_str()), Some("updated"));
}
#[tokio::test]
async fn final_env_empty_script() {
let limits = ExecutionLimits::new().capture_final_env(true);
let mut bash = Bash::builder().limits(limits).build();
let result = bash.exec("true").await.unwrap();
let env = result.final_env.expect("final_env should be Some");
assert!(env.is_empty() || !env.is_empty()); }
#[tokio::test]
async fn final_env_not_present_when_disabled() {
let limits = ExecutionLimits::new().capture_final_env(false);
let mut bash = Bash::builder().limits(limits).build();
let result = bash.exec("export FOO=bar").await.unwrap();
assert!(result.final_env.is_none());
}
#[tokio::test]
async fn final_env_persists_across_calls() {
let limits = ExecutionLimits::new().capture_final_env(true);
let mut bash = Bash::builder().limits(limits).build();
bash.exec("A=1").await.unwrap();
bash.exec("B=2").await.unwrap();
let result = bash.exec("C=3").await.unwrap();
let env = result.final_env.expect("final_env should be Some");
assert_eq!(env.get("A").map(|s| s.as_str()), Some("1"));
assert_eq!(env.get("B").map(|s| s.as_str()), Some("2"));
assert_eq!(env.get("C").map(|s| s.as_str()), Some("3"));
}
#[tokio::test]
async fn final_env_on_error_still_captured() {
let limits = ExecutionLimits::new().capture_final_env(true);
let mut bash = Bash::builder().limits(limits).build();
let result = bash.exec("BEFORE_ERR=yes; false").await.unwrap();
assert_ne!(result.exit_code, 0);
let env = result
.final_env
.expect("final_env should be Some even on error");
assert_eq!(env.get("BEFORE_ERR").map(|s| s.as_str()), Some("yes"));
}
#[tokio::test]
async fn final_env_hides_hidden_markers() {
let limits = ExecutionLimits::new().capture_final_env(true);
let mut bash = Bash::builder().limits(limits).build();
let result = bash.exec("_TTY_TEST=secret; VISIBLE=ok").await.unwrap();
let env = result.final_env.expect("final_env should be Some");
assert_eq!(env.get("VISIBLE").map(|s| s.as_str()), Some("ok"));
assert!(!env.contains_key("_TTY_TEST"));
}
#[tokio::test]
async fn final_env_respects_output_byte_cap() {
let limits = ExecutionLimits::new()
.capture_final_env(true)
.max_stdout_bytes(4096);
let mut bash = Bash::builder().limits(limits).build();
let script = format!("SMALL=ok; BIG={}", "x".repeat(10_000));
let result = bash.exec(&script).await.unwrap();
let env = result.final_env.expect("final_env should be Some");
let total_bytes: usize = env.iter().map(|(k, v)| k.len() + v.len()).sum();
assert!(total_bytes <= 4096);
assert_eq!(env.get("SMALL").map(|s| s.as_str()), Some("ok"));
assert!(!env.contains_key("BIG"));
}