From 670a55716564c5e26e3f80c7f9f87fedf3ea090d Mon Sep 17 00:00:00 2001 From: Versun Date: Sat, 21 Dec 2024 12:45:04 +0800 Subject: [PATCH] test: enhance test coverage for git operations 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`. --- tests/test_git_operations.py | 302 ++++++++++++++++++++++++++++++++++- 1 file changed, 301 insertions(+), 1 deletion(-) diff --git a/tests/test_git_operations.py b/tests/test_git_operations.py index 531ad92..9797177 100644 --- a/tests/test_git_operations.py +++ b/tests/test_git_operations.py @@ -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 @@ -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) @@ -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