use std::path::PathBuf;
use std::process::Command;
fn dcg_binary() -> PathBuf {
if let Some(path) = std::env::var_os("CARGO_BIN_EXE_dcg") {
return PathBuf::from(path);
}
let mut path = std::env::current_exe().unwrap();
path.pop(); path.pop(); path.push("dcg");
path
}
fn run_dcg(args: &[&str]) -> (String, String, i32) {
let binary = dcg_binary();
let output = Command::new(&binary)
.args(args)
.output()
.unwrap_or_else(|e| panic!("failed to run dcg at {}: {}", binary.display(), e));
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
let exit_code = output.status.code().unwrap_or(-1);
(stdout, stderr, exit_code)
}
fn run_dcg_with_env(args: &[&str], key: &str, value: &str) -> (String, String, i32) {
let binary = dcg_binary();
let output = Command::new(&binary)
.args(args)
.env(key, value)
.output()
.unwrap_or_else(|e| panic!("failed to run dcg at {}: {}", binary.display(), e));
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
let exit_code = output.status.code().unwrap_or(-1);
(stdout, stderr, exit_code)
}
#[test]
fn test_robot_flag_enables_json_output() {
let (stdout, _stderr, exit_code) = run_dcg(&["--robot", "test", "git status"]);
assert_eq!(exit_code, 0, "robot mode should exit 0 for allowed command");
let json: serde_json::Value =
serde_json::from_str(&stdout).expect("robot mode should produce valid JSON");
assert!(json.is_object(), "robot mode should output JSON object");
assert!(json.get("command").is_some(), "should have command field");
assert!(json.get("decision").is_some(), "should have decision field");
}
#[test]
fn test_robot_flag_denied_command_exit_code() {
let (stdout, _stderr, exit_code) = run_dcg(&["--robot", "test", "git reset --hard"]);
assert_eq!(exit_code, 1, "robot mode should exit 1 for denied command");
let json: serde_json::Value =
serde_json::from_str(&stdout).expect("robot mode should produce valid JSON");
assert_eq!(json["decision"], "deny", "decision should be deny");
}
#[test]
fn test_robot_flag_no_ansi_codes() {
let (stdout, stderr, _) = run_dcg(&["--robot", "test", "git reset --hard"]);
assert!(
!stdout.contains("\x1b["),
"robot mode stdout should not contain ANSI codes\nstdout: {stdout}"
);
assert!(
!stderr.contains("\x1b["),
"robot mode stderr should not contain ANSI codes\nstderr: {stderr}"
);
}
#[test]
fn test_robot_flag_silent_stderr() {
let (_stdout, stderr, _) = run_dcg(&["--robot", "test", "git reset --hard"]);
assert!(
!stderr.contains("â•") && !stderr.contains("â•°") && !stderr.contains("│"),
"robot mode should not have box-drawing characters in stderr\nstderr: {stderr}"
);
}
#[test]
fn test_dcg_robot_env_enables_json_output() {
let (stdout, _stderr, exit_code) = run_dcg_with_env(&["test", "git status"], "DCG_ROBOT", "1");
assert_eq!(
exit_code, 0,
"DCG_ROBOT=1 should exit 0 for allowed command"
);
let json: serde_json::Value =
serde_json::from_str(&stdout).expect("DCG_ROBOT=1 should produce valid JSON");
assert!(json.is_object(), "DCG_ROBOT=1 should output JSON object");
}
#[test]
fn test_dcg_robot_env_denied_exit_code() {
let (_stdout, _stderr, exit_code) =
run_dcg_with_env(&["test", "git reset --hard"], "DCG_ROBOT", "1");
assert_eq!(exit_code, 1, "DCG_ROBOT=1 should exit 1 for denied command");
}
#[test]
fn test_dcg_robot_env_no_ansi_codes() {
let (stdout, stderr, _) = run_dcg_with_env(&["test", "git reset --hard"], "DCG_ROBOT", "1");
assert!(
!stdout.contains("\x1b["),
"DCG_ROBOT=1 stdout should not contain ANSI codes"
);
assert!(
!stderr.contains("\x1b["),
"DCG_ROBOT=1 stderr should not contain ANSI codes"
);
}
#[test]
fn test_robot_mode_json_has_agent_info() {
let (stdout, _stderr, _) = run_dcg(&["--robot", "test", "git reset --hard"]);
let json: serde_json::Value = serde_json::from_str(&stdout).expect("should produce valid JSON");
if let Some(agent) = json.get("agent") {
assert!(agent.is_object(), "agent should be an object");
}
}
#[test]
fn test_robot_mode_json_has_severity() {
let (stdout, _stderr, _) = run_dcg(&["--robot", "test", "git reset --hard"]);
let json: serde_json::Value = serde_json::from_str(&stdout).expect("should produce valid JSON");
if json["decision"] == "deny" {
assert!(
json.get("severity").is_some(),
"denied commands should include severity"
);
}
}
#[test]
fn test_robot_mode_json_has_rule_id() {
let (stdout, _stderr, _) = run_dcg(&["--robot", "test", "git reset --hard"]);
let json: serde_json::Value = serde_json::from_str(&stdout).expect("should produce valid JSON");
if json["decision"] == "deny" {
assert!(
json.get("rule_id").is_some(),
"denied commands should include rule_id"
);
assert!(
json.get("pack_id").is_some(),
"denied commands should include pack_id"
);
}
}
#[test]
fn test_robot_mode_exit_0_allowed() {
let safe_commands = ["ls -la", "git status", "echo hello", "cat /etc/hosts"];
for cmd in safe_commands {
let (_stdout, _stderr, exit_code) = run_dcg(&["--robot", "test", cmd]);
assert_eq!(
exit_code, 0,
"robot mode should exit 0 for allowed command: {cmd}"
);
}
}
#[test]
fn test_robot_mode_exit_1_denied() {
let dangerous_commands = [
"git reset --hard",
"git clean -fd",
"rm -rf /",
"git push --force origin main",
];
for cmd in dangerous_commands {
let (_stdout, _stderr, exit_code) = run_dcg(&["--robot", "test", cmd]);
assert_eq!(
exit_code, 1,
"robot mode should exit 1 for denied command: {cmd}"
);
}
}
#[test]
fn test_robot_mode_vs_hook_mode_exit_codes() {
let (_stdout, _stderr, robot_exit) = run_dcg(&["--robot", "test", "git reset --hard"]);
assert_eq!(robot_exit, 1, "robot mode denied should exit 1");
let (_stdout, _stderr, robot_exit) = run_dcg(&["--robot", "test", "git status"]);
assert_eq!(robot_exit, 0, "robot mode allowed should exit 0");
}
#[test]
fn test_robot_mode_explain_command() {
let (stdout, _stderr, exit_code) = run_dcg(&["--robot", "explain", "git reset --hard"]);
assert_eq!(exit_code, 0, "robot mode explain should exit 0");
let json: serde_json::Value =
serde_json::from_str(&stdout).expect("robot mode explain should produce valid JSON");
assert!(json.is_object(), "explain should output JSON object");
}
#[test]
fn test_robot_mode_packs_command() {
let (stdout, _stderr, exit_code) = run_dcg(&["--robot", "packs"]);
assert_eq!(exit_code, 0, "robot mode packs should exit 0");
let json: serde_json::Value =
serde_json::from_str(&stdout).expect("robot mode packs should produce valid JSON");
assert!(json.get("packs").is_some(), "should have packs array");
}
#[test]
fn test_robot_mode_empty_command() {
let (stdout, _stderr, exit_code) = run_dcg(&["--robot", "test", ""]);
assert!(
exit_code == 0 || exit_code == 4,
"empty command should exit 0 (allow) or 4 (parse error), got: {exit_code}"
);
if !stdout.trim().is_empty() {
let _: serde_json::Value =
serde_json::from_str(&stdout).expect("output should be valid JSON");
}
}
#[test]
fn test_robot_mode_whitespace_command() {
let (stdout, _stderr, exit_code) = run_dcg(&["--robot", "test", " "]);
assert!(
exit_code == 0 || exit_code == 4,
"whitespace command should exit 0 or 4, got: {exit_code}"
);
if !stdout.trim().is_empty() {
let _: serde_json::Value =
serde_json::from_str(&stdout).expect("output should be valid JSON");
}
}
#[test]
fn test_robot_mode_complex_command() {
let (stdout, _stderr, exit_code) = run_dcg(&[
"--robot",
"test",
"cat file.txt | grep pattern > output.txt",
]);
assert!(
exit_code == 0 || exit_code == 1,
"complex command should exit 0 or 1, got: {exit_code}"
);
let json: serde_json::Value = serde_json::from_str(&stdout).expect("should produce valid JSON");
assert!(json.is_object(), "should be JSON object");
}
#[test]
fn test_robot_flag_and_env_produce_same_result() {
let cmd = "git reset --hard";
let (stdout_flag, _stderr_flag, exit_flag) = run_dcg(&["--robot", "test", cmd]);
let (stdout_env, _stderr_env, exit_env) = run_dcg_with_env(&["test", cmd], "DCG_ROBOT", "1");
assert_eq!(
exit_flag, exit_env,
"--robot flag and DCG_ROBOT=1 should have same exit code"
);
let json_flag: serde_json::Value =
serde_json::from_str(&stdout_flag).expect("--robot should produce valid JSON");
let json_env: serde_json::Value =
serde_json::from_str(&stdout_env).expect("DCG_ROBOT=1 should produce valid JSON");
assert_eq!(
json_flag["decision"], json_env["decision"],
"decision should match between flag and env var"
);
}