Spaces:
Running
Running
File size: 4,134 Bytes
229af03 a79668c 229af03 a79668c 229af03 a79668c 229af03 a79668c 229af03 a79668c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | # 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
|