use std::path::PathBuf;
use std::time::Duration;
use crate::command::{
ForgeCommand, ForgeCommandError, ForgeCommandOutput, ForgeCommandRunner,
RealForgeCommandRunner, command_output_detail,
};
#[test]
fn forge_command_builders_set_environment_and_working_directory() {
let working_directory = PathBuf::from("/tmp/repo");
let command = ForgeCommand::new("gh", vec!["pr".to_string(), "view".to_string()])
.with_environment("GH_TOKEN", "secret")
.with_working_directory(working_directory.clone());
assert_eq!(command.executable, "gh");
assert_eq!(command.arguments, vec!["pr", "view"]);
assert_eq!(
command.environment,
vec![("GH_TOKEN".to_string(), "secret".to_string())]
);
assert_eq!(command.working_directory, Some(working_directory));
}
#[test]
fn forge_command_optional_working_directory_leaves_command_unchanged_when_missing() {
let command = ForgeCommand::new("glab", vec!["mr".to_string(), "view".to_string()]);
let command = command.with_optional_working_directory(None);
assert_eq!(command.working_directory, None);
}
#[test]
fn forge_command_output_success_requires_zero_exit_code() {
let successful_output = ForgeCommandOutput {
exit_code: Some(0),
stderr: String::new(),
stdout: String::new(),
};
let failed_output = ForgeCommandOutput {
exit_code: Some(1),
stderr: String::new(),
stdout: String::new(),
};
let signaled_output = ForgeCommandOutput {
exit_code: None,
stderr: String::new(),
stdout: String::new(),
};
let successful = successful_output.success();
let failed = failed_output.success();
let signaled = signaled_output.success();
assert!(successful);
assert!(!failed);
assert!(!signaled);
}
#[tokio::test]
async fn real_runner_captures_stdout_stderr_and_exit_code() {
let command = ForgeCommand::new(
"sh",
vec![
"-c".to_string(),
"printf '%s' \"$FORGE_STDOUT\"; printf '%s' err >&2; exit 7".to_string(),
],
)
.with_environment("FORGE_STDOUT", "out");
let runner = RealForgeCommandRunner;
let output = runner
.run(command)
.await
.expect("shell command should run and return captured output");
assert_eq!(
output,
ForgeCommandOutput {
exit_code: Some(7),
stderr: "err".to_string(),
stdout: "out".to_string(),
}
);
}
#[tokio::test]
async fn real_runner_reports_missing_executable() {
let command = ForgeCommand::new(
"agentty-definitely-missing-forge-command",
vec!["version".to_string()],
);
let runner = RealForgeCommandRunner;
let error = runner
.run(command)
.await
.expect_err("missing executable should be reported before output capture");
assert_eq!(
error,
ForgeCommandError::ExecutableNotFound {
executable: "agentty-definitely-missing-forge-command".to_string(),
}
);
}
#[tokio::test]
async fn command_timeout_cancels_long_running_process() {
let command = ForgeCommand::new("sh", vec!["-c".to_string(), "exec sleep 5".to_string()]);
let timeout = Duration::from_millis(25);
let error = RealForgeCommandRunner::run_command_with_timeout(command, timeout)
.await
.expect_err("long-running command should time out");
assert_eq!(
error,
ForgeCommandError::TimedOut {
executable: "sh".to_string(),
timeout,
}
);
}
#[test]
fn command_output_detail_prefers_trimmed_stderr_then_stdout() {
let stderr_output = ForgeCommandOutput {
exit_code: Some(1),
stderr: " stderr detail\n".to_string(),
stdout: "stdout detail".to_string(),
};
let stdout_output = ForgeCommandOutput {
exit_code: Some(1),
stderr: " \n".to_string(),
stdout: " stdout detail\n".to_string(),
};
let stderr_detail = command_output_detail(&stderr_output);
let stdout_detail = command_output_detail(&stdout_output);
assert_eq!(stderr_detail, "stderr detail");
assert_eq!(stdout_detail, "stdout detail");
}
#[test]
fn command_output_detail_falls_back_when_output_is_blank() {
let output = ForgeCommandOutput {
exit_code: Some(1),
stderr: " \n".to_string(),
stdout: "\t".to_string(),
};
let detail = command_output_detail(&output);
assert_eq!(detail, "Unknown forge CLI error");
}
#[tokio::test]
async fn real_runner_applies_working_directory() {
let working_directory = std::env::current_dir().expect("test directory should exist");
let command = ForgeCommand::new("sh", vec!["-c".to_string(), "pwd -P".to_string()])
.with_working_directory(working_directory.clone());
let runner = RealForgeCommandRunner;
let output = runner.run(command).await.expect("shell should run");
assert!(output.success());
assert_eq!(PathBuf::from(output.stdout.trim()), working_directory);
}
#[tokio::test]
async fn real_runner_reports_non_executable_path() {
let command = ForgeCommand::new(".", Vec::new());
let runner = RealForgeCommandRunner;
let error = runner
.run(command)
.await
.expect_err("directory cannot execute");
assert!(
matches!(error, ForgeCommandError::SpawnFailed { executable, message }
if executable == "." && !message.is_empty())
);
}