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"); }