use similar_asserts::assert_eq;
use zenops::{Cmd, error::Error, init::InitError};
use test_env::{Entry, paths};
mod test_env;
const MINIMAL_CONFIG: &str = r#"
[shell]
type = "bash"
[shell.environment]
[shell.alias]
"#;
fn init_cmd(url: &str) -> Cmd {
Cmd::Init {
url: Some(url.to_string()),
branch: None,
apply: false,
yes: false,
}
}
fn bootstrap_cmd() -> Cmd {
Cmd::Init {
url: None,
branch: None,
apply: false,
yes: false,
}
}
#[test]
fn init_clones_into_empty_existing_dir() {
let env = test_env::TestEnv::load();
let bare = env.seed_bare_repo(&[("config.toml", MINIMAL_CONFIG)]);
env.run(&init_cmd(bare.to_str().unwrap()))
.expect("init should succeed into empty existing dir");
let config_path = env.resolve_path(paths::ZENOPS_CONFIG);
assert!(config_path.exists(), "config.toml should exist after init");
let git_dir = env.resolve_path(paths::ZENOPS_DIR).join(".git");
assert!(git_dir.exists(), ".git should exist after init");
}
#[test]
fn init_clones_into_nonexistent_dir() {
let env = test_env::TestEnv::load();
env.delete_dir_all(paths::ZENOPS_DIR);
let bare = env.seed_bare_repo(&[("config.toml", MINIMAL_CONFIG)]);
env.run(&init_cmd(bare.to_str().unwrap()))
.expect("init should succeed into nonexistent dir");
let config_path = env.resolve_path(paths::ZENOPS_CONFIG);
assert!(config_path.exists(), "config.toml should exist after init");
}
#[test]
fn init_refuses_nonempty_dir() {
let env = test_env::TestEnv::load();
env.write_zenops_file(
zenops_safe_relative_path::srpath!("leftover"),
"stale\n",
None,
);
let bare = env.seed_bare_repo(&[("config.toml", MINIMAL_CONFIG)]);
let result = env.run(&init_cmd(bare.to_str().unwrap()));
assert_eq!(
result,
Err(Error::Init(InitError::DirNotEmpty(
env.resolve_path(paths::ZENOPS_DIR)
))),
);
}
#[test]
fn init_rejects_repo_without_config_toml() {
let env = test_env::TestEnv::load();
let bare = env.seed_bare_repo(&[("README.md", "no config here\n")]);
let result = env.run(&init_cmd(bare.to_str().unwrap()));
assert_eq!(
result,
Err(Error::Init(InitError::NoConfigToml(
env.resolve_path(paths::ZENOPS_DIR)
))),
);
let readme = env.resolve_path(paths::ZENOPS_DIR).join("README.md");
assert!(
readme.exists(),
"clone should be left in place for inspection"
);
}
#[test]
fn init_bootstrap_refuses_existing_empty_dir() {
let env = test_env::TestEnv::load();
let result = env.run(&bootstrap_cmd());
assert_eq!(
result,
Err(Error::Init(InitError::DirExists(
env.resolve_path(paths::ZENOPS_DIR)
))),
);
}
#[test]
fn init_bootstrap_refuses_existing_git_dir() {
let env = test_env::TestEnv::load();
env.create_dir(zenops_safe_relative_path::srpath!(
"home/bob/.config/zenops/.git"
));
let result = env.run(&bootstrap_cmd());
assert_eq!(
result,
Err(Error::Init(InitError::GitDirExists(
env.resolve_path(paths::ZENOPS_DIR)
))),
);
}
#[test]
fn init_bootstrap_needs_tty_when_dir_is_clear() {
let env = test_env::TestEnv::load();
env.delete_dir_all(paths::ZENOPS_DIR);
let result = env.run(&bootstrap_cmd());
assert_eq!(result, Err(Error::Init(InitError::NeedsTty)));
}
#[test]
fn init_with_apply_yes_runs_apply() {
let env = test_env::TestEnv::load();
let bare = env.seed_bare_repo(&[("config.toml", MINIMAL_CONFIG)]);
env.run(&Cmd::Init {
url: Some(bare.to_str().unwrap().to_string()),
branch: None,
apply: true,
yes: true,
})
.expect("init --apply --yes should succeed");
let profile = env.resolve_path(zenops_safe_relative_path::srpath!(
"home/bob/.zenops_bash_profile"
));
assert!(
profile.exists(),
".zenops_bash_profile should exist after init --apply, got missing at {profile:?}"
);
}
#[test]
fn init_apply_false_emits_init_summary_event() {
use zenops::output::InitSummary;
let env = test_env::TestEnv::load();
let bare = env.seed_bare_repo(&[("config.toml", MINIMAL_CONFIG)]);
let out = env
.run(&Cmd::Init {
url: Some(bare.to_str().unwrap().to_string()),
branch: None,
apply: false,
yes: false,
})
.expect("init should succeed");
let summaries: Vec<&InitSummary> = out
.entries
.iter()
.filter_map(|e| match e {
Entry::Init(s) => Some(s),
_ => None,
})
.collect();
assert_eq!(
summaries.len(),
1,
"expected exactly one init_summary, got: {:?}",
out.entries,
);
assert_eq!(summaries[0].shell.as_deref(), Some("bash"));
}
#[test]
fn init_apply_false_emits_init_summary_with_remote_url() {
let env = test_env::TestEnv::load();
let bare = env.seed_bare_repo(&[("config.toml", MINIMAL_CONFIG)]);
let out = env
.run(&init_cmd(bare.to_str().unwrap()))
.expect("init should succeed");
let summary = out
.entries
.iter()
.find_map(|e| match e {
Entry::Init(s) => Some(s),
_ => None,
})
.unwrap_or_else(|| panic!("expected one InitSummary, got: {:?}", out.entries));
assert_eq!(
summary.remote.as_deref(),
Some(bare.to_str().unwrap()),
"expected remote URL to match the bare repo path",
);
}
#[test]
fn init_clone_summary_handles_zsh_shell() {
use zenops::output::InitSummary;
let env = test_env::TestEnv::load();
let bare = env.seed_bare_repo(&[(
"config.toml",
r#"
[shell]
type = "zsh"
[shell.environment]
[shell.alias]
"#,
)]);
let out = env
.run(&init_cmd(bare.to_str().unwrap()))
.expect("init should succeed");
let summary: &InitSummary = out
.entries
.iter()
.find_map(|e| match e {
Entry::Init(s) => Some(s),
_ => None,
})
.unwrap_or_else(|| panic!("expected one InitSummary, got: {:?}", out.entries));
assert_eq!(summary.shell.as_deref(), Some("zsh"));
}
#[test]
fn init_clone_summary_omits_shell_when_not_configured() {
use zenops::output::InitSummary;
let env = test_env::TestEnv::load();
let bare = env.seed_bare_repo(&[("config.toml", "")]);
let out = env
.run(&init_cmd(bare.to_str().unwrap()))
.expect("init should succeed");
let summary: &InitSummary = out
.entries
.iter()
.find_map(|e| match e {
Entry::Init(s) => Some(s),
_ => None,
})
.unwrap_or_else(|| panic!("expected one InitSummary, got: {:?}", out.entries));
assert_eq!(summary.shell, None);
}
#[test]
fn init_clone_summary_pkg_count_includes_user_packages() {
use zenops::output::InitSummary;
let env = test_env::TestEnv::load();
let baseline_bare = env.seed_bare_repo(&[("config.toml", "")]);
let baseline_out = env
.run(&init_cmd(baseline_bare.to_str().unwrap()))
.expect("baseline init should succeed");
let baseline = baseline_out
.entries
.iter()
.find_map(|e| match e {
Entry::Init(s) => Some(s.pkg_count),
_ => None,
})
.unwrap();
let env2 = test_env::TestEnv::load();
let extended_bare = env2.seed_bare_repo(&[(
"config.toml",
r#"
[pkg.alpha]
[pkg.alpha.install_hint.brew]
packages = ["alpha"]
[pkg.alpha.install_hint.dnf5]
packages = []
[pkg.alpha.install_hint.apt]
packages = []
[pkg.alpha.install_hint.pacman]
packages = []
[pkg.alpha.install_hint.cargo]
packages = []
[pkg.beta]
[pkg.beta.install_hint.brew]
packages = ["beta"]
[pkg.beta.install_hint.dnf5]
packages = []
[pkg.beta.install_hint.apt]
packages = []
[pkg.beta.install_hint.pacman]
packages = []
[pkg.beta.install_hint.cargo]
packages = []
[pkg.gamma]
[pkg.gamma.install_hint.brew]
packages = ["gamma"]
[pkg.gamma.install_hint.dnf5]
packages = []
[pkg.gamma.install_hint.apt]
packages = []
[pkg.gamma.install_hint.pacman]
packages = []
[pkg.gamma.install_hint.cargo]
packages = []
"#,
)]);
let out: zenops::output::InitSummary = env2
.run(&init_cmd(extended_bare.to_str().unwrap()))
.expect("init should succeed")
.entries
.into_iter()
.find_map(|e| match e {
Entry::Init(s) => Some(s),
_ => None,
})
.unwrap();
let _: InitSummary = out;
assert_eq!(out.pkg_count, baseline + 3);
}
#[test]
fn init_apply_true_does_not_emit_init_summary() {
let env = test_env::TestEnv::load();
let bare = env.seed_bare_repo(&[("config.toml", MINIMAL_CONFIG)]);
let out = env
.run(&Cmd::Init {
url: Some(bare.to_str().unwrap().to_string()),
branch: None,
apply: true,
yes: true,
})
.expect("init --apply --yes should succeed");
let has_summary = out.entries.iter().any(|e| matches!(e, Entry::Init(_)));
assert!(
!has_summary,
"init --apply should defer to apply's event stream and skip init_summary, got: {:?}",
out.entries,
);
}