Skip to content

Commit

Permalink
test: enhance test coverage for git operations
Browse files Browse the repository at this point in the history
This commit significantly expands the test coverage for the `GitOperations` class, adding new tests to cover:

- Handling of deleted files.
- Handling of binary files and UnicodeDecodeErrors.
- Modified file changes and diff generation.
- Error handling during staging, pushing, and checking out branches.
- Exception handling during file processing.
- Specific scenarios like a deleted file after being added to the index.
- Edge cases and error conditions in `commit_changes`.
  • Loading branch information
versun committed Dec 21, 2024
1 parent dc61e71 commit 670a557
Showing 1 changed file with 301 additions and 1 deletion.
302 changes: 301 additions & 1 deletion tests/test_git_operations.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import os
import pytest
from git import Repo, InvalidGitRepositoryError
from git import Repo, InvalidGitRepositoryError, GitCommandError, NoSuchPathError
from aicmt.git_operations import GitOperations
from pathlib import Path


@pytest.fixture
Expand Down Expand Up @@ -31,6 +32,14 @@ def test_init_invalid_repo(tmp_path):
GitOperations(str(tmp_path))


def test_init_nonexistent_path(tmp_path):
"""Test initialization with a nonexistent path"""
nonexistent_path = str(tmp_path / "nonexistent")
with pytest.raises(NoSuchPathError) as excinfo:
GitOperations(nonexistent_path)
assert str(excinfo.value) == f"Path '{nonexistent_path}' does not exist"


def test_get_unstaged_changes(temp_git_repo):
"""Test getting unstaged changes"""
git_ops = GitOperations(temp_git_repo)
Expand Down Expand Up @@ -143,3 +152,294 @@ def test_get_commit_history(temp_git_repo):
finally:
# Restore the original directory
os.chdir(current_dir)


def test_deleted_file_changes(temp_git_repo):
"""Test handling of deleted files"""
git_ops = GitOperations(temp_git_repo)
current_dir = os.getcwd()
os.chdir(temp_git_repo)

try:
# Create and commit a file first
with open("to_delete.txt", "w") as f:
f.write("file to be deleted")
git_ops.stage_files(["to_delete.txt"])
git_ops.commit_changes("Add file to delete")

# Delete the file
os.remove("to_delete.txt")

changes = git_ops.get_unstaged_changes()
assert len(changes) == 1
assert changes[0].file == "to_delete.txt"
assert changes[0].status == "deleted"
finally:
os.chdir(current_dir)


def test_binary_file_changes(temp_git_repo):
"""Test handling of binary files"""
git_ops = GitOperations(temp_git_repo)
current_dir = os.getcwd()
os.chdir(temp_git_repo)

try:
# Create a binary file
with open("binary_file.bin", "wb") as f:
f.write(bytes([0x00, 0x01, 0x02, 0x03]))

changes = git_ops.get_unstaged_changes()
assert len(changes) == 1
assert changes[0].file == "binary_file.bin"
assert changes[0].status == "new file (binary)"
assert "[Binary file]" in changes[0].diff
finally:
os.chdir(current_dir)


def test_modified_file_changes(temp_git_repo):
"""Test handling of modified files"""
git_ops = GitOperations(temp_git_repo)
current_dir = os.getcwd()
os.chdir(temp_git_repo)

try:
# Create and commit a file
with open("modify_me.txt", "w") as f:
f.write("original content")
git_ops.stage_files(["modify_me.txt"])
git_ops.commit_changes("Add file to modify")

# Modify the file
with open("modify_me.txt", "w") as f:
f.write("modified content")

changes = git_ops.get_unstaged_changes()
assert len(changes) == 1
assert changes[0].file == "modify_me.txt"
assert changes[0].status == "modified"
assert "+modified content" in changes[0].diff
assert "-original content" in changes[0].diff
finally:
os.chdir(current_dir)


def test_binary_file_decode_error(temp_git_repo):
"""Test handling of binary files that cause UnicodeDecodeError"""
git_ops = GitOperations(temp_git_repo)
current_dir = os.getcwd()
os.chdir(temp_git_repo)

try:
# Create a binary file that will cause UnicodeDecodeError
with open("test.bin", "wb") as f:
f.write(bytes([0xFF, 0xFE, 0xFD] * 100)) # Invalid UTF-8 bytes

changes = git_ops.get_unstaged_changes()
assert len(changes) == 1
assert changes[0].file == "test.bin"
assert changes[0].status == "new file (binary)"
assert changes[0].diff == "[Binary file]"
finally:
os.chdir(current_dir)


def test_file_not_found_error(temp_git_repo):
"""Test handling of files that don't exist during diff generation"""
git_ops = GitOperations(temp_git_repo)
current_dir = os.getcwd()
os.chdir(temp_git_repo)

try:
# Create a file first
file_path = "temp.txt"
with open(file_path, "w") as f:
f.write("test content")

# Add the file to git's index
git_ops.repo.index.add([file_path])

# Now delete the file
os.remove(file_path)

# Get changes after file deletion
changes = git_ops.get_unstaged_changes()
matching_changes = [c for c in changes if c.file == file_path]
assert len(matching_changes) == 1
change = matching_changes[0]
assert change.status == "deleted"
assert change.diff == "[File deleted]"
finally:
os.chdir(current_dir)


def test_error_handling(temp_git_repo):
"""Test error handling scenarios"""
git_ops = GitOperations(temp_git_repo)
current_dir = os.getcwd()
os.chdir(temp_git_repo)

try:
# Test staging non-existent file
with pytest.raises(FileNotFoundError):
git_ops.stage_files(["non_existent.txt"])

# Test push to non-existent remote
with pytest.raises(ValueError):
git_ops.push_changes("non-existent-remote")

# Add a remote that points to a non-existent repository
git_ops.repo.create_remote("test-remote", "file:///non-existent-repo")
with pytest.raises(GitCommandError):
git_ops.push_changes("test-remote")

# Test checkout non-existent branch without create flag
with pytest.raises(GitCommandError):
git_ops.checkout_branch("non-existent-branch", create=False)
finally:
os.chdir(current_dir)


def test_get_unstaged_changes_exception_handling(temp_git_repo, monkeypatch, capsys):
"""Test exception handling in get_unstaged_changes method"""
git_ops = GitOperations(temp_git_repo)

# Switch to the repository directory
current_dir = os.getcwd()
os.chdir(temp_git_repo)

try:
# Create a file that will cause an exception when processed
test_file = "test_exception.txt"
with open(test_file, "w") as f:
f.write("test content")

# Mock the _handle_untracked_file method to raise an exception
def mock_handle_untracked(*args):
raise Exception("Test exception")

original_method = git_ops._handle_untracked_file
git_ops._handle_untracked_file = mock_handle_untracked

# Get captured output
captured = capsys.readouterr()

# Verify that the warning was printed (rich console formatting removed)
assert "Warning: Could not process test_exception.txt: Test exception" in captured.out.replace("[yellow]", "").replace("[/yellow]", "")

# Restore the original method
git_ops._handle_untracked_file = original_method

finally:
# Cleanup
if os.path.exists(test_file):
os.remove(test_file)
os.chdir(current_dir)


def test_handle_modified_file_diff_error(temp_git_repo):
"""Test IOError handling when git diff fails for a modified file"""
git_ops = GitOperations(temp_git_repo)
current_dir = os.getcwd()
os.chdir(temp_git_repo)

try:
# Create and commit a test file
test_file = "test_diff_error.txt"
with open(test_file, "w") as f:
f.write("initial content")
git_ops.stage_files([test_file])
git_ops.commit_changes("Add test file")

# Modify the file
with open(test_file, "w") as f:
f.write("modified content")

# Create a Mock object for Git
class MockGit:
def diff(self, *args, **kwargs):
raise GitCommandError("git diff", 128)

# Replace the git object with our mock
git_ops.git = MockGit()

# Test that IOError is raised when trying to get diff
with pytest.raises(IOError) as excinfo:
git_ops._handle_modified_file(test_file, Path(test_file))

assert str(excinfo.value) == f"Failed to get diff for {test_file}"

finally:
os.chdir(current_dir)


def test_handle_untracked_file_deleted(temp_git_repo):
"""Test _handle_untracked_file method when the file is deleted"""
git_ops = GitOperations(temp_git_repo)

# Create a non-existent file path
file_path = os.path.join(temp_git_repo, "non_existent_file.txt")
file_path_obj = Path(file_path)

# Test the _handle_untracked_file method
status, content = git_ops._handle_untracked_file(file_path, file_path_obj)

# Assert the results
assert status == "deleted"
assert content == "[File deleted]"


def test_stage_deleted_file(temp_git_repo):
"""Test staging a deleted file"""
git_ops = GitOperations(temp_git_repo)

# Switch to the repository directory
current_dir = os.getcwd()
os.chdir(temp_git_repo)

try:
# Create and commit a new file first
test_file = "to_be_deleted.txt"
with open(test_file, "w") as f:
f.write("this file will be deleted")

# Add and commit the file
git_ops.repo.index.add([test_file])
git_ops.repo.index.commit("Add file that will be deleted")

# Delete the file
os.remove(test_file)

# Stage the deleted file
git_ops.stage_files([test_file])

# Verify that the file is marked as deleted in the index
diff = git_ops.repo.head.commit.diff()
deleted_files = [d.a_path for d in diff if d.change_type == "D"]
assert test_file in deleted_files

finally:
# Restore the original directory
os.chdir(current_dir)


def test_commit_changes_error_handling(temp_git_repo, monkeypatch):
"""Test error handling in commit_changes method"""
git_ops = GitOperations(temp_git_repo)

# Create a mock index object that raises GitCommandError on commit
class MockIndex:
def commit(self, *args, **kwargs):
raise GitCommandError("commit", status=128, stderr="fatal: some git error")

# Replace the entire index object
monkeypatch.setattr(git_ops.repo, "index", MockIndex())

# Test that the function properly raises GitCommandError with the correct message
with pytest.raises(GitCommandError) as excinfo:
git_ops.commit_changes("Test commit")

assert "Failed to commit changes" in str(excinfo.value)
assert excinfo.value.status == 128
assert "fatal: some git error" in excinfo.value.stderr

0 comments on commit 670a557

Please sign in to comment.