mod common;
use std::fs;
use tempfile::tempdir;
#[test]
fn test_help() {
let home = tempdir().unwrap();
let dir = tempdir().unwrap();
common::lade(home.path())
.current_dir(dir.path())
.arg("-h")
.assert()
.success();
}
#[test]
fn test_set_raw_values() {
let dir = tempdir().unwrap();
let home = tempdir().unwrap();
fs::write(
dir.path().join("lade.yml"),
"\"mycmd\":\n SECRET: mysecret\n",
)
.unwrap();
common::lade(home.path())
.current_dir(dir.path())
.args(["set", "mycmd"])
.assert()
.success()
.stdout(predicates::str::contains("export SECRET='mysecret'"));
}
#[test]
fn test_set_multiple_secrets() {
let dir = tempdir().unwrap();
let home = tempdir().unwrap();
fs::write(
dir.path().join("lade.yml"),
"\"mycmd\":\n KEY1: val1\n KEY2: val2\n",
)
.unwrap();
let output = common::lade(home.path())
.current_dir(dir.path())
.args(["set", "mycmd"])
.assert()
.success()
.get_output()
.stdout
.clone();
let stdout = String::from_utf8_lossy(&output);
assert!(stdout.contains("export KEY1='val1'"), "stdout: {stdout}");
assert!(stdout.contains("export KEY2='val2'"), "stdout: {stdout}");
}
#[test]
fn test_unset_keys() {
let dir = tempdir().unwrap();
let home = tempdir().unwrap();
fs::write(
dir.path().join("lade.yml"),
"\"mycmd\":\n SECRET: mysecret\n",
)
.unwrap();
common::lade(home.path())
.current_dir(dir.path())
.args(["unset", "mycmd"])
.assert()
.success()
.stdout(predicates::str::contains("unset -v SECRET"));
}
#[test]
fn test_set_no_lade_yml_exits_cleanly() {
let dir = tempdir().unwrap();
let home = tempdir().unwrap();
let out = common::lade(home.path())
.current_dir(dir.path())
.args(["set", "mycmd"])
.assert()
.success()
.get_output()
.stdout
.clone();
let stdout = String::from_utf8_lossy(&out);
assert!(!stdout.contains("export"), "unexpected exports: {stdout}");
}
#[test]
fn test_set_malformed_lade_yml_fails() {
let dir = tempdir().unwrap();
let home = tempdir().unwrap();
fs::write(
dir.path().join("lade.yml"),
"\"cmd\":\n \".\": \"old_string_format\"\n KEY: val\n",
)
.unwrap();
common::lade(home.path())
.current_dir(dir.path())
.args(["set", "cmd"])
.assert()
.failure();
}
#[test]
fn test_set_with_file_provider() {
let dir = tempdir().unwrap();
let home = tempdir().unwrap();
let source = dir.path().join("source.json");
fs::write(&source, r#"{"api_key":"filevalue123"}"#).unwrap();
let source_url_path = source.to_str().unwrap().replace('\\', "/");
let lade_yml = format!(
"\"cmd\":\n VALUE: \"file://{}?query=.api_key\"\n",
source_url_path
);
fs::write(dir.path().join("lade.yml"), &lade_yml).unwrap();
common::lade(home.path())
.current_dir(dir.path())
.args(["set", "cmd"])
.assert()
.success()
.stdout(predicates::str::contains("export VALUE='filevalue123'"));
}
#[test]
fn test_inject_raw_value_reaches_child_process() {
let dir = tempdir().unwrap();
let home = tempdir().unwrap();
fs::write(
dir.path().join("lade.yml"),
"\"echo.*\":\n SECRET: injected_secret\n",
)
.unwrap();
common::lade(home.path())
.current_dir(dir.path())
.args(["inject", "--no-mask", "echo", "$SECRET"])
.assert()
.success()
.stdout(predicates::str::contains("injected_secret"));
}
#[test]
fn test_inject_masks_secret_in_output_by_default() {
let dir = tempdir().unwrap();
let home = tempdir().unwrap();
fs::write(
dir.path().join("lade.yml"),
"\"echo.*\":\n SECRET: mysecret42\n",
)
.unwrap();
let out = common::lade(home.path())
.current_dir(dir.path())
.args(["inject", "echo", "$SECRET"])
.assert()
.success()
.get_output()
.stdout
.clone();
let stdout = String::from_utf8_lossy(&out);
assert!(
!stdout.contains("mysecret42"),
"raw secret leaked into output: {stdout}"
);
assert!(
stdout.contains("${SECRET:-REDACTED}"),
"expected redaction token in output: {stdout}"
);
}
#[test]
fn test_inject_no_mask_shows_raw_secret() {
let dir = tempdir().unwrap();
let home = tempdir().unwrap();
fs::write(
dir.path().join("lade.yml"),
"\"echo.*\":\n SECRET: rawsecret99\n",
)
.unwrap();
common::lade(home.path())
.current_dir(dir.path())
.args(["inject", "--no-mask", "echo", "$SECRET"])
.assert()
.success()
.stdout(predicates::str::contains("rawsecret99"));
}
#[test]
fn test_inject_static_mask_format() {
let dir = tempdir().unwrap();
let home = tempdir().unwrap();
fs::write(
dir.path().join("lade.yml"),
"\"echo.*\":\n SECRET: statictest77\n",
)
.unwrap();
let out = common::lade(home.path())
.current_dir(dir.path())
.args(["inject", "--mask-format", "REDACTED", "echo", "$SECRET"])
.assert()
.success()
.get_output()
.stdout
.clone();
let stdout = String::from_utf8_lossy(&out);
assert!(
!stdout.contains("statictest77"),
"raw secret leaked: {stdout}"
);
assert!(stdout.contains("REDACTED"), "expected REDACTED: {stdout}");
assert!(
!stdout.contains("SECRET"),
"var name should not appear with static format: {stdout}"
);
}
#[test]
fn test_inject_stdin_escape_responses_do_not_leak_to_stdout() {
let dir = tempdir().unwrap();
let home = tempdir().unwrap();
fs::write(
dir.path().join("lade.yml"),
"\"echo.*\":\n SECRET: stdincheck42\n",
)
.unwrap();
let out = common::lade(home.path())
.current_dir(dir.path())
.args(["inject", "echo hello"])
.write_stdin("\x1b]11;rgb:1f1f/2424/2828\x1b\\\x1b[37;1R")
.assert()
.success()
.get_output()
.stdout
.clone();
let stdout = String::from_utf8_lossy(&out);
assert!(stdout.contains("hello"), "expected child output: {stdout}");
assert!(
!stdout.contains("\x1b]11;"),
"OSC 11 response leaked into stdout: {stdout:?}"
);
assert!(
!stdout.contains("\x1b[37;1R"),
"CPR response leaked into stdout: {stdout:?}"
);
}
#[test]
fn test_inject_exit_code_propagation() {
let dir = tempdir().unwrap();
let home = tempdir().unwrap();
common::lade(home.path())
.current_dir(dir.path())
.args(["inject", "exit 7"])
.assert()
.code(7);
}
#[test]
#[cfg(unix)]
fn test_inject_with_fake_vault_cli() {
let dir = tempdir().unwrap();
let home = tempdir().unwrap();
let fake_bin = tempdir().unwrap();
common::fake_cli(
&fake_bin,
"vault",
r#"echo '{"data":{"data":{"password":"vault_injected"}}}'"#,
);
fs::write(
dir.path().join("lade.yml"),
"\"vault.*\":\n PASSWORD: \"vault://localhost/secret/myapp/password\"\n",
)
.unwrap();
let new_path = format!(
"{}:{}",
fake_bin.path().display(),
std::env::var("PATH").unwrap_or_default()
);
common::lade(home.path())
.current_dir(dir.path())
.env("PATH", &new_path)
.args(["set", "vault cmd"])
.assert()
.success()
.stdout(predicates::str::contains(
"export PASSWORD='vault_injected'",
));
}