use std::path::PathBuf;
use super::super::{DefaultBranchName, WorktreeInfo, finalize_worktree};
#[test]
fn test_parse_worktree_list() {
let output = "worktree /path/to/main
HEAD abcd1234
branch refs/heads/main
worktree /path/to/feature
HEAD efgh5678
branch refs/heads/feature
";
let worktrees = WorktreeInfo::parse_porcelain_list(output).unwrap();
let [main_wt, feature_wt]: [WorktreeInfo; 2] = worktrees.try_into().unwrap();
assert_eq!(main_wt.path, PathBuf::from("/path/to/main"));
assert_eq!(main_wt.head, "abcd1234");
assert_eq!(main_wt.branch, Some("main".to_string()));
assert!(!main_wt.bare);
assert!(!main_wt.detached);
assert_eq!(feature_wt.path, PathBuf::from("/path/to/feature"));
assert_eq!(feature_wt.head, "efgh5678");
assert_eq!(feature_wt.branch, Some("feature".to_string()));
}
#[test]
fn test_parse_detached_worktree() {
let output = "worktree /path/to/detached
HEAD abcd1234
detached
";
let worktrees = WorktreeInfo::parse_porcelain_list(output).unwrap();
let [wt]: [WorktreeInfo; 1] = worktrees.try_into().unwrap();
assert!(wt.detached);
assert_eq!(wt.branch, None);
}
#[test]
fn test_finalize_worktree_with_branch() {
let wt = WorktreeInfo {
path: PathBuf::from("/path/to/worktree"),
head: "abcd1234".to_string(),
branch: Some("feature".to_string()),
bare: false,
detached: false,
locked: None,
prunable: None,
};
let finalized = finalize_worktree(wt.clone());
assert_eq!(finalized.branch, Some("feature".to_string()));
}
#[test]
fn test_finalize_worktree_detached_with_branch() {
let wt = WorktreeInfo {
path: PathBuf::from("/path/to/worktree"),
head: "abcd1234".to_string(),
branch: Some("feature".to_string()),
bare: false,
detached: true,
locked: None,
prunable: None,
};
let finalized = finalize_worktree(wt.clone());
assert_eq!(finalized.branch, Some("feature".to_string()));
}
#[test]
fn test_finalize_worktree_detached_no_branch() {
let wt = WorktreeInfo {
path: PathBuf::from("/nonexistent/path"),
head: "abcd1234".to_string(),
branch: None,
bare: false,
detached: true,
locked: None,
prunable: None,
};
let finalized = finalize_worktree(wt);
assert_eq!(finalized.branch, None);
}
#[test]
fn test_parse_locked_worktree() {
let output = "worktree /path/to/locked
HEAD abcd1234
branch refs/heads/main
locked reason for lock
";
let worktrees = WorktreeInfo::parse_porcelain_list(output).unwrap();
let [wt]: [WorktreeInfo; 1] = worktrees.try_into().unwrap();
assert_eq!(wt.locked, Some("reason for lock".to_string()));
}
#[test]
fn test_parse_bare_worktree() {
let output = "worktree /path/to/bare
HEAD abcd1234
bare
";
let worktrees = WorktreeInfo::parse_porcelain_list(output).unwrap();
let [wt]: [WorktreeInfo; 1] = worktrees.try_into().unwrap();
assert!(wt.bare);
}
#[test]
fn test_parse_local_default_branch_with_prefix() {
let output = "origin/main\n";
let branch = DefaultBranchName::from_local("origin", output)
.map(DefaultBranchName::into_string)
.unwrap();
assert_eq!(branch, "main");
}
#[test]
fn test_parse_local_default_branch_without_prefix() {
let output = "main\n";
let branch = DefaultBranchName::from_local("origin", output)
.map(DefaultBranchName::into_string)
.unwrap();
assert_eq!(branch, "main");
}
#[test]
fn test_parse_local_default_branch_master() {
let output = "origin/master\n";
let branch = DefaultBranchName::from_local("origin", output)
.map(DefaultBranchName::into_string)
.unwrap();
assert_eq!(branch, "master");
}
#[test]
fn test_parse_local_default_branch_custom_name() {
let output = "origin/develop\n";
let branch = DefaultBranchName::from_local("origin", output)
.map(DefaultBranchName::into_string)
.unwrap();
assert_eq!(branch, "develop");
}
#[test]
fn test_parse_local_default_branch_custom_remote() {
let output = "upstream/main\n";
let branch = DefaultBranchName::from_local("upstream", output)
.map(DefaultBranchName::into_string)
.unwrap();
assert_eq!(branch, "main");
}
#[test]
fn test_parse_local_default_branch_empty() {
let output = "";
let result =
DefaultBranchName::from_local("origin", output).map(DefaultBranchName::into_string);
assert!(result.is_err());
}
#[test]
fn test_parse_local_default_branch_whitespace_only() {
let output = " \n ";
let result =
DefaultBranchName::from_local("origin", output).map(DefaultBranchName::into_string);
assert!(result.is_err());
}
#[test]
fn test_parse_remote_default_branch_main() {
let output = "ref: refs/heads/main\tHEAD
85a1ce7c7182540f9c02453441cb3e8bf0ced214\tHEAD
";
let branch = DefaultBranchName::from_remote(output)
.map(DefaultBranchName::into_string)
.unwrap();
assert_eq!(branch, "main");
}
#[test]
fn test_parse_remote_default_branch_master() {
let output = "ref: refs/heads/master\tHEAD
abcd1234567890abcd1234567890abcd12345678\tHEAD
";
let branch = DefaultBranchName::from_remote(output)
.map(DefaultBranchName::into_string)
.unwrap();
assert_eq!(branch, "master");
}
#[test]
fn test_parse_remote_default_branch_custom() {
let output = "ref: refs/heads/develop\tHEAD
1234567890abcdef1234567890abcdef12345678\tHEAD
";
let branch = DefaultBranchName::from_remote(output)
.map(DefaultBranchName::into_string)
.unwrap();
assert_eq!(branch, "develop");
}
#[test]
fn test_parse_remote_default_branch_only_symref_line() {
let output = "ref: refs/heads/main\tHEAD\n";
let branch = DefaultBranchName::from_remote(output)
.map(DefaultBranchName::into_string)
.unwrap();
assert_eq!(branch, "main");
}
#[test]
fn test_parse_remote_default_branch_missing_symref() {
let output = "85a1ce7c7182540f9c02453441cb3e8bf0ced214\tHEAD\n";
let result = DefaultBranchName::from_remote(output).map(DefaultBranchName::into_string);
assert!(result.is_err());
}
#[test]
fn test_parse_remote_default_branch_empty() {
let output = "";
let result = DefaultBranchName::from_remote(output).map(DefaultBranchName::into_string);
assert!(result.is_err());
}
#[test]
fn test_parse_remote_default_branch_malformed_ref() {
let output = "ref: main\tHEAD\n";
let result = DefaultBranchName::from_remote(output).map(DefaultBranchName::into_string);
assert!(result.is_err());
}
#[test]
fn test_parse_remote_default_branch_with_spaces() {
let output = "ref: refs/heads/main HEAD\n";
let result = DefaultBranchName::from_remote(output).map(DefaultBranchName::into_string);
assert!(result.is_err());
}
#[test]
fn test_parse_remote_default_branch_branch_with_slash() {
let output = "ref: refs/heads/feature/new-ui\tHEAD\n";
let branch = DefaultBranchName::from_remote(output)
.map(DefaultBranchName::into_string)
.unwrap();
assert_eq!(branch, "feature/new-ui");
}
use super::ResolvedWorktree;
#[test]
fn test_resolved_worktree_clone() {
let wt = ResolvedWorktree::Worktree {
path: PathBuf::from("/path/to/worktree"),
branch: Some("feature".to_string()),
};
let cloned = wt.clone();
if let ResolvedWorktree::Worktree { path, branch } = cloned {
assert_eq!(path, PathBuf::from("/path/to/worktree"));
assert_eq!(branch, Some("feature".to_string()));
} else {
panic!("Expected Worktree variant");
}
}
#[test]
fn test_resolved_worktree_none_branch() {
let wt = ResolvedWorktree::Worktree {
path: PathBuf::from("/path/to/worktree"),
branch: None,
};
if let ResolvedWorktree::Worktree { path, branch } = wt {
assert_eq!(path, PathBuf::from("/path/to/worktree"));
assert!(branch.is_none());
} else {
panic!("Expected Worktree variant");
}
}
#[test]
fn test_worktree_locked_empty_reason() {
let output = "worktree /path/to/locked
HEAD abcd1234
branch refs/heads/main
locked
";
let worktrees = WorktreeInfo::parse_porcelain_list(output).unwrap();
let [wt]: [WorktreeInfo; 1] = worktrees.try_into().unwrap();
assert_eq!(wt.locked, Some(String::new()));
}
#[test]
fn test_worktree_prunable() {
let output = "worktree /path/to/prunable
HEAD abcd1234
detached
prunable gitdir file points to non-existent location
";
let worktrees = WorktreeInfo::parse_porcelain_list(output).unwrap();
let [wt]: [WorktreeInfo; 1] = worktrees.try_into().unwrap();
assert!(wt.prunable.is_some());
assert!(wt.prunable.as_ref().unwrap().contains("non-existent"));
}
#[test]
fn test_parse_multiple_worktrees() {
let output = "worktree /main
HEAD 1111111111111111111111111111111111111111
branch refs/heads/main
worktree /feature-a
HEAD 2222222222222222222222222222222222222222
branch refs/heads/feature-a
worktree /feature-b
HEAD 3333333333333333333333333333333333333333
branch refs/heads/feature-b
worktree /detached
HEAD 4444444444444444444444444444444444444444
detached
";
let worktrees = WorktreeInfo::parse_porcelain_list(output).unwrap();
let [main_wt, feature_a, feature_b, detached_wt]: [WorktreeInfo; 4] =
worktrees.try_into().unwrap();
assert_eq!(main_wt.branch, Some("main".to_string()));
assert_eq!(feature_a.branch, Some("feature-a".to_string()));
assert_eq!(feature_b.branch, Some("feature-b".to_string()));
assert!(detached_wt.detached);
assert_eq!(detached_wt.branch, None);
}
#[test]
fn test_default_branch_name_display() {
let cases = [
("origin/main\n", "main"),
("upstream/develop\n", "develop"),
("origin/master\n", "master"),
];
for (input, expected) in cases {
let remote = input.split('/').next().unwrap();
let branch = DefaultBranchName::from_local(remote, input)
.map(DefaultBranchName::into_string)
.unwrap();
assert_eq!(branch, expected);
}
}
#[test]
fn repo_path_error_when_is_bare_fails() {
use super::RepoCache;
use std::sync::Arc;
let repo = super::Repository {
discovery_path: PathBuf::from("/nonexistent/repo"),
git_common_dir: PathBuf::from("/nonexistent/.git"),
cache: Arc::new(RepoCache::default()),
};
let err = repo.repo_path().unwrap_err();
let msg = format!("{err:#}");
assert!(
msg.starts_with("failed to check if repository is bare: "),
"unexpected error message: {msg}"
);
}
#[test]
fn extract_failed_command_from_stream_error() {
use super::StreamCommandError;
let err: anyhow::Error = StreamCommandError {
output: "fatal: ref exists".into(),
command: "git worktree add /path".into(),
exit_info: "exit code 128".into(),
}
.into();
let (output, cmd) = super::Repository::extract_failed_command(&err);
assert_eq!(output, "fatal: ref exists");
let cmd = cmd.unwrap();
assert_eq!(cmd.command, "git worktree add /path");
assert_eq!(cmd.exit_info, "exit code 128");
}
#[test]
fn extract_failed_command_from_other_error() {
let err = anyhow::anyhow!("some other error");
let (output, cmd) = super::Repository::extract_failed_command(&err);
assert_eq!(output, "some other error");
assert!(cmd.is_none());
}