Reformatting, fixing tests, adding basic RAG pipeline implementation
This commit is contained in:
parent
a6cdbf1761
commit
24bfef99a2
12 changed files with 721 additions and 131 deletions
|
@ -8,13 +8,21 @@ from unittest.mock import Mock, PropertyMock, patch
|
|||
import pytest
|
||||
from git.exc import GitCommandError, InvalidGitRepositoryError
|
||||
|
||||
from reviewllama.git_diff import (GitAnalysis, GitDiff, analyze_git_repository,
|
||||
branch_exists, create_git_diff,
|
||||
determine_change_type, extract_file_content,
|
||||
filter_reviewable_diffs, find_git_repository,
|
||||
get_base_branch, get_current_branch_name,
|
||||
get_diff_between_branches,
|
||||
process_diff_items)
|
||||
from reviewllama.git_diff import (
|
||||
GitAnalysis,
|
||||
GitDiff,
|
||||
analyze_git_repository,
|
||||
branch_exists,
|
||||
create_git_diff,
|
||||
determine_change_type,
|
||||
extract_file_content,
|
||||
filter_reviewable_diffs,
|
||||
find_git_repository,
|
||||
get_base_branch,
|
||||
get_current_branch_name,
|
||||
get_diff_between_branches,
|
||||
process_diff_items,
|
||||
)
|
||||
|
||||
|
||||
# Fixtures
|
||||
|
@ -55,26 +63,26 @@ def sample_git_diffs():
|
|||
|
||||
|
||||
# Tests for find_git_repository
|
||||
@patch('reviewllama.git_diff.Repo')
|
||||
@patch("reviewllama.git_diff.Repo")
|
||||
def test_find_git_repository_success(mock_repo_class):
|
||||
"""Test successful git repository detection."""
|
||||
mock_repo_instance = Mock()
|
||||
mock_repo_class.return_value = mock_repo_instance
|
||||
|
||||
|
||||
path = Path("/project")
|
||||
result = find_git_repository(path)
|
||||
|
||||
|
||||
mock_repo_class.assert_called_once_with(path, search_parent_directories=True)
|
||||
assert result == mock_repo_instance
|
||||
|
||||
|
||||
@patch('reviewllama.git_diff.Repo')
|
||||
@patch("reviewllama.git_diff.Repo")
|
||||
def test_find_git_repository_not_found(mock_repo_class):
|
||||
"""Test git repository not found."""
|
||||
mock_repo_class.side_effect = InvalidGitRepositoryError()
|
||||
|
||||
|
||||
path = Path("/not-a-repo")
|
||||
|
||||
|
||||
with pytest.raises(ValueError, match="No git repository found"):
|
||||
find_git_repository(path)
|
||||
|
||||
|
@ -97,7 +105,7 @@ def test_get_current_branch_name_detached_head(mock_repo):
|
|||
def test_branch_exists_true(mock_repo):
|
||||
"""Test branch exists returns True when branch found."""
|
||||
mock_repo.commit.return_value = Mock()
|
||||
|
||||
|
||||
result = branch_exists(mock_repo, "main")
|
||||
assert result is True
|
||||
mock_repo.commit.assert_called_once_with("main")
|
||||
|
@ -106,45 +114,49 @@ def test_branch_exists_true(mock_repo):
|
|||
def test_branch_exists_false(mock_repo):
|
||||
"""Test branch exists returns False when branch not found."""
|
||||
mock_repo.commit.side_effect = Exception("Branch not found")
|
||||
|
||||
|
||||
result = branch_exists(mock_repo, "nonexistent")
|
||||
assert result is False
|
||||
|
||||
|
||||
# Tests for get_base_branch
|
||||
@patch('reviewllama.git_diff.branch_exists')
|
||||
@patch("reviewllama.git_diff.branch_exists")
|
||||
def test_get_base_branch_requested_exists(mock_branch_exists, mock_repo):
|
||||
"""Test get_base_branch returns requested branch when it exists."""
|
||||
mock_branch_exists.return_value = True
|
||||
|
||||
|
||||
result = get_base_branch(mock_repo, "develop")
|
||||
assert result == "develop"
|
||||
mock_branch_exists.assert_called_once_with(mock_repo, "develop")
|
||||
|
||||
|
||||
@patch('reviewllama.git_diff.branch_exists')
|
||||
@patch("reviewllama.git_diff.branch_exists")
|
||||
def test_get_base_branch_fallback_to_master(mock_branch_exists, mock_repo):
|
||||
"""Test get_base_branch falls back to master when requested not found."""
|
||||
mock_branch_exists.side_effect = [False, True] # requested=False, master=True
|
||||
|
||||
|
||||
result = get_base_branch(mock_repo, "nonexistent")
|
||||
assert result == "master"
|
||||
|
||||
|
||||
@patch('reviewllama.git_diff.branch_exists')
|
||||
@patch("reviewllama.git_diff.branch_exists")
|
||||
def test_get_base_branch_fallback_to_main(mock_branch_exists, mock_repo):
|
||||
"""Test get_base_branch falls back to main when master not found."""
|
||||
mock_branch_exists.side_effect = [False, False, True] # requested=False, master=False, main=True
|
||||
|
||||
mock_branch_exists.side_effect = [
|
||||
False,
|
||||
False,
|
||||
True,
|
||||
] # requested=False, master=False, main=True
|
||||
|
||||
result = get_base_branch(mock_repo, "nonexistent")
|
||||
assert result == "main"
|
||||
|
||||
|
||||
@patch('reviewllama.git_diff.branch_exists')
|
||||
@patch("reviewllama.git_diff.branch_exists")
|
||||
def test_get_base_branch_no_fallback_found(mock_branch_exists, mock_repo):
|
||||
"""Test get_base_branch raises error when no fallback found."""
|
||||
mock_branch_exists.return_value = False
|
||||
|
||||
|
||||
with pytest.raises(ValueError, match="Base branch 'nonexistent' not found"):
|
||||
get_base_branch(mock_repo, "nonexistent")
|
||||
|
||||
|
@ -155,12 +167,12 @@ def test_get_diff_between_branches_success(mock_repo):
|
|||
base_commit = Mock()
|
||||
current_commit = Mock()
|
||||
mock_diff = Mock()
|
||||
|
||||
|
||||
mock_repo.commit.side_effect = [base_commit, current_commit]
|
||||
base_commit.diff.return_value = mock_diff
|
||||
|
||||
|
||||
result = get_diff_between_branches(mock_repo, "master", "feature")
|
||||
|
||||
|
||||
assert result == mock_diff
|
||||
base_commit.diff.assert_called_once_with(current_commit)
|
||||
|
||||
|
@ -168,7 +180,7 @@ def test_get_diff_between_branches_success(mock_repo):
|
|||
def test_get_diff_between_branches_git_error(mock_repo):
|
||||
"""Test diff between branches with git error."""
|
||||
mock_repo.commit.side_effect = GitCommandError("git", "error")
|
||||
|
||||
|
||||
with pytest.raises(ValueError, match="Failed to get diff"):
|
||||
get_diff_between_branches(mock_repo, "master", "feature")
|
||||
|
||||
|
@ -179,7 +191,7 @@ def test_determine_change_type_added():
|
|||
diff = Mock()
|
||||
diff.new_file = True
|
||||
diff.deleted_file = False
|
||||
|
||||
|
||||
result = determine_change_type(diff)
|
||||
assert result == "added"
|
||||
|
||||
|
@ -189,7 +201,7 @@ def test_determine_change_type_deleted():
|
|||
diff = Mock()
|
||||
diff.new_file = False
|
||||
diff.deleted_file = True
|
||||
|
||||
|
||||
result = determine_change_type(diff)
|
||||
assert result == "deleted"
|
||||
|
||||
|
@ -199,7 +211,7 @@ def test_determine_change_type_modified():
|
|||
diff = Mock()
|
||||
diff.new_file = False
|
||||
diff.deleted_file = False
|
||||
|
||||
|
||||
result = determine_change_type(diff)
|
||||
assert result == "modified"
|
||||
|
||||
|
@ -223,7 +235,7 @@ def test_extract_file_content_none_blob():
|
|||
"""Test extracting content when blob is None."""
|
||||
diff = Mock()
|
||||
diff.a_blob = None
|
||||
|
||||
|
||||
result = extract_file_content(diff, is_old=True)
|
||||
assert result == ""
|
||||
|
||||
|
@ -231,8 +243,10 @@ def test_extract_file_content_none_blob():
|
|||
def test_extract_file_content_decode_error():
|
||||
"""Test extracting content with decode error."""
|
||||
diff = Mock()
|
||||
diff.a_blob.data_stream.read.side_effect = UnicodeDecodeError("utf-8", b"", 0, 1, "error")
|
||||
|
||||
diff.a_blob.data_stream.read.side_effect = UnicodeDecodeError(
|
||||
"utf-8", b"", 0, 1, "error"
|
||||
)
|
||||
|
||||
result = extract_file_content(diff, is_old=True)
|
||||
assert result == ""
|
||||
|
||||
|
@ -241,7 +255,7 @@ def test_extract_file_content_decode_error():
|
|||
def test_create_git_diff(mock_diff_item):
|
||||
"""Test creating GitDiff from git diff item."""
|
||||
result = create_git_diff(mock_diff_item)
|
||||
|
||||
|
||||
assert isinstance(result, GitDiff)
|
||||
assert result.file_path == "src/main.py"
|
||||
assert result.old_content == "old content"
|
||||
|
@ -260,7 +274,7 @@ def test_create_git_diff_unknown_path():
|
|||
diff.a_blob = None
|
||||
diff.b_blob = None
|
||||
diff.__str__ = Mock(return_value="diff")
|
||||
|
||||
|
||||
result = create_git_diff(diff)
|
||||
assert result.file_path == "unknown"
|
||||
|
||||
|
@ -276,7 +290,7 @@ def test_process_diff_items():
|
|||
diff1.a_blob = None
|
||||
diff1.b_blob = None
|
||||
diff1.__str__ = Mock(return_value="diff1")
|
||||
|
||||
|
||||
diff2 = Mock()
|
||||
diff2.a_path = "file2.py"
|
||||
diff2.b_path = "file2.py"
|
||||
|
@ -285,11 +299,11 @@ def test_process_diff_items():
|
|||
diff2.a_blob = None
|
||||
diff2.b_blob = None
|
||||
diff2.__str__ = Mock(return_value="diff2")
|
||||
|
||||
|
||||
diff_index = [diff1, diff2]
|
||||
|
||||
|
||||
result = process_diff_items(diff_index)
|
||||
|
||||
|
||||
assert len(result) == 2
|
||||
assert all(isinstance(diff, GitDiff) for diff in result)
|
||||
assert result[0].file_path == "file1.py"
|
||||
|
@ -300,7 +314,7 @@ def test_process_diff_items():
|
|||
def test_filter_reviewable_diffs(sample_git_diffs):
|
||||
"""Test filtering reviewable diffs."""
|
||||
result = filter_reviewable_diffs(sample_git_diffs)
|
||||
|
||||
|
||||
# Should include .py and .js files, exclude .md and .png
|
||||
assert len(result) == 2
|
||||
file_paths = [diff.file_path for diff in result]
|
||||
|
@ -317,15 +331,20 @@ def test_filter_reviewable_diffs_empty():
|
|||
|
||||
|
||||
# Tests for analyze_git_repository
|
||||
@patch('reviewllama.git_diff.find_git_repository')
|
||||
@patch('reviewllama.git_diff.get_current_branch_name')
|
||||
@patch('reviewllama.git_diff.get_base_branch')
|
||||
@patch('reviewllama.git_diff.get_diff_between_branches')
|
||||
@patch('reviewllama.git_diff.process_diff_items')
|
||||
@patch('reviewllama.git_diff.filter_reviewable_diffs')
|
||||
@patch("reviewllama.git_diff.find_git_repository")
|
||||
@patch("reviewllama.git_diff.get_current_branch_name")
|
||||
@patch("reviewllama.git_diff.get_base_branch")
|
||||
@patch("reviewllama.git_diff.get_diff_between_branches")
|
||||
@patch("reviewllama.git_diff.process_diff_items")
|
||||
@patch("reviewllama.git_diff.filter_reviewable_diffs")
|
||||
def test_analyze_git_repository_success(
|
||||
mock_filter, mock_process, mock_get_diff, mock_get_base,
|
||||
mock_get_current, mock_find_repo, sample_git_diffs
|
||||
mock_filter,
|
||||
mock_process,
|
||||
mock_get_diff,
|
||||
mock_get_base,
|
||||
mock_get_current,
|
||||
mock_find_repo,
|
||||
sample_git_diffs,
|
||||
):
|
||||
"""Test successful git repository analysis."""
|
||||
# Setup mocks
|
||||
|
@ -336,10 +355,10 @@ def test_analyze_git_repository_success(
|
|||
mock_get_diff.return_value = Mock()
|
||||
mock_process.return_value = sample_git_diffs
|
||||
mock_filter.return_value = sample_git_diffs[:2] # Filter to 2 items
|
||||
|
||||
|
||||
path = Path("/project")
|
||||
result = analyze_git_repository(path, "main")
|
||||
|
||||
|
||||
# Verify result
|
||||
assert isinstance(result, GitAnalysis)
|
||||
assert result.repository_path == path
|
||||
|
@ -347,7 +366,7 @@ def test_analyze_git_repository_success(
|
|||
assert result.base_branch == "master"
|
||||
assert len(result.diffs) == 2
|
||||
assert result.total_files_changed == 2
|
||||
|
||||
|
||||
# Verify function calls
|
||||
mock_find_repo.assert_called_once_with(path)
|
||||
mock_get_current.assert_called_once_with(mock_repo)
|
||||
|
@ -355,56 +374,12 @@ def test_analyze_git_repository_success(
|
|||
mock_get_diff.assert_called_once_with(mock_repo, "master", "feature-branch")
|
||||
|
||||
|
||||
@patch('reviewllama.git_diff.find_git_repository')
|
||||
@patch("reviewllama.git_diff.find_git_repository")
|
||||
def test_analyze_git_repository_not_found(mock_find_repo):
|
||||
"""Test git repository analysis when repo not found."""
|
||||
mock_find_repo.side_effect = ValueError("No git repository found")
|
||||
|
||||
|
||||
path = Path("/not-a-repo")
|
||||
|
||||
|
||||
with pytest.raises(ValueError, match="No git repository found"):
|
||||
analyze_git_repository(path, "")
|
||||
|
||||
|
||||
# Integration test
|
||||
def test_git_diff_dataclass():
|
||||
"""Test GitDiff dataclass creation and immutability."""
|
||||
diff = GitDiff(
|
||||
file_path="test.py",
|
||||
old_content="old",
|
||||
new_content="new",
|
||||
diff_text="diff",
|
||||
change_type="modified"
|
||||
)
|
||||
|
||||
assert diff.file_path == "test.py"
|
||||
assert diff.old_content == "old"
|
||||
assert diff.new_content == "new"
|
||||
assert diff.diff_text == "diff"
|
||||
assert diff.change_type == "modified"
|
||||
|
||||
# Test immutability
|
||||
with pytest.raises(AttributeError):
|
||||
diff.file_path = "changed.py"
|
||||
|
||||
|
||||
def test_git_diff_dataclass():
|
||||
"""Test GitAnalysis dataclass creation and immutability."""
|
||||
diffs = [GitDiff("test.py", "old", "new", "diff", "modified")]
|
||||
analysis = GitAnalysis(
|
||||
repository_path=Path("/project"),
|
||||
current_branch="feature",
|
||||
base_branch="master",
|
||||
diffs=diffs,
|
||||
total_files_changed=1
|
||||
)
|
||||
|
||||
assert analysis.repository_path == Path("/project")
|
||||
assert analysis.current_branch == "feature"
|
||||
assert analysis.base_branch == "master"
|
||||
assert len(analysis.diffs) == 1
|
||||
assert analysis.total_files_changed == 1
|
||||
|
||||
# Test immutability
|
||||
with pytest.raises(AttributeError):
|
||||
analysis.current_branch = "changed"
|
||||
|
|
35
tests/test_llm.py
Normal file
35
tests/test_llm.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
"""
|
||||
Unit tests for llm chat client functionality
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from reviewllama.configs import create_ollama_config
|
||||
from reviewllama.llm import chat_with_client, create_chat_client
|
||||
from reviewllama.utilities import is_ollama_available
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ollama_config():
|
||||
return create_ollama_config(
|
||||
"gemma3:4b", "localhost:11434", "You are a helpful assistant.", 0.0
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def chat_client(ollama_config):
|
||||
return create_chat_client(ollama_config)
|
||||
|
||||
|
||||
def test_chat_client(ollama_config, chat_client):
|
||||
if not is_ollama_available(ollama_config):
|
||||
pytest.skip("Local Ollama server is not available")
|
||||
|
||||
chat_client = chat_with_client(
|
||||
chat_client, "Tell me your name and introduce yourself briefly"
|
||||
)
|
||||
response = chat_client.get_last_response_or_none()
|
||||
|
||||
assert response is not None
|
||||
assert len(response.content) > 0
|
||||
assert "gemma" in response.content.lower()
|
Loading…
Add table
Add a link
Reference in a new issue