#[path = "common/mod.rs"]
mod common;
use common::run_fallow_raw;
use std::fs;
use std::process::Command;
fn init_temp_dir(suffix: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!(
"fallow-init-test-{}-{}",
std::process::id(),
suffix
));
if dir.exists() {
let _ = fs::remove_dir_all(&dir);
}
fs::create_dir_all(&dir).unwrap();
fs::write(
dir.join("package.json"),
r#"{"name": "init-test", "main": "index.ts"}"#,
)
.unwrap();
dir
}
fn cleanup(dir: &std::path::Path) {
let _ = fs::remove_dir_all(dir);
}
#[test]
fn init_creates_fallowrc_json() {
let dir = init_temp_dir("json");
let output = run_fallow_raw(&["init", "--root", dir.to_str().unwrap(), "--quiet"]);
assert_eq!(
output.code, 0,
"init should succeed, stderr: {}",
output.stderr
);
assert!(
dir.join(".fallowrc.json").exists(),
"init should create .fallowrc.json"
);
cleanup(&dir);
}
#[test]
fn init_creates_toml_with_flag() {
let dir = init_temp_dir("toml");
let output = run_fallow_raw(&["init", "--toml", "--root", dir.to_str().unwrap(), "--quiet"]);
assert_eq!(
output.code, 0,
"init --toml should succeed, stderr: {}",
output.stderr
);
assert!(
dir.join("fallow.toml").exists(),
"init --toml should create fallow.toml"
);
cleanup(&dir);
}
#[test]
fn init_exits_nonzero_if_config_exists() {
let dir = init_temp_dir("exists");
run_fallow_raw(&["init", "--root", dir.to_str().unwrap(), "--quiet"]);
let output = run_fallow_raw(&["init", "--root", dir.to_str().unwrap(), "--quiet"]);
assert_ne!(
output.code, 0,
"init should fail when config already exists"
);
cleanup(&dir);
}
#[test]
fn init_created_config_is_valid_json() {
let dir = init_temp_dir("valid");
run_fallow_raw(&["init", "--root", dir.to_str().unwrap(), "--quiet"]);
let content = fs::read_to_string(dir.join(".fallowrc.json")).unwrap();
let mut stripped = String::new();
std::io::Read::read_to_string(
&mut json_comments::StripComments::new(content.as_bytes()),
&mut stripped,
)
.unwrap_or_else(|e| panic!("init should produce valid JSONC: {e}\ncontent: {content}"));
let _: serde_json::Value = serde_json::from_str(&stripped)
.unwrap_or_else(|e| panic!("init should produce valid JSONC: {e}\ncontent: {content}"));
cleanup(&dir);
}
#[test]
fn hooks_namespace_installs_and_uninstalls_git_hook() {
let dir = init_temp_dir("hooks-namespace-git");
let git = Command::new("git")
.arg("init")
.arg("-q")
.current_dir(&dir)
.status()
.expect("git init should run");
assert!(git.success());
let root = dir.to_str().unwrap();
let install = run_fallow_raw(&[
"--root", root, "hooks", "install", "--target", "git", "--branch", "develop",
]);
assert_eq!(
install.code, 0,
"hooks install --target git should succeed, stderr: {}",
install.stderr
);
let hook_path = dir.join(".git/hooks/pre-commit");
let hook = fs::read_to_string(&hook_path).unwrap();
assert!(hook.contains("Generated by fallow hooks install --target git"));
assert!(hook.contains("BASE=\"develop\""));
let uninstall = run_fallow_raw(&["--root", root, "hooks", "uninstall", "--target", "git"]);
assert_eq!(
uninstall.code, 0,
"hooks uninstall --target git should succeed, stderr: {}",
uninstall.stderr
);
assert!(!hook_path.exists());
cleanup(&dir);
}
#[test]
fn hooks_namespace_agent_dry_run_uses_setup_hooks_engine() {
let dir = init_temp_dir("hooks-namespace-agent");
let output = run_fallow_raw(&[
"--root",
dir.to_str().unwrap(),
"hooks",
"install",
"--target",
"agent",
"--agent",
"claude",
"--dry-run",
]);
assert_eq!(
output.code, 0,
"hooks install --target agent should succeed, stderr: {}",
output.stderr
);
assert!(
output
.stderr
.contains("fallow hooks install --target agent (install) (dry run)"),
"expected hooks namespace summary, stderr: {}",
output.stderr
);
assert!(!dir.join(".claude").exists());
cleanup(&dir);
}
#[test]
fn hooks_namespace_validation_respects_json_format() {
let output = run_fallow_raw(&[
"--format", "json", "hooks", "install", "--target", "git", "--agent", "claude",
]);
assert_eq!(
output.code, 2,
"target mismatch should exit 2, stderr: {}",
output.stderr
);
assert!(
output.stderr.is_empty(),
"json errors should not emit human stderr: {}",
output.stderr
);
let json: serde_json::Value =
serde_json::from_str(&output.stdout).expect("stdout should be structured JSON");
assert_eq!(json["error"], true);
assert!(
json["message"]
.as_str()
.unwrap_or_default()
.contains("--agent, --user, and --gitignore-claude"),
"unexpected error payload: {json}"
);
}