#![allow(
clippy::missing_docs_in_private_items,
reason = "test helpers and fixtures do not need doc comments"
)]
use super::*;
use crate::routines::model::Routine;
fn make_routine(title: &str) -> Routine {
Routine {
model: None,
id: "cmd-test-id".to_string(),
schedule: "@daily".to_string(),
title: title.to_string(),
agent: "claude".to_string(),
prompt: "do it".to_string(),
goal: None,
repositories: vec![],
machines: vec![crate::machine::current_machine()],
enabled: true,
source: "managed".to_string(),
created_at: 0,
updated_at: 0,
last_manual_trigger_at: None,
last_scheduled_trigger_at: None,
snoozed_until: None,
skip_runs: None,
power_saving: false,
tags: vec![],
ttl_secs: None,
max_runtime_secs: None,
env: std::collections::HashMap::new(),
}
}
fn with_path(value: &std::path::Path, body: impl FnOnce()) {
let saved = std::env::var_os("PATH");
unsafe {
std::env::set_var("PATH", value);
}
body();
unsafe {
match saved {
Some(prev) => std::env::set_var("PATH", prev),
None => std::env::remove_var("PATH"),
}
}
}
#[test]
fn build_routine_command_resolves_bin_dir_when_tool_on_path() {
let dir = std::env::temp_dir().join(format!("moadim-cmd-path-{}", uuid::Uuid::new_v4()));
std::fs::create_dir_all(&dir).unwrap();
let tmux = dir.join("tmux");
std::fs::write(&tmux, "#!/bin/sh\n").unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt as _;
std::fs::set_permissions(&tmux, std::fs::Permissions::from_mode(0o755)).unwrap();
}
let dir_str = dir.to_string_lossy().into_owned();
with_path(&dir, || {
let routine = make_routine("Cmd Path Routine");
let agent = AgentCommand {
command: "claude".to_string(),
args: vec![],
instructions_file: "CLAUDE.md".to_string(),
setup: None,
};
let cmd = build_routine_command(&routine, &agent, TriggerSource::Scheduled);
assert!(
cmd.contains(&dir_str),
"expected resolved tmux dir {dir_str} in: {cmd}"
);
});
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn build_routine_command_extends_path_rather_than_replacing_it() {
let routine = make_routine("Path Extend Routine");
let agent = AgentCommand {
command: "claude".to_string(),
args: vec![],
instructions_file: "CLAUDE.md".to_string(),
setup: None,
};
let cmd = build_routine_command(&routine, &agent, TriggerSource::Scheduled);
assert!(
cmd.contains("export PATH=$PATH:"),
"expected PATH to extend the profile's $PATH, not replace it, in: {cmd}"
);
}
#[test]
fn build_routine_command_writes_daemon_preamble_before_prompt_copy() {
let routine = make_routine("Cmd Preamble Guard Routine");
let agent = AgentCommand {
command: "claude".to_string(),
args: vec![],
instructions_file: "CLAUDE.md".to_string(),
setup: None,
};
let cmd = build_routine_command(&routine, &agent, TriggerSource::Scheduled);
let write = cmd.find(r#"> "$WB/CLAUDE.md" || {"#).unwrap();
assert!(
cmd.contains(
r#"> "$WB/CLAUDE.md" || { echo "moadim: failed to write agent instructions preamble; aborting launch" | tee -a "$WB/agent.log" >&2; exit 1; }"#
),
"expected the CLAUDE.md preamble write to fail-fast in: {cmd}"
);
let copy = cmd.find("/prompt.md\"").unwrap();
assert!(
write < copy,
"preamble-write guard must precede the prompt copy"
);
assert!(
cmd.contains(r#">> "$WB/CLAUDE.md" || true"#),
"user-prompt append must remain best-effort in: {cmd}"
);
}
#[test]
fn build_routine_command_workbench_base_tracks_moadim_home_override() {
let dir = std::env::temp_dir().join(format!("moadim-cmd-home-{}", 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);
}
let expected_base = crate::paths::workbenches_dir()
.to_string_lossy()
.into_owned();
let routine = make_routine("Cmd Workbench Base Routine");
let agent = AgentCommand {
command: "claude".to_string(),
args: vec![],
instructions_file: "CLAUDE.md".to_string(),
setup: None,
};
let cmd = build_routine_command(&routine, &agent, TriggerSource::Scheduled);
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);
assert!(
cmd.contains(&format!(
r#"WB={}/"$SLUG-$RID""#,
shell_quote(&expected_base)
)),
"expected WB base derived from paths::workbenches_dir() ({expected_base}) in: {cmd}"
);
assert!(
!cmd.contains(r#"WB="$HOME/.moadim/workbenches"#),
"expected the hardcoded $HOME/.moadim/workbenches literal to be gone, got: {cmd}"
);
}
#[test]
fn cron_path_falls_back_to_root_home_when_home_unset() {
let saved = std::env::var_os("HOME");
unsafe {
std::env::remove_var("HOME");
}
let path = cron_path("definitely-not-a-real-binary-xyz");
assert!(
path.contains("/root/.local/bin"),
"expected /root-anchored fallback dirs in: {path}"
);
unsafe {
match saved {
Some(prev) => std::env::set_var("HOME", prev),
None => std::env::remove_var("HOME"),
}
}
}
#[test]
fn build_routine_command_guards_agent_setup_step() {
let routine = make_routine("Setup Routine");
let agent = AgentCommand {
command: "claude".to_string(),
args: vec![],
setup: Some("python3 seed.py".to_string()),
instructions_file: "CLAUDE.md".to_string(),
};
let cmd = build_routine_command(&routine, &agent, TriggerSource::Scheduled);
assert!(
cmd.contains(
r#"{ python3 seed.py; } || { echo "moadim: agent setup failed; aborting launch" | tee -a "$WB/agent.log" >&2; exit 1; }"#
),
"expected guarded setup step in: {cmd}"
);
let setup_pos = cmd.find("agent setup failed").unwrap();
let tmux_pos = cmd.find("tmux new-session").unwrap();
assert!(
setup_pos < tmux_pos,
"setup guard must precede tmux new-session in: {cmd}"
);
}
#[test]
fn build_routine_command_omits_setup_guard_when_no_setup() {
let routine = make_routine("No Setup Routine");
let agent = AgentCommand {
command: "claude".to_string(),
args: vec![],
setup: None,
instructions_file: "CLAUDE.md".to_string(),
};
let cmd = build_routine_command(&routine, &agent, TriggerSource::Scheduled);
assert!(
!cmd.contains("agent setup failed"),
"did not expect a setup guard with no setup step in: {cmd}"
);
}
#[test]
fn slugify_preserves_folder_segments() {
assert_eq!(slugify("ops/nightly triage"), "ops/nightly-triage");
assert_eq!(slugify("///ops///nightly triage///"), "ops/nightly-triage");
}
#[test]
fn build_routine_command_appends_model_override() {
let mut routine = make_routine("Cmd Model Routine");
routine.model = Some("claude-sonnet-4-6".to_string());
let agent = AgentCommand {
command: "claude".to_string(),
args: vec!["--permission-mode".to_string(), "auto".to_string()],
instructions_file: "CLAUDE.md".to_string(),
setup: None,
};
let cmd = build_routine_command(&routine, &agent, TriggerSource::Scheduled);
let args_pos = cmd.find("--permission-mode auto").unwrap();
let model_pos = cmd.find("--model").unwrap();
assert!(
model_pos > args_pos,
"expected --model after the agent's own args in: {cmd}"
);
assert!(
cmd[model_pos..].contains("claude-sonnet-4-6"),
"expected model id after --model in: {cmd}"
);
}
#[test]
fn build_routine_command_omits_model_flag_when_unset() {
let routine = make_routine("Cmd No Model Routine");
let agent = AgentCommand {
command: "claude".to_string(),
args: vec![],
instructions_file: "CLAUDE.md".to_string(),
setup: None,
};
let cmd = build_routine_command(&routine, &agent, TriggerSource::Scheduled);
assert!(
!cmd.contains("--model"),
"expected no --model flag in: {cmd}"
);
}
#[test]
fn tmux_session_prefix_matches_the_sess_line_build_routine_command_emits() {
let routine = make_routine("Cmd Session Prefix Routine");
let agent = AgentCommand {
command: "claude".to_string(),
args: vec![],
instructions_file: "CLAUDE.md".to_string(),
setup: None,
};
let cmd = build_routine_command(&routine, &agent, TriggerSource::Scheduled);
assert!(
cmd.contains(&format!(r#"SESS="{TMUX_SESSION_PREFIX}$SLUG-$RID""#)),
"expected SESS line built from TMUX_SESSION_PREFIX in: {cmd}"
);
let slug = slugify(&routine.title);
assert_eq!(
tmux_session_prefix(&slug),
format!("{TMUX_SESSION_PREFIX}{slug}-")
);
}
#[test]
fn build_routine_command_records_exit_code_after_invocation() {
let routine = make_routine("Cmd Exit Code Routine");
let agent = AgentCommand {
command: "claude".to_string(),
args: vec!["--permission-mode".to_string(), "auto".to_string()],
instructions_file: "CLAUDE.md".to_string(),
setup: None,
};
let cmd = build_routine_command(&routine, &agent, TriggerSource::Scheduled);
let new_session_pos = cmd.find("tmux new-session").unwrap();
let invocation_pos = cmd.find("--permission-mode auto").unwrap();
let printf_pos = invocation_pos + cmd[invocation_pos..].find("printf").unwrap();
let exit_code_pos = cmd.rfind("> exit_code").unwrap();
assert!(
new_session_pos < invocation_pos
&& invocation_pos < printf_pos
&& printf_pos < exit_code_pos,
"expected exit-code capture after the invocation inside tmux new-session in: {cmd}"
);
assert!(cmd.contains(r#""$?""#), "expected $? capture in: {cmd}");
assert!(
!cmd.contains("$WB/exit_code"),
"exit_code must be workbench-relative, not $WB-prefixed, since $WB isn't exported: {cmd}"
);
}
#[test]
fn build_routine_command_attaches_pipe_pane_in_the_same_tmux_invocation() {
let routine = make_routine("Cmd Log Capture Routine");
let agent = AgentCommand {
command: "claude".to_string(),
args: vec![],
instructions_file: "CLAUDE.md".to_string(),
setup: None,
};
let cmd = build_routine_command(&routine, &agent, TriggerSource::Scheduled);
let new_session_pos = cmd.find("tmux new-session").unwrap();
assert!(
!cmd.contains("tmux pipe-pane"),
"pipe-pane must not be a standalone tmux invocation, but chained onto new-session: {cmd}"
);
let pipe_pane_pos = cmd.find("pipe-pane").unwrap();
let next_tmux_or_end = cmd[new_session_pos + 1..]
.find("tmux ")
.map_or(cmd.len(), |offset| new_session_pos + 1 + offset);
assert!(
new_session_pos < pipe_pane_pos && pipe_pane_pos < next_tmux_or_end,
"expected pipe-pane chained via \\; inside the same tmux new-session invocation in: {cmd}"
);
assert!(
cmd.contains(r#"\; pipe-pane -o -t "$SESS""#),
"expected pipe-pane chained with tmux's own \\; separator, targeting $SESS, in: {cmd}"
);
}
#[test]
fn inline_prompt_overflow_none_for_prompt_file_agent_regardless_of_size() {
let mut routine = make_routine("Cmd Overflow Prompt File Routine");
routine.prompt = "x".repeat(MAX_INLINE_PROMPT_BYTES * 2);
let agent = AgentCommand {
command: "codex".to_string(),
args: vec!["exec".to_string(), "{prompt_file}".to_string()],
instructions_file: "AGENTS.md".to_string(),
setup: None,
};
assert_eq!(inline_prompt_overflow(&routine, &agent), None);
}
#[test]
fn inline_prompt_overflow_none_when_composed_prompt_fits() {
let routine = make_routine("Cmd Overflow Small Routine");
let agent = AgentCommand {
command: "claude".to_string(),
args: vec![
"--permission-mode".to_string(),
"auto".to_string(),
"{prompt}".to_string(),
],
instructions_file: "CLAUDE.md".to_string(),
setup: None,
};
assert_eq!(inline_prompt_overflow(&routine, &agent), None);
}
#[test]
fn inline_prompt_overflow_some_when_composed_prompt_exceeds_inline_limit() {
let mut routine = make_routine("Cmd Overflow Large Routine");
routine.prompt = "x".repeat(MAX_INLINE_PROMPT_BYTES * 2);
let agent = AgentCommand {
command: "claude".to_string(),
args: vec![
"--permission-mode".to_string(),
"auto".to_string(),
"{prompt}".to_string(),
],
instructions_file: "CLAUDE.md".to_string(),
setup: None,
};
let overflow = inline_prompt_overflow(&routine, &agent);
assert_eq!(overflow, Some(compose_prompt(&routine).len()));
assert!(overflow.unwrap() > MAX_INLINE_PROMPT_BYTES);
}
#[path = "command_run_id_tests.rs"]
mod command_run_id_tests;
#[path = "command_umask_tests.rs"]
mod command_umask_tests;
#[path = "command_trigger_source_tests.rs"]
mod command_trigger_source_tests;
#[path = "command_env_tests.rs"]
mod command_env_tests;