use assert_cmd::Command;
use predicates::str::contains;
use std::fs;
use std::path::Path;
fn aristo_in(dir: &Path) -> Command {
let mut cmd = Command::cargo_bin("aristo").unwrap();
cmd.current_dir(dir);
cmd
}
#[test]
fn creates_all_state_files_in_empty_dir() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
aristo_in(root).arg("init").assert().success();
assert!(root.join("aristo.toml").is_file());
assert!(root.join(".aristo").is_dir());
assert!(root.join(".aristo/index.toml").is_file());
assert!(root.join(".aristo/specs").is_dir());
assert!(root.join(".aristo/doc").is_dir());
assert!(root.join(".github/workflows/aristo.yml").is_file());
assert!(!root.join(".git/hooks/pre-commit").exists());
}
#[test]
fn writes_index_with_meta_only_zero_entries() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
aristo_in(root).arg("init").assert().success();
let index = fs::read_to_string(root.join(".aristo/index.toml")).unwrap();
let parsed: aristo_core::index::IndexFile =
toml::from_str(&index).expect("index round-trips through aristo-core");
assert_eq!(
parsed.meta.schema_version, 1,
"schema_version must be 1 (current SDK version)"
);
assert!(
parsed
.meta
.generated_by
.as_deref()
.unwrap()
.contains("aristo init"),
"generated_by should identify init: {:?}",
parsed.meta.generated_by
);
assert!(
parsed.meta.generated_at.is_some(),
"generated_at must be set"
);
assert_eq!(
parsed.entries.len(),
0,
"init writes zero annotation entries"
);
}
#[test]
fn writes_aristo_toml_that_round_trips_as_default_config() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
aristo_in(root).arg("init").assert().success();
let cfg_text = fs::read_to_string(root.join("aristo.toml")).unwrap();
let parsed: aristo_core::config::ConfigFile =
toml::from_str(&cfg_text).expect("aristo.toml round-trips through aristo-core");
assert_eq!(
parsed,
aristo_core::config::ConfigFile::default(),
"init writes the canonical default config"
);
}
#[test]
fn second_invocation_is_idempotent() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
aristo_in(root).arg("init").assert().success();
let cfg_mtime_before = fs::metadata(root.join("aristo.toml"))
.unwrap()
.modified()
.unwrap();
let idx_mtime_before = fs::metadata(root.join(".aristo/index.toml"))
.unwrap()
.modified()
.unwrap();
std::thread::sleep(std::time::Duration::from_millis(50));
aristo_in(root)
.arg("init")
.assert()
.success()
.stdout(contains("nothing to do."))
.stdout(contains("already exists"));
let cfg_mtime_after = fs::metadata(root.join("aristo.toml"))
.unwrap()
.modified()
.unwrap();
let idx_mtime_after = fs::metadata(root.join(".aristo/index.toml"))
.unwrap()
.modified()
.unwrap();
assert_eq!(
cfg_mtime_before, cfg_mtime_after,
"aristo.toml must not be rewritten on second init"
);
assert_eq!(
idx_mtime_before, idx_mtime_after,
".aristo/index.toml must not be rewritten on second init"
);
}
#[test]
fn installs_hook_inside_git_repo() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
fs::create_dir_all(root.join(".git/hooks")).unwrap();
aristo_in(root)
.arg("init")
.assert()
.success()
.stdout(contains("installed pre-commit hook"));
let hook = root.join(".git/hooks/pre-commit");
assert!(hook.is_file(), "expected pre-commit hook installed");
let content = fs::read_to_string(&hook).unwrap();
assert!(
content.contains("Aristo pre-commit hook"),
"hook content should identify itself; got:\n{content}"
);
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mode = fs::metadata(&hook).unwrap().permissions().mode() & 0o777;
assert_eq!(mode, 0o755, "hook must be executable (0755)");
}
}
#[test]
fn second_invocation_in_git_repo_notes_existing_hook() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
fs::create_dir_all(root.join(".git/hooks")).unwrap();
aristo_in(root).arg("init").assert().success();
aristo_in(root)
.arg("init")
.assert()
.success()
.stdout(contains("pre-commit hook already installed"));
}