mod common;
use assert_cmd::Command;
use assert_cmd::cargo::cargo_bin_cmd;
use common::{add_test_remote, create_nested_repo, create_test_repo, get_remote_url, write_config};
use predicates::prelude::*;
fn gemote() -> Command {
cargo_bin_cmd!("gemote")
}
#[test]
fn save_empty_repo() {
let (dir, _repo) = create_test_repo();
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "save"])
.assert()
.success()
.stdout(predicate::str::contains("Saved"));
let content = std::fs::read_to_string(dir.path().join(".gemote")).unwrap();
assert!(content.contains("[settings]"));
}
#[test]
fn save_single_remote() {
let (dir, repo) = create_test_repo();
add_test_remote(&repo, "origin", "https://example.com/repo.git", None);
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "save"])
.assert()
.success();
let content = std::fs::read_to_string(dir.path().join(".gemote")).unwrap();
assert!(content.contains("origin"));
assert!(content.contains("https://example.com/repo.git"));
}
#[test]
fn save_multiple_remotes() {
let (dir, repo) = create_test_repo();
add_test_remote(&repo, "origin", "https://a.com/repo.git", None);
add_test_remote(&repo, "upstream", "https://b.com/repo.git", None);
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "save"])
.assert()
.success();
let content = std::fs::read_to_string(dir.path().join(".gemote")).unwrap();
assert!(content.contains("origin"));
assert!(content.contains("upstream"));
}
#[test]
fn save_with_push_url() {
let (dir, repo) = create_test_repo();
add_test_remote(
&repo,
"origin",
"https://example.com/repo.git",
Some("git@example.com:repo.git"),
);
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "save"])
.assert()
.success();
let content = std::fs::read_to_string(dir.path().join(".gemote")).unwrap();
assert!(content.contains("push_url"));
assert!(content.contains("git@example.com:repo.git"));
}
#[test]
fn save_fails_if_exists() {
let (dir, _repo) = create_test_repo();
write_config(dir.path(), "# existing");
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "save"])
.assert()
.failure()
.stderr(predicate::str::contains("--force"));
}
#[test]
fn save_force_overwrites() {
let (dir, repo) = create_test_repo();
write_config(dir.path(), "# old content");
add_test_remote(&repo, "origin", "https://example.com/repo.git", None);
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "save", "--force"])
.assert()
.success();
let content = std::fs::read_to_string(dir.path().join(".gemote")).unwrap();
assert!(!content.contains("# old content"));
assert!(content.contains("origin"));
}
#[test]
fn save_custom_config_path() {
let (dir, repo) = create_test_repo();
add_test_remote(&repo, "origin", "https://example.com/repo.git", None);
let config_path = dir.path().join("custom.toml");
gemote()
.args([
"--repo",
dir.path().to_str().unwrap(),
"--config",
config_path.to_str().unwrap(),
"save",
])
.assert()
.success();
let content = std::fs::read_to_string(&config_path).unwrap();
assert!(content.contains("origin"));
assert!(!dir.path().join(".gemote").exists());
}
#[test]
fn save_then_sync_roundtrip() {
let (dir, repo) = create_test_repo();
add_test_remote(&repo, "origin", "https://example.com/repo.git", None);
add_test_remote(
&repo,
"upstream",
"https://upstream.com/repo.git",
Some("git@upstream.com:repo.git"),
);
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "save"])
.assert()
.success();
repo.remote_delete("origin").unwrap();
repo.remote_delete("upstream").unwrap();
assert!(repo.remotes().unwrap().is_empty());
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "sync"])
.assert()
.success();
let (url, _) = get_remote_url(&repo, "origin");
assert_eq!(url, "https://example.com/repo.git");
let (url, push_url) = get_remote_url(&repo, "upstream");
assert_eq!(url, "https://upstream.com/repo.git");
assert_eq!(push_url.as_deref(), Some("git@upstream.com:repo.git"));
}
#[test]
fn save_recursive_with_nested_repo() {
let (dir, _repo) = create_test_repo();
let nested = create_nested_repo(dir.path(), "libs/core");
nested
.remote("origin", "https://example.com/core.git")
.unwrap();
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "save", "-r"])
.assert()
.success();
let content = std::fs::read_to_string(dir.path().join(".gemote")).unwrap();
assert!(content.contains("[submodules.\"libs/core\".remotes.origin]"));
assert!(content.contains("https://example.com/core.git"));
}
#[test]
fn save_recursive_no_nested() {
let (dir, _repo) = create_test_repo();
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "save", "-r"])
.assert()
.success();
let content = std::fs::read_to_string(dir.path().join(".gemote")).unwrap();
assert!(!content.contains("submodules"));
}
#[test]
fn save_nonrecursive_ignores_nested() {
let (dir, _repo) = create_test_repo();
let nested = create_nested_repo(dir.path(), "libs/core");
nested
.remote("origin", "https://example.com/core.git")
.unwrap();
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "save"])
.assert()
.success();
let content = std::fs::read_to_string(dir.path().join(".gemote")).unwrap();
assert!(!content.contains("submodules"));
}
#[test]
fn save_recursive_deeply_nested() {
let (dir, _repo) = create_test_repo();
let nested = create_nested_repo(dir.path(), "libs/core");
nested
.remote("origin", "https://example.com/core.git")
.unwrap();
let deep = create_nested_repo(dir.path().join("libs/core").as_path(), "inner");
deep.remote("origin", "https://example.com/inner.git")
.unwrap();
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "save", "-r"])
.assert()
.success();
let content = std::fs::read_to_string(dir.path().join(".gemote")).unwrap();
assert!(content.contains("[submodules.\"libs/core\".submodules.inner.remotes.origin]"));
assert!(content.contains("https://example.com/inner.git"));
}
#[test]
fn save_then_sync_deeply_nested_roundtrip() {
let (dir, repo) = create_test_repo();
add_test_remote(&repo, "origin", "https://example.com/repo.git", None);
let nested = create_nested_repo(dir.path(), "libs/core");
nested
.remote("origin", "https://example.com/core.git")
.unwrap();
let deep = create_nested_repo(dir.path().join("libs/core").as_path(), "inner");
deep.remote("origin", "https://example.com/inner.git")
.unwrap();
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "save", "-r"])
.assert()
.success();
deep.remote_delete("origin").unwrap();
assert!(deep.remotes().unwrap().is_empty());
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "sync", "-r"])
.assert()
.success();
let (url, _) = get_remote_url(&deep, "origin");
assert_eq!(url, "https://example.com/inner.git");
}
#[test]
fn save_then_sync_recursive_roundtrip() {
let (dir, repo) = create_test_repo();
add_test_remote(&repo, "origin", "https://example.com/repo.git", None);
let nested = create_nested_repo(dir.path(), "libs/core");
nested
.remote("origin", "https://example.com/core.git")
.unwrap();
nested
.remote("upstream", "https://upstream.com/core.git")
.unwrap();
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "save", "-r"])
.assert()
.success();
nested.remote_delete("origin").unwrap();
nested.remote_delete("upstream").unwrap();
assert!(nested.remotes().unwrap().is_empty());
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "sync", "-r"])
.assert()
.success();
let (url, _) = get_remote_url(&nested, "origin");
assert_eq!(url, "https://example.com/core.git");
let (url, _) = get_remote_url(&nested, "upstream");
assert_eq!(url, "https://upstream.com/core.git");
}
#[test]
fn save_preserves_prior_settings() {
let (dir, repo) = create_test_repo();
add_test_remote(&repo, "origin", "https://example.com/repo.git", None);
write_config(
dir.path(),
r#"
[settings]
extra_remotes = "warn"
recursive = true
[remotes.origin]
url = "https://stale.example.com/repo.git"
"#,
);
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "save", "-f"])
.assert()
.success();
let content = std::fs::read_to_string(dir.path().join(".gemote")).unwrap();
assert!(content.contains("extra_remotes = \"warn\""));
assert!(content.contains("recursive = true"));
assert!(content.contains("https://example.com/repo.git"));
assert!(!content.contains("stale.example.com"));
}
#[test]
fn save_config_recursive_true_recurses_without_flag() {
let (dir, repo) = create_test_repo();
add_test_remote(&repo, "origin", "https://example.com/repo.git", None);
let nested = create_nested_repo(dir.path(), "libs/core");
nested
.remote("origin", "https://example.com/core.git")
.unwrap();
write_config(
dir.path(),
r#"
[settings]
recursive = true
"#,
);
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "save", "-f"])
.assert()
.success();
let content = std::fs::read_to_string(dir.path().join(".gemote")).unwrap();
assert!(content.contains("[submodules.\"libs/core\".remotes.origin]"));
assert!(content.contains("https://example.com/core.git"));
}
#[test]
fn save_no_recursive_flag_overrides_config_true() {
let (dir, repo) = create_test_repo();
add_test_remote(&repo, "origin", "https://example.com/repo.git", None);
let nested = create_nested_repo(dir.path(), "libs/core");
nested
.remote("origin", "https://example.com/core.git")
.unwrap();
write_config(
dir.path(),
r#"
[settings]
recursive = true
"#,
);
gemote()
.args([
"--repo",
dir.path().to_str().unwrap(),
"save",
"-f",
"--no-recursive",
])
.assert()
.success();
let content = std::fs::read_to_string(dir.path().join(".gemote")).unwrap();
assert!(!content.contains("submodules"));
}
#[test]
fn save_warns_when_overwriting_submodule_sections() {
let (dir, repo) = create_test_repo();
add_test_remote(&repo, "origin", "https://example.com/repo.git", None);
write_config(
dir.path(),
r#"
[submodules."libs/core".remotes.origin]
url = "https://example.com/core.git"
"#,
);
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "save", "-f"])
.assert()
.success()
.stderr(predicate::str::contains("recursion is disabled"));
}
#[test]
fn save_errors_on_malformed_existing_config() {
let (dir, repo) = create_test_repo();
add_test_remote(&repo, "origin", "https://example.com/repo.git", None);
write_config(dir.path(), "this is not = [valid toml");
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "save", "-f"])
.assert()
.failure();
}