arc/tests/config.rs
hanna 2e0952f9fb
feat: add colored output with distinct symbols using the colored crate
Add a centralized ui module with Arc's visual identity: colored commit
IDs (magenta), bookmarks (cyan), tags (yellow), status symbols, and
diff highlighting. Update all command output and tests accordingly.
2026-02-09 03:51:59 +00:00

614 lines
16 KiB
Rust

use std::process::Command;
use tempfile::TempDir;
fn arc_cmd() -> Command {
let mut cmd = Command::new(env!("CARGO_BIN_EXE_arc"));
cmd.env("NO_COLOR", "1");
cmd
}
fn init_repo(dir: &TempDir) {
arc_cmd()
.arg("init")
.current_dir(dir.path())
.output()
.expect("failed to run arc init");
}
#[test]
fn config_yml_is_valid_yaml() {
let dir = TempDir::new().unwrap();
init_repo(&dir);
let config_path = dir.path().join(".arc").join("config.yml");
let contents = std::fs::read_to_string(&config_path).unwrap();
let value: serde_yaml::Value = serde_yaml::from_str(&contents).unwrap();
assert!(value.is_mapping());
}
#[test]
fn config_has_default_section() {
let dir = TempDir::new().unwrap();
init_repo(&dir);
let config_path = dir.path().join(".arc").join("config.yml");
let contents = std::fs::read_to_string(&config_path).unwrap();
let value: serde_yaml::Value = serde_yaml::from_str(&contents).unwrap();
let defaults = &value["default"];
assert_eq!(defaults["bookmark"].as_str(), Some("main"));
assert_eq!(defaults["remote"].as_str(), Some("origin"));
}
#[test]
fn head_is_valid_yaml() {
let dir = TempDir::new().unwrap();
init_repo(&dir);
let head_path = dir.path().join(".arc").join("HEAD");
let contents = std::fs::read_to_string(&head_path).unwrap();
let value: serde_yaml::Value = serde_yaml::from_str(&contents).unwrap();
assert!(value.is_mapping());
assert_eq!(value["state"].as_str(), Some("unborn"));
assert_eq!(value["bookmark"].as_str(), Some("main"));
}
#[test]
fn bookmark_ref_is_valid_yaml() {
let dir = TempDir::new().unwrap();
init_repo(&dir);
let bookmark_path = dir.path().join(".arc").join("bookmarks").join("main");
let contents = std::fs::read_to_string(&bookmark_path).unwrap();
let value: serde_yaml::Value = serde_yaml::from_str(&contents).unwrap();
assert!(value["commit"].is_null());
}
#[test]
fn config_set_and_get_user_name() {
let dir = TempDir::new().unwrap();
init_repo(&dir);
let out = arc_cmd()
.args(["config", "set", "user.name", "alice"])
.current_dir(dir.path())
.output()
.unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("user.name = alice"));
let out = arc_cmd()
.args(["config", "get", "user.name"])
.current_dir(dir.path())
.output()
.unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert_eq!(stdout.trim(), "alice");
}
#[test]
fn config_set_and_get_user_email() {
let dir = TempDir::new().unwrap();
init_repo(&dir);
arc_cmd()
.args(["config", "set", "user.email", "alice@example.com"])
.current_dir(dir.path())
.output()
.unwrap();
let out = arc_cmd()
.args(["config", "get", "user.email"])
.current_dir(dir.path())
.output()
.unwrap();
assert!(out.status.success());
assert_eq!(
String::from_utf8_lossy(&out.stdout).trim(),
"alice@example.com"
);
}
#[test]
fn config_set_and_get_user_key() {
let dir = TempDir::new().unwrap();
init_repo(&dir);
arc_cmd()
.args(["config", "set", "user.key", "~/.ssh/id_ed25519"])
.current_dir(dir.path())
.output()
.unwrap();
let out = arc_cmd()
.args(["config", "get", "user.key"])
.current_dir(dir.path())
.output()
.unwrap();
assert!(out.status.success());
assert_eq!(
String::from_utf8_lossy(&out.stdout).trim(),
"~/.ssh/id_ed25519"
);
}
#[test]
fn config_set_and_get_default_bookmark() {
let dir = TempDir::new().unwrap();
init_repo(&dir);
arc_cmd()
.args(["config", "set", "default.bookmark", "develop"])
.current_dir(dir.path())
.output()
.unwrap();
let out = arc_cmd()
.args(["config", "get", "default.bookmark"])
.current_dir(dir.path())
.output()
.unwrap();
assert!(out.status.success());
assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), "develop");
}
#[test]
fn config_set_and_get_default_remote() {
let dir = TempDir::new().unwrap();
init_repo(&dir);
arc_cmd()
.args(["config", "set", "default.remote", "upstream"])
.current_dir(dir.path())
.output()
.unwrap();
let out = arc_cmd()
.args(["config", "get", "default.remote"])
.current_dir(dir.path())
.output()
.unwrap();
assert!(out.status.success());
assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), "upstream");
}
#[test]
fn config_set_and_get_alias() {
let dir = TempDir::new().unwrap();
init_repo(&dir);
arc_cmd()
.args(["config", "set", "aliases.c", "commit"])
.current_dir(dir.path())
.output()
.unwrap();
let out = arc_cmd()
.args(["config", "get", "aliases.c"])
.current_dir(dir.path())
.output()
.unwrap();
assert!(out.status.success());
assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), "commit");
}
#[test]
fn config_get_unset_key_returns_not_set() {
let dir = TempDir::new().unwrap();
init_repo(&dir);
let out = arc_cmd()
.args(["config", "get", "user.name"])
.current_dir(dir.path())
.output()
.unwrap();
assert!(out.status.success());
assert!(String::from_utf8_lossy(&out.stdout).contains("is not set"));
}
#[test]
fn config_unset_key() {
let dir = TempDir::new().unwrap();
init_repo(&dir);
arc_cmd()
.args(["config", "set", "user.name", "alice"])
.current_dir(dir.path())
.output()
.unwrap();
let out = arc_cmd()
.args(["config", "unset", "user.name"])
.current_dir(dir.path())
.output()
.unwrap();
assert!(out.status.success());
assert!(String::from_utf8_lossy(&out.stdout).contains("unset user.name"));
let out = arc_cmd()
.args(["config", "get", "user.name"])
.current_dir(dir.path())
.output()
.unwrap();
assert!(String::from_utf8_lossy(&out.stdout).contains("is not set"));
}
#[test]
fn config_unset_alias() {
let dir = TempDir::new().unwrap();
init_repo(&dir);
arc_cmd()
.args(["config", "set", "aliases.c", "commit"])
.current_dir(dir.path())
.output()
.unwrap();
arc_cmd()
.args(["config", "unset", "aliases.c"])
.current_dir(dir.path())
.output()
.unwrap();
let out = arc_cmd()
.args(["config", "get", "aliases.c"])
.current_dir(dir.path())
.output()
.unwrap();
assert!(String::from_utf8_lossy(&out.stdout).contains("is not set"));
}
#[test]
fn config_show_displays_yaml() {
let dir = TempDir::new().unwrap();
init_repo(&dir);
arc_cmd()
.args(["config", "set", "user.name", "bob"])
.current_dir(dir.path())
.output()
.unwrap();
let out = arc_cmd()
.args(["config", "show"])
.current_dir(dir.path())
.output()
.unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("bob"));
}
#[test]
fn config_invalid_key_fails() {
let dir = TempDir::new().unwrap();
init_repo(&dir);
let out = arc_cmd()
.args(["config", "set", "invalid", "value"])
.current_dir(dir.path())
.output()
.unwrap();
assert!(!out.status.success());
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(stderr.contains("invalid config key"));
}
#[test]
fn config_invalid_section_fails() {
let dir = TempDir::new().unwrap();
init_repo(&dir);
let out = arc_cmd()
.args(["config", "set", "bogus.field", "value"])
.current_dir(dir.path())
.output()
.unwrap();
assert!(!out.status.success());
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(stderr.contains("invalid config key"));
}
#[test]
fn config_invalid_field_fails() {
let dir = TempDir::new().unwrap();
init_repo(&dir);
let out = arc_cmd()
.args(["config", "set", "user.bogus", "value"])
.current_dir(dir.path())
.output()
.unwrap();
assert!(!out.status.success());
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(stderr.contains("invalid config key"));
}
#[test]
fn config_set_overwrites_existing() {
let dir = TempDir::new().unwrap();
init_repo(&dir);
arc_cmd()
.args(["config", "set", "user.name", "alice"])
.current_dir(dir.path())
.output()
.unwrap();
arc_cmd()
.args(["config", "set", "user.name", "bob"])
.current_dir(dir.path())
.output()
.unwrap();
let out = arc_cmd()
.args(["config", "get", "user.name"])
.current_dir(dir.path())
.output()
.unwrap();
assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), "bob");
}
#[test]
fn config_global_set_and_get() {
let dir = TempDir::new().unwrap();
let global_dir = TempDir::new().unwrap();
init_repo(&dir);
let out = arc_cmd()
.args(["config", "set", "-g", "user.name", "global-user"])
.current_dir(dir.path())
.env("XDG_CONFIG_HOME", global_dir.path())
.output()
.unwrap();
assert!(out.status.success());
let out = arc_cmd()
.args(["config", "get", "-g", "user.name"])
.current_dir(dir.path())
.env("XDG_CONFIG_HOME", global_dir.path())
.output()
.unwrap();
assert!(out.status.success());
assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), "global-user");
}
#[test]
fn config_local_overrides_global() {
let dir = TempDir::new().unwrap();
let global_dir = TempDir::new().unwrap();
init_repo(&dir);
arc_cmd()
.args(["config", "set", "-g", "user.name", "global-user"])
.current_dir(dir.path())
.env("XDG_CONFIG_HOME", global_dir.path())
.output()
.unwrap();
arc_cmd()
.args(["config", "set", "user.name", "local-user"])
.current_dir(dir.path())
.env("XDG_CONFIG_HOME", global_dir.path())
.output()
.unwrap();
let out = arc_cmd()
.args(["config", "get", "user.name"])
.current_dir(dir.path())
.env("XDG_CONFIG_HOME", global_dir.path())
.output()
.unwrap();
assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), "local-user");
}
#[test]
fn config_get_falls_back_to_global() {
let dir = TempDir::new().unwrap();
let global_dir = TempDir::new().unwrap();
init_repo(&dir);
arc_cmd()
.args(["config", "set", "-g", "user.email", "global@example.com"])
.current_dir(dir.path())
.env("XDG_CONFIG_HOME", global_dir.path())
.output()
.unwrap();
let out = arc_cmd()
.args(["config", "get", "user.email"])
.current_dir(dir.path())
.env("XDG_CONFIG_HOME", global_dir.path())
.output()
.unwrap();
assert!(out.status.success());
assert_eq!(
String::from_utf8_lossy(&out.stdout).trim(),
"global@example.com"
);
}
#[test]
fn config_global_unset() {
let dir = TempDir::new().unwrap();
let global_dir = TempDir::new().unwrap();
init_repo(&dir);
arc_cmd()
.args(["config", "set", "-g", "user.name", "global-user"])
.current_dir(dir.path())
.env("XDG_CONFIG_HOME", global_dir.path())
.output()
.unwrap();
arc_cmd()
.args(["config", "unset", "-g", "user.name"])
.current_dir(dir.path())
.env("XDG_CONFIG_HOME", global_dir.path())
.output()
.unwrap();
let out = arc_cmd()
.args(["config", "get", "-g", "user.name"])
.current_dir(dir.path())
.env("XDG_CONFIG_HOME", global_dir.path())
.output()
.unwrap();
assert!(String::from_utf8_lossy(&out.stdout).contains("is not set"));
}
#[test]
fn config_global_show() {
let dir = TempDir::new().unwrap();
let global_dir = TempDir::new().unwrap();
init_repo(&dir);
arc_cmd()
.args(["config", "set", "-g", "user.name", "global-user"])
.current_dir(dir.path())
.env("XDG_CONFIG_HOME", global_dir.path())
.output()
.unwrap();
let out = arc_cmd()
.args(["config", "show", "-g"])
.current_dir(dir.path())
.env("XDG_CONFIG_HOME", global_dir.path())
.output()
.unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("global-user"));
}
#[test]
fn config_show_effective_merges_local_and_global() {
let dir = TempDir::new().unwrap();
let global_dir = TempDir::new().unwrap();
init_repo(&dir);
arc_cmd()
.args(["config", "set", "-g", "user.name", "global-user"])
.current_dir(dir.path())
.env("XDG_CONFIG_HOME", global_dir.path())
.output()
.unwrap();
arc_cmd()
.args(["config", "set", "user.email", "local@example.com"])
.current_dir(dir.path())
.env("XDG_CONFIG_HOME", global_dir.path())
.output()
.unwrap();
let out = arc_cmd()
.args(["config", "show"])
.current_dir(dir.path())
.env("XDG_CONFIG_HOME", global_dir.path())
.output()
.unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("global-user"));
assert!(stdout.contains("local@example.com"));
}
#[test]
fn config_persisted_to_yaml_file() {
let dir = TempDir::new().unwrap();
init_repo(&dir);
arc_cmd()
.args(["config", "set", "user.name", "alice"])
.current_dir(dir.path())
.output()
.unwrap();
let config_path = dir.path().join(".arc").join("config.yml");
let contents = std::fs::read_to_string(&config_path).unwrap();
let value: serde_yaml::Value = serde_yaml::from_str(&contents).unwrap();
assert_eq!(value["user"]["name"].as_str(), Some("alice"));
}
#[test]
fn alias_expansion_via_config() {
let dir = TempDir::new().unwrap();
init_repo(&dir);
arc_cmd()
.args(["config", "set", "aliases.s", "status"])
.current_dir(dir.path())
.output()
.unwrap();
let out = arc_cmd().arg("s").current_dir(dir.path()).output().unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.contains("clean") || stdout.contains("no changes") || stdout.is_empty(),
"unexpected status output: {stdout}"
);
}
#[test]
fn alias_expansion_passes_arguments() {
let dir = TempDir::new().unwrap();
init_repo(&dir);
std::fs::write(dir.path().join("hello.txt"), "hello world").unwrap();
arc_cmd()
.args(["config", "set", "aliases.c", "commit"])
.current_dir(dir.path())
.output()
.unwrap();
let out = arc_cmd()
.args(["c", "test commit via alias"])
.current_dir(dir.path())
.output()
.unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("committed"));
}
#[test]
fn config_multiple_aliases() {
let dir = TempDir::new().unwrap();
init_repo(&dir);
arc_cmd()
.args(["config", "set", "aliases.c", "commit"])
.current_dir(dir.path())
.output()
.unwrap();
arc_cmd()
.args(["config", "set", "aliases.s", "status"])
.current_dir(dir.path())
.output()
.unwrap();
let out = arc_cmd()
.args(["config", "get", "aliases.c"])
.current_dir(dir.path())
.output()
.unwrap();
assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), "commit");
let out = arc_cmd()
.args(["config", "get", "aliases.s"])
.current_dir(dir.path())
.output()
.unwrap();
assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), "status");
}