#![allow(
clippy::missing_docs_in_private_items,
reason = "test helpers and fixtures do not need doc comments"
)]
use super::*;
use std::collections::HashMap;
use std::fmt::Write as _;
fn with_home_override(body: impl FnOnce(&std::path::Path)) {
let dir = std::env::temp_dir().join(format!("moadim-cmd-env-{}", uuid::Uuid::new_v4()));
std::fs::create_dir_all(&dir).unwrap();
let previous = std::env::var_os("MOADIM_HOME_OVERRIDE");
unsafe {
std::env::set_var("MOADIM_HOME_OVERRIDE", &dir);
}
body(&dir);
unsafe {
match previous {
Some(prev) => std::env::set_var("MOADIM_HOME_OVERRIDE", prev),
None => std::env::remove_var("MOADIM_HOME_OVERRIDE"),
}
}
let _ = std::fs::remove_dir_all(&dir);
}
fn write_local_env(slug: &str, entries: &[(&str, &str)]) {
let path = crate::paths::routine_local_toml_path(slug);
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
let mut body = String::from("[env]\n");
for (key, value) in entries {
let _ = writeln!(body, "{key} = {value:?}");
}
std::fs::write(&path, body).unwrap();
}
fn test_agent() -> AgentCommand {
AgentCommand {
command: "claude".to_string(),
args: vec![],
instructions_file: "CLAUDE.md".to_string(),
setup: None,
}
}
#[test]
fn build_routine_command_exports_tracked_env_vars() {
with_home_override(|_| {
let mut routine = make_routine("Cmd Env Tracked Routine");
routine.env = HashMap::from([("MY_VAR".to_string(), "hello world".to_string())]);
let cmd = build_routine_command(&routine, &test_agent(), TriggerSource::Scheduled);
assert!(
cmd.contains(r#"export MY_VAR="$MOADIM_ROUTINE_ENV__MY_VAR""#),
"expected tracked env var's indirection export in: {cmd}"
);
assert_eq!(
routine_env_carrier_vars(&routine).get("MOADIM_ROUTINE_ENV__MY_VAR"),
Some(&"hello world".to_string())
);
});
}
#[test]
fn build_routine_command_never_inlines_a_secret_value() {
const SENTINEL: &str = "sk-live-sentinel-1515-do-not-leak";
with_home_override(|_| {
let mut routine = make_routine("Cmd Env Secret Not Inlined Routine");
let slug = slugify(&routine.title);
write_local_env(&slug, &[("SECRET_TOKEN", SENTINEL)]);
routine.env = HashMap::new();
let cmd = build_routine_command(&routine, &test_agent(), TriggerSource::Scheduled);
assert!(
!cmd.contains(SENTINEL),
"secret value must never be inlined into the command string, in: {cmd}"
);
assert!(
cmd.contains(r#"export SECRET_TOKEN="$MOADIM_ROUTINE_ENV__SECRET_TOKEN""#),
"expected the indirection export by real name in: {cmd}"
);
assert_eq!(
routine_env_carrier_vars(&routine).get("MOADIM_ROUTINE_ENV__SECRET_TOKEN"),
Some(&SENTINEL.to_string()),
"the value must still reach the spawned process, just via the carrier env var"
);
});
}
#[test]
fn build_routine_command_env_exports_precede_ts_and_follow_path() {
with_home_override(|_| {
let mut routine = make_routine("Cmd Env Order Routine");
routine.env = HashMap::from([("ORDER_VAR".to_string(), "1".to_string())]);
let cmd = build_routine_command(&routine, &test_agent(), TriggerSource::Scheduled);
let path_pos = cmd.find("export PATH=$PATH:").unwrap();
let env_pos = cmd.find("export ORDER_VAR=").unwrap();
let ts_pos = cmd.find(r#"TS="$(date +%s)""#).unwrap();
assert!(
path_pos < env_pos && env_pos < ts_pos,
"expected PATH export < env export < TS= in: {cmd}"
);
});
}
#[test]
fn build_routine_command_local_toml_overrides_tracked_env_for_the_same_key() {
with_home_override(|_| {
let mut routine = make_routine("Cmd Env Override Routine");
let slug = slugify(&routine.title);
routine.env = HashMap::from([("SHARED_KEY".to_string(), "tracked-value".to_string())]);
write_local_env(&slug, &[("SHARED_KEY", "secret-value")]);
let cmd = build_routine_command(&routine, &test_agent(), TriggerSource::Scheduled);
assert!(
cmd.contains(r#"export SHARED_KEY="$MOADIM_ROUTINE_ENV__SHARED_KEY""#),
"expected the indirection export by real name in: {cmd}"
);
assert_eq!(
routine_env_carrier_vars(&routine).get("MOADIM_ROUTINE_ENV__SHARED_KEY"),
Some(&"secret-value".to_string()),
"expected routine.local.toml's value to win"
);
});
}
#[test]
fn build_routine_command_merges_local_and_tracked_env_for_distinct_keys() {
with_home_override(|_| {
let mut routine = make_routine("Cmd Env Merge Routine");
let slug = slugify(&routine.title);
routine.env = HashMap::from([("TRACKED_ONLY".to_string(), "a".to_string())]);
write_local_env(&slug, &[("LOCAL_ONLY", "b")]);
let cmd = build_routine_command(&routine, &test_agent(), TriggerSource::Scheduled);
assert!(
cmd.contains(r#"export TRACKED_ONLY="$MOADIM_ROUTINE_ENV__TRACKED_ONLY""#),
"in: {cmd}"
);
assert!(
cmd.contains(r#"export LOCAL_ONLY="$MOADIM_ROUTINE_ENV__LOCAL_ONLY""#),
"in: {cmd}"
);
let carriers = routine_env_carrier_vars(&routine);
assert_eq!(
carriers.get("MOADIM_ROUTINE_ENV__TRACKED_ONLY"),
Some(&"a".to_string())
);
assert_eq!(
carriers.get("MOADIM_ROUTINE_ENV__LOCAL_ONLY"),
Some(&"b".to_string())
);
});
}
#[path = "command_env_validation_tests.rs"]
mod command_env_validation_tests;