repograg / tests /test_answer.py
AAYUSH1003's picture
Swap answer generation from Anthropic to Gemini (free tier)
ca003a8
Raw History Blame Contribute Delete
3.6 kB
from repograg.answer import (
AnswerGenerator,
build_generator,
extract_citations,
validate_citations,
)
from repograg.retrieval import RetrievalResult
def make_result(file_path="src/elo.py", start=10, end=20, name="update_elo"):
return RetrievalResult(
chunk={
"id": 1,
"file_path": file_path,
"start_line": start,
"end_line": end,
"name": name,
"parent": "",
"kind": "function",
"language": "python",
"text": "def update_elo(): ...",
"header": f"# {file_path}:{start}-{end}",
"calls": "[]",
},
score=1.0,
sources=["bm25"],
)
class StubResponse:
def __init__(self, text):
self.text = text
class StubClient:
"""Mimics google-genai's Client: returns queued responses in order."""
def __init__(self, *texts):
self._texts = list(texts)
self.calls = 0
self.last_kwargs = None
outer = self
class Models:
def generate_content(self, **kwargs):
outer.calls += 1
outer.last_kwargs = kwargs
return StubResponse(outer._texts.pop(0))
self.models = Models()
def test_extract_citations():
cits = extract_citations(
"Ratings update in update_elo [src/elo.py:10-20], "
"using expected_score [src/elo.py:3]."
)
assert len(cits) == 2
assert cits[0].file_path == "src/elo.py"
assert (cits[0].start_line, cits[0].end_line) == (10, 20)
assert (cits[1].start_line, cits[1].end_line) == (3, 3)
def test_validate_citations_rejects_fabricated():
results = [make_result()]
cits = extract_citations(
"Real [src/elo.py:12-15]. Fabricated file [src/fake.py:1-5]. "
"Out of range [src/elo.py:100-110]."
)
valid = validate_citations(cits, results)
assert len(valid) == 1
assert valid[0].file_path == "src/elo.py"
def test_answer_with_valid_citations():
gen = AnswerGenerator(client=StubClient(
"update_elo mutates the ratings dict [src/elo.py:10-20]."
))
ans = gen.answer("how do ratings update", [make_result()])
assert ans.citations
assert gen.client.calls == 1
def test_answer_retries_when_uncited():
gen = AnswerGenerator(client=StubClient(
"Ratings update in place.", # no citations → retry
"Ratings update in place [src/elo.py:10-20].",
))
ans = gen.answer("how do ratings update", [make_result()])
assert gen.client.calls == 2
assert ans.citations
assert "[src/elo.py:10-20]" in ans.text
def test_answer_handles_blocked_response():
# google-genai returns text=None when the response is blocked/empty
gen = AnswerGenerator(client=StubClient(None))
ans = gen.answer("how do ratings update", [make_result()])
assert ans.text == ""
assert not ans.citations
assert gen.client.calls == 1 # no retry on empty text
def test_conversation_roles_mapped_for_gemini():
# the corrective retry sends the assistant turn back as role "model"
stub = StubClient(
"Uncited first answer.",
"Cited [src/elo.py:10-20].",
)
AnswerGenerator(client=stub).answer("q", [make_result()])
roles = [c["role"] for c in stub.last_kwargs["contents"]]
assert roles == ["user", "model", "user"]
def test_build_generator_disabled_without_key(monkeypatch):
monkeypatch.delenv("GEMINI_API_KEY", raising=False)
monkeypatch.delenv("GOOGLE_API_KEY", raising=False)
assert build_generator() is None