ai-code-review-agent / tests /test_code_review_agent_extended.py
Padmanav's picture
fixed lint
a79668c
Raw
History Blame Contribute Delete
4.13 kB
# tests/test_code_review_agent_extended.py
from unittest.mock import AsyncMock, patch
import pytest
from app.agents import code_review_agent
from app.models.repository import RepositoryMetadata
def _metadata(
base_sha: str | None = None, head_sha: str | None = None
) -> RepositoryMetadata:
return RepositoryMetadata(
url="https://github.com/test/repo",
name="repo",
local_path="/tmp/fake",
base_sha=base_sha,
head_sha=head_sha,
)
@pytest.mark.asyncio
async def test_code_review_uses_changed_files_when_shas_provided(tmp_path) -> None:
"""When base_sha and head_sha are set, get_changed_files is called."""
src = tmp_path / "main.py"
src.write_text("def foo(): pass\n")
with (
patch(
"app.agents.code_review_agent.get_changed_files",
return_value=[str(src)],
) as mock_diff,
patch(
"app.agents.code_review_agent.call_llm_for_json",
new=AsyncMock(
return_value={
"solid_violations": [],
"duplicate_code": [],
"refactor_suggestions": [],
"overall_score": 7.0,
"summary": "ok",
}
),
),
):
result = await code_review_agent.run(
str(tmp_path),
_metadata(base_sha="abc", head_sha="def"),
)
mock_diff.assert_called_once()
assert result.overall_score == 7.0
@pytest.mark.asyncio
async def test_code_review_skips_test_files(tmp_path) -> None:
"""Files named test_*.py are excluded from the review."""
test_file = tmp_path / "test_something.py"
test_file.write_text("def test_x(): pass\n")
with patch(
"app.agents.code_review_agent.call_llm_for_json",
new=AsyncMock(return_value={}),
):
result = await code_review_agent.run(str(tmp_path), _metadata())
# Only test files present → no source samples → score 0
assert result.overall_score == 0.0
@pytest.mark.asyncio
async def test_code_review_low_score_triggers_followup(tmp_path) -> None:
"""A score below 5.0 triggers a second LLM call for priority fixes."""
src = tmp_path / "main.py"
src.write_text("def foo(): pass\n")
responses = iter(
[
# first call: main review — score is 3.0 (triggers second pass)
{
"solid_violations": ["SRP violation"],
"duplicate_code": [],
"refactor_suggestions": [],
"overall_score": 3.0,
"summary": "poor code",
},
# auto-fix calls (top_findings[:3], but solid_violations has 1 item)
{
"file": "",
"original_snippet": "",
"fixed_snippet": "",
"explanation": "n/a",
},
# second pass: priority fixes
{"priority_fixes": ["fix SRP first"]},
]
)
with patch(
"app.agents.code_review_agent.call_llm_for_json",
new=AsyncMock(side_effect=lambda *a, **kw: next(responses)),
):
result = await code_review_agent.run(str(tmp_path), _metadata())
assert "fix SRP first" in result.refactor_suggestions
@pytest.mark.asyncio
async def test_code_review_handles_file_read_error(tmp_path) -> None:
"""A file that raises on open is skipped gracefully."""
src = tmp_path / "main.py"
src.write_text("def foo(): pass\n")
original_open = open
def patched_open(path, *args, **kwargs): # type: ignore[no-untyped-def]
if str(path).endswith("main.py"):
raise OSError("permission denied")
return original_open(path, *args, **kwargs)
with (
patch("builtins.open", side_effect=patched_open),
patch(
"app.agents.code_review_agent.call_llm_for_json",
new=AsyncMock(return_value={}),
),
):
result = await code_review_agent.run(str(tmp_path), _metadata())
# No readable files → falls back to empty review
assert result.overall_score == 0.0