Skip to content

Commit e44b0b7

Browse files
committed
Add test coverage
1 parent 96b7909 commit e44b0b7

File tree

5 files changed

+423
-9
lines changed

5 files changed

+423
-9
lines changed

src/lib.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,14 @@ impl std::fmt::Display for GitXError {
3535
}
3636
}
3737

38-
impl std::error::Error for GitXError {}
38+
impl std::error::Error for GitXError {
39+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
40+
match self {
41+
GitXError::Io(err) => Some(err),
42+
GitXError::GitCommand(_) | GitXError::Parse(_) => None,
43+
}
44+
}
45+
}
3946

4047
impl From<std::io::Error> for GitXError {
4148
fn from(err: std::io::Error) -> Self {

src/sync.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,15 @@ pub fn run(merge: bool) {
6767
}
6868

6969
#[derive(Debug, PartialEq)]
70-
enum SyncStatus {
70+
pub enum SyncStatus {
7171
UpToDate,
7272
Behind(u32),
7373
Ahead(u32),
7474
Diverged(u32, u32), // behind, ahead
7575
}
7676

7777
// Helper function to get current branch
78-
fn get_current_branch() -> Result<String, &'static str> {
78+
pub fn get_current_branch() -> Result<String, &'static str> {
7979
let output = Command::new("git")
8080
.args(["rev-parse", "--abbrev-ref", "HEAD"])
8181
.output()
@@ -89,7 +89,7 @@ fn get_current_branch() -> Result<String, &'static str> {
8989
}
9090

9191
// Helper function to get upstream branch
92-
fn get_upstream_branch(branch: &str) -> Result<String, &'static str> {
92+
pub fn get_upstream_branch(branch: &str) -> Result<String, &'static str> {
9393
let output = Command::new("git")
9494
.args(["rev-parse", "--abbrev-ref", &format!("{branch}@{{u}}")])
9595
.output()
@@ -103,7 +103,7 @@ fn get_upstream_branch(branch: &str) -> Result<String, &'static str> {
103103
}
104104

105105
// Helper function to fetch from upstream
106-
fn fetch_upstream(upstream: &str) -> Result<(), &'static str> {
106+
pub fn fetch_upstream(upstream: &str) -> Result<(), &'static str> {
107107
let remote = upstream.split('/').next().unwrap_or("origin");
108108

109109
let status = Command::new("git")
@@ -119,7 +119,7 @@ fn fetch_upstream(upstream: &str) -> Result<(), &'static str> {
119119
}
120120

121121
// Helper function to get sync status
122-
fn get_sync_status(branch: &str, upstream: &str) -> Result<SyncStatus, &'static str> {
122+
pub fn get_sync_status(branch: &str, upstream: &str) -> Result<SyncStatus, &'static str> {
123123
let output = Command::new("git")
124124
.args([
125125
"rev-list",
@@ -162,7 +162,7 @@ pub fn parse_sync_counts(output: &str) -> Result<(u32, u32), &'static str> {
162162
}
163163

164164
// Helper function to sync with upstream
165-
fn sync_with_upstream(upstream: &str, merge: bool) -> Result<(), &'static str> {
165+
pub fn sync_with_upstream(upstream: &str, merge: bool) -> Result<(), &'static str> {
166166
let args = if merge {
167167
["merge", upstream]
168168
} else {

tests/test_lib.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
use git_x::{GitXError, Result};
2+
use std::io;
3+
4+
#[test]
5+
fn test_gitx_error_git_command() {
6+
let error = GitXError::GitCommand("git status failed".to_string());
7+
assert_eq!(format!("{error}"), "Git command failed: git status failed");
8+
}
9+
10+
#[test]
11+
fn test_gitx_error_io() {
12+
let io_error = io::Error::new(io::ErrorKind::NotFound, "File not found");
13+
let error = GitXError::Io(io_error);
14+
assert_eq!(format!("{error}"), "IO error: File not found");
15+
}
16+
17+
#[test]
18+
fn test_gitx_error_parse() {
19+
let error = GitXError::Parse("Invalid format".to_string());
20+
assert_eq!(format!("{error}"), "Parse error: Invalid format");
21+
}
22+
23+
#[test]
24+
fn test_gitx_error_debug() {
25+
let error = GitXError::GitCommand("test".to_string());
26+
let debug_str = format!("{error:?}");
27+
assert!(debug_str.contains("GitCommand"));
28+
assert!(debug_str.contains("test"));
29+
}
30+
31+
#[test]
32+
fn test_gitx_error_is_error() {
33+
let error = GitXError::Parse("test error".to_string());
34+
let _: &dyn std::error::Error = &error;
35+
}
36+
37+
#[test]
38+
fn test_from_io_error() {
39+
let io_error = io::Error::new(io::ErrorKind::PermissionDenied, "Access denied");
40+
let gitx_error: GitXError = io_error.into();
41+
42+
match gitx_error {
43+
GitXError::Io(err) => {
44+
assert_eq!(err.kind(), io::ErrorKind::PermissionDenied);
45+
assert_eq!(format!("{err}"), "Access denied");
46+
}
47+
_ => panic!("Expected GitXError::Io"),
48+
}
49+
}
50+
51+
#[test]
52+
fn test_result_type() {
53+
let success: i32 = 42;
54+
assert_eq!(success, 42);
55+
56+
let failure: Result<i32> = Err(GitXError::Parse("test".to_string()));
57+
assert!(failure.is_err());
58+
}
59+
60+
#[test]
61+
fn test_error_chain() {
62+
let io_error = io::Error::other("Original error");
63+
let gitx_error: GitXError = io_error.into();
64+
65+
let error_string = format!("{gitx_error}");
66+
assert!(error_string.contains("IO error"));
67+
assert!(error_string.contains("Original error"));
68+
}
69+
70+
#[test]
71+
fn test_gitx_error_source() {
72+
let io_error = io::Error::new(io::ErrorKind::InvalidInput, "Bad input");
73+
let gitx_error = GitXError::Io(io_error);
74+
75+
// Test that it implements Error trait properly
76+
use std::error::Error;
77+
let error_trait: &dyn Error = &gitx_error;
78+
assert!(error_trait.source().is_some());
79+
}

tests/test_rename_branch.rs

Lines changed: 189 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
mod common;
22

3+
use assert_cmd::Command as AssertCmd;
34
use common::repo_with_branch;
45
use git_x::rename_branch::*;
6+
use predicates::prelude::*;
7+
use std::fs;
8+
use std::path::PathBuf;
59
use std::process::Command;
10+
use tempfile::TempDir;
611

712
#[test]
813
fn test_rename_branch_in_isolated_repo() {
@@ -139,5 +144,187 @@ fn test_format_rename_success_message() {
139144
);
140145
}
141146

142-
// Note: rename_branch::run() interacts with remotes, making it difficult to test directly
143-
// The CLI integration tests cover this functionality instead
147+
// Helper function to create a test git repository
148+
fn create_test_repo() -> (TempDir, PathBuf) {
149+
let temp_dir = TempDir::new().expect("Failed to create temp directory");
150+
let repo_path = temp_dir.path().to_path_buf();
151+
152+
// Initialize git repo
153+
Command::new("git")
154+
.args(["init"])
155+
.current_dir(&repo_path)
156+
.output()
157+
.expect("Failed to init repo");
158+
159+
// Configure git
160+
Command::new("git")
161+
.args(["config", "user.name", "Test User"])
162+
.current_dir(&repo_path)
163+
.output()
164+
.expect("Failed to configure user");
165+
166+
Command::new("git")
167+
.args(["config", "user.email", "test@example.com"])
168+
.current_dir(&repo_path)
169+
.output()
170+
.expect("Failed to configure email");
171+
172+
// Create initial commit
173+
fs::write(repo_path.join("README.md"), "Initial commit").expect("Failed to write file");
174+
Command::new("git")
175+
.args(["add", "README.md"])
176+
.current_dir(&repo_path)
177+
.output()
178+
.expect("Failed to add file");
179+
180+
Command::new("git")
181+
.args(["commit", "-m", "Initial commit"])
182+
.current_dir(&repo_path)
183+
.output()
184+
.expect("Failed to commit");
185+
186+
(temp_dir, repo_path)
187+
}
188+
189+
#[test]
190+
fn test_rename_branch_run_outside_git_repo() {
191+
let temp_dir = TempDir::new().expect("Failed to create temp directory");
192+
193+
let mut cmd = AssertCmd::cargo_bin("git-x").expect("Failed to find binary");
194+
cmd.args(["rename-branch", "new-name"])
195+
.current_dir(temp_dir.path())
196+
.assert()
197+
.failure()
198+
.code(1)
199+
.stderr(predicate::str::contains(
200+
"Failed to get current branch name",
201+
));
202+
}
203+
204+
#[test]
205+
fn test_rename_branch_same_name() {
206+
let (_temp_dir, repo_path) = create_test_repo();
207+
208+
// Get current branch name (should be main or master)
209+
let output = Command::new("git")
210+
.args(["rev-parse", "--abbrev-ref", "HEAD"])
211+
.current_dir(&repo_path)
212+
.output()
213+
.expect("Failed to get current branch");
214+
215+
let current_branch = String::from_utf8_lossy(&output.stdout).trim().to_string();
216+
217+
let mut cmd = AssertCmd::cargo_bin("git-x").expect("Failed to find binary");
218+
cmd.args(["rename-branch", &current_branch])
219+
.current_dir(&repo_path)
220+
.assert()
221+
.success()
222+
.stdout(predicate::str::contains(format!(
223+
"Current branch is already named '{current_branch}'. Nothing to do."
224+
)));
225+
}
226+
227+
#[test]
228+
fn test_rename_branch_local_rename_failure() {
229+
let (_temp_dir, repo_path) = create_test_repo();
230+
231+
// Create a branch with an invalid name that would cause rename to fail
232+
// Use invalid characters that git doesn't allow
233+
let mut cmd = AssertCmd::cargo_bin("git-x").expect("Failed to find binary");
234+
cmd.args(["rename-branch", "branch..with..double..dots"])
235+
.current_dir(&repo_path)
236+
.assert()
237+
.failure()
238+
.code(1)
239+
.stderr(predicate::str::contains("Failed to rename local branch"));
240+
}
241+
242+
#[test]
243+
fn test_rename_branch_command_help() {
244+
let mut cmd = AssertCmd::cargo_bin("git-x").expect("Failed to find binary");
245+
cmd.args(["rename-branch", "--help"])
246+
.assert()
247+
.success()
248+
.stdout(predicate::str::contains("Rename the current branch"));
249+
}
250+
251+
#[test]
252+
fn test_rename_branch_push_failure() {
253+
let (_temp_dir, repo_path) = create_test_repo();
254+
255+
// Add a fake remote that doesn't exist to cause push failure
256+
Command::new("git")
257+
.args([
258+
"remote",
259+
"add",
260+
"origin",
261+
"https://github.com/nonexistent/repo.git",
262+
])
263+
.current_dir(&repo_path)
264+
.output()
265+
.expect("Failed to add remote");
266+
267+
let mut cmd = AssertCmd::cargo_bin("git-x").expect("Failed to find binary");
268+
cmd.args(["rename-branch", "new-branch-name"])
269+
.current_dir(&repo_path)
270+
.assert()
271+
.failure()
272+
.code(1)
273+
.stderr(predicate::str::contains(
274+
"Failed to push new branch to origin",
275+
));
276+
}
277+
278+
#[test]
279+
fn test_rename_branch_edge_cases() {
280+
// Test various edge cases in formatting and validation
281+
282+
// Test empty branch name handling
283+
assert_eq!(
284+
format_already_named_message(""),
285+
"Current branch is already named ''. Nothing to do."
286+
);
287+
288+
// Test special characters in branch names
289+
assert_eq!(
290+
format_rename_start_message("feature/test-123", "hotfix/urgent_fix"),
291+
"Renaming branch 'feature/test-123' to 'hotfix/urgent_fix'"
292+
);
293+
294+
// Test long branch names
295+
let long_name = "very-long-branch-name-that-exceeds-normal-length";
296+
assert_eq!(
297+
format_delete_success_message(long_name),
298+
format!("Deleted old branch '{long_name}' from origin.")
299+
);
300+
}
301+
302+
#[test]
303+
fn test_rename_branch_args_completeness() {
304+
// Ensure all argument generation functions produce valid arrays
305+
let current_args = get_current_branch_args();
306+
assert_eq!(current_args.len(), 3);
307+
assert!(current_args.contains(&"rev-parse"));
308+
assert!(current_args.contains(&"--abbrev-ref"));
309+
assert!(current_args.contains(&"HEAD"));
310+
311+
let local_rename_args = get_local_rename_args("test");
312+
assert_eq!(local_rename_args.len(), 3);
313+
assert!(local_rename_args.contains(&"branch".to_string()));
314+
assert!(local_rename_args.contains(&"-m".to_string()));
315+
assert!(local_rename_args.contains(&"test".to_string()));
316+
317+
let push_args = get_push_new_branch_args("test");
318+
assert_eq!(push_args.len(), 4);
319+
assert!(push_args.contains(&"push".to_string()));
320+
assert!(push_args.contains(&"-u".to_string()));
321+
assert!(push_args.contains(&"origin".to_string()));
322+
assert!(push_args.contains(&"test".to_string()));
323+
324+
let delete_args = get_delete_old_branch_args("old");
325+
assert_eq!(delete_args.len(), 4);
326+
assert!(delete_args.contains(&"push".to_string()));
327+
assert!(delete_args.contains(&"origin".to_string()));
328+
assert!(delete_args.contains(&"--delete".to_string()));
329+
assert!(delete_args.contains(&"old".to_string()));
330+
}

0 commit comments

Comments
 (0)