feat. Review foot note, docker fix, pass message to reviewer , update tests
This commit is contained in:
@@ -9,6 +9,7 @@ from gitea_codex_bot.workers.container_runner import (
|
||||
CONTAINER_CODEX_HOME,
|
||||
_build_docker_command,
|
||||
_build_install_and_run_command,
|
||||
_extract_result_meta_from_codex_stdout,
|
||||
_load_codex_auth_json_b64,
|
||||
_parse_codex_exec_stdout,
|
||||
_resolve_codex_auth_json_path,
|
||||
@@ -145,3 +146,18 @@ def test_parse_codex_exec_stdout_from_fenced_json_text() -> None:
|
||||
parsed = _parse_codex_exec_stdout(stdout)
|
||||
assert parsed["verdict"] == "has_issues"
|
||||
assert parsed["summary"] == "x"
|
||||
|
||||
|
||||
def test_extract_result_meta_from_codex_stdout_collects_model_and_usage() -> None:
|
||||
settings = get_settings()
|
||||
stdout = '\n'.join(
|
||||
[
|
||||
'{"type":"response.started","model":"gpt-5.3-codex"}',
|
||||
'{"type":"response.completed","response":{"usage":{"input_tokens":101,"output_tokens":22,"total_tokens":123}}}',
|
||||
]
|
||||
)
|
||||
meta = _extract_result_meta_from_codex_stdout(stdout, settings)
|
||||
assert meta["model"] == "gpt-5.3-codex"
|
||||
assert meta["usage"]["input_tokens"] == 101
|
||||
assert meta["usage"]["output_tokens"] == 22
|
||||
assert meta["usage"]["total_tokens"] == 123
|
||||
|
||||
@@ -23,6 +23,7 @@ def test_process_one_job_recreates_persistent_comment_when_edit_returns_404(monk
|
||||
pr_number=9,
|
||||
head_sha="deadbeef",
|
||||
trigger_comment_id=111,
|
||||
trigger_comment_body="@codex review",
|
||||
requested_by="alice",
|
||||
command=ParsedCommand(name="review", raw="@codex review"),
|
||||
)
|
||||
@@ -71,3 +72,44 @@ def test_process_one_job_recreates_persistent_comment_when_edit_returns_404(monk
|
||||
assert persisted_comment_id == 990
|
||||
stored_job = session.execute(select(ReviewJob).where(ReviewJob.id == job.id)).scalar_one()
|
||||
assert stored_job.status.value == "succeeded"
|
||||
|
||||
|
||||
def test_process_one_job_passes_full_trigger_message_to_runner(monkeypatch) -> None:
|
||||
captured: dict[str, str] = {}
|
||||
session_factory = get_session_factory()
|
||||
with session_factory() as session:
|
||||
enqueue_job(
|
||||
session,
|
||||
repo="acme/repo",
|
||||
pr_number=10,
|
||||
head_sha="cafebabe",
|
||||
trigger_comment_id=112,
|
||||
trigger_comment_body="@codex review security --full\nFocus auth/session handling.",
|
||||
requested_by="alice",
|
||||
command=ParsedCommand(name="review", raw="@codex review security --full", arguments=["security", "--full"]),
|
||||
)
|
||||
|
||||
def _fake_run_review_ephemeral(_settings, *, repo: str, pr_number: int, command: ParsedCommand):
|
||||
captured["raw"] = command.raw
|
||||
return {"verdict": "correct", "confidence": 0.9, "summary": "ok", "findings": []}
|
||||
|
||||
class _FakeGiteaClient:
|
||||
def __init__(self, _settings) -> None:
|
||||
pass
|
||||
|
||||
def get_pull_request(self, _repo: str, _pr_number: int):
|
||||
return SimpleNamespace(is_fork=False)
|
||||
|
||||
def post_issue_comment(self, _repo: str, _pr_number: int, _body: str) -> int:
|
||||
return 901
|
||||
|
||||
def edit_issue_comment(self, _repo: str, _comment_id: int, _body: str) -> int:
|
||||
return _comment_id
|
||||
|
||||
monkeypatch.setattr("gitea_codex_bot.workers.dispatcher.run_review_ephemeral", _fake_run_review_ephemeral)
|
||||
monkeypatch.setattr("gitea_codex_bot.workers.dispatcher.GiteaClient", _FakeGiteaClient)
|
||||
|
||||
settings = get_settings()
|
||||
processed = process_one_job(settings)
|
||||
assert processed is True
|
||||
assert captured["raw"] == "@codex review security --full\nFocus auth/session handling."
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from gitea_codex_bot.db import get_session_factory
|
||||
from gitea_codex_bot.models import ReviewJob
|
||||
from gitea_codex_bot.services.jobs import cooldown_remaining_seconds, enqueue_job, persist_webhook_event
|
||||
from gitea_codex_bot.types import ParsedCommand
|
||||
|
||||
@@ -19,7 +20,16 @@ def test_enqueue_and_cooldown() -> None:
|
||||
session_factory = get_session_factory()
|
||||
with session_factory() as session:
|
||||
cmd = ParsedCommand(name="review", raw="@codex review")
|
||||
enqueue_job(session, repo="acme/repo", pr_number=42, head_sha="abc", trigger_comment_id=100, requested_by="user", command=cmd)
|
||||
enqueue_job(
|
||||
session,
|
||||
repo="acme/repo",
|
||||
pr_number=42,
|
||||
head_sha="abc",
|
||||
trigger_comment_id=100,
|
||||
trigger_comment_body="@codex review",
|
||||
requested_by="user",
|
||||
command=cmd,
|
||||
)
|
||||
remaining = cooldown_remaining_seconds(session, "acme/repo", 42, 60)
|
||||
assert remaining >= 0
|
||||
|
||||
@@ -28,11 +38,48 @@ def test_trigger_comment_unique() -> None:
|
||||
session_factory = get_session_factory()
|
||||
with session_factory() as session:
|
||||
cmd = ParsedCommand(name="review", raw="@codex review")
|
||||
enqueue_job(session, repo="acme/repo", pr_number=7, head_sha="x", trigger_comment_id=321, requested_by="user", command=cmd)
|
||||
enqueue_job(
|
||||
session,
|
||||
repo="acme/repo",
|
||||
pr_number=7,
|
||||
head_sha="x",
|
||||
trigger_comment_id=321,
|
||||
trigger_comment_body="@codex review",
|
||||
requested_by="user",
|
||||
command=cmd,
|
||||
)
|
||||
try:
|
||||
enqueue_job(session, repo="acme/repo", pr_number=7, head_sha="x", trigger_comment_id=321, requested_by="user", command=cmd)
|
||||
enqueue_job(
|
||||
session,
|
||||
repo="acme/repo",
|
||||
pr_number=7,
|
||||
head_sha="x",
|
||||
trigger_comment_id=321,
|
||||
trigger_comment_body="@codex review",
|
||||
requested_by="user",
|
||||
command=cmd,
|
||||
)
|
||||
duplicate_raised = False
|
||||
except IntegrityError:
|
||||
duplicate_raised = True
|
||||
session.rollback()
|
||||
assert duplicate_raised is True
|
||||
|
||||
|
||||
def test_enqueue_persists_full_trigger_comment_body() -> None:
|
||||
session_factory = get_session_factory()
|
||||
with session_factory() as session:
|
||||
cmd = ParsedCommand(name="review", raw="@codex review security\nplease focus auth")
|
||||
job = enqueue_job(
|
||||
session,
|
||||
repo="acme/repo",
|
||||
pr_number=55,
|
||||
head_sha="abc123",
|
||||
trigger_comment_id=9191,
|
||||
trigger_comment_body=cmd.raw,
|
||||
requested_by="alice",
|
||||
command=cmd,
|
||||
)
|
||||
stored = session.get(ReviewJob, job.id)
|
||||
assert stored is not None
|
||||
assert stored.trigger_comment_body == "@codex review security\nplease focus auth"
|
||||
|
||||
@@ -28,3 +28,30 @@ def test_format_result_comment_replaces_existing_marker() -> None:
|
||||
assert body.startswith("<!-- codex-review:head_sha=def5678 -->")
|
||||
assert "old" not in body.splitlines()[0]
|
||||
|
||||
|
||||
def test_format_result_comment_appends_usage_note_for_markdown_comment() -> None:
|
||||
body = format_result_comment(
|
||||
"ff0011",
|
||||
{
|
||||
"markdown_comment": "## Codex Review\n\nLooks fine.",
|
||||
"_meta": {
|
||||
"model": "gpt-5.3-codex",
|
||||
"usage": {"input_tokens": 120, "output_tokens": 45, "total_tokens": 165},
|
||||
},
|
||||
},
|
||||
)
|
||||
assert "_Note: model `gpt-5.3-codex`, input `120`, output `45`, total `165` tokens used._" in body
|
||||
|
||||
|
||||
def test_format_result_comment_appends_usage_note_for_fallback_layout() -> None:
|
||||
body = format_result_comment(
|
||||
"ff0011",
|
||||
{
|
||||
"verdict": "correct",
|
||||
"confidence": 0.8,
|
||||
"summary": "No issues.",
|
||||
"findings": [],
|
||||
"_meta": {"model": "gpt-5.3-codex", "usage": {"total_tokens": 88}},
|
||||
},
|
||||
)
|
||||
assert body.endswith("_Note: model `gpt-5.3-codex`, total `88` tokens used._")
|
||||
|
||||
@@ -4,7 +4,7 @@ import httpx
|
||||
|
||||
from gitea_codex_bot.config import get_settings
|
||||
from gitea_codex_bot.services.repo_config import RepoReviewConfig
|
||||
from gitea_codex_bot.services.reviewer import _fallback_review, run_review_for_pr
|
||||
from gitea_codex_bot.services.reviewer import _build_prompt, _fallback_review, run_review_for_pr
|
||||
from gitea_codex_bot.types import ParsedCommand
|
||||
|
||||
|
||||
@@ -37,3 +37,21 @@ def test_run_review_for_pr_uses_openai_http_error_in_fallback(monkeypatch) -> No
|
||||
assert result["findings"][0]["title"] == "OpenAI review request failed"
|
||||
assert "rate_limited" in result["findings"][0]["body"]
|
||||
assert any(finding["title"] == "TODO marker in diff" for finding in result["findings"])
|
||||
|
||||
|
||||
def test_build_prompt_includes_trigger_message() -> None:
|
||||
pr = type("PR", (), {"html_url": "https://gitea.example/pr/1"})()
|
||||
command = ParsedCommand(name="review", raw="@codex review security\nPlease focus auth.")
|
||||
diff_context = {"truncated": False, "changed_files": ["app.py"], "diff": "diff --git a/app.py b/app.py"}
|
||||
repo_cfg = RepoReviewConfig()
|
||||
|
||||
prompt = _build_prompt(
|
||||
pr,
|
||||
command,
|
||||
diff_context,
|
||||
repo_cfg,
|
||||
changed_file_contents="",
|
||||
test_output=None,
|
||||
)
|
||||
|
||||
assert "Trigger message: @codex review security\nPlease focus auth." in prompt
|
||||
|
||||
@@ -17,6 +17,7 @@ def test_claim_and_transition() -> None:
|
||||
pr_number=314,
|
||||
head_sha="deadbeef",
|
||||
trigger_comment_id=9901,
|
||||
trigger_comment_body="@codex review",
|
||||
requested_by="alice",
|
||||
command=ParsedCommand(name="review", raw="@codex review"),
|
||||
)
|
||||
@@ -33,4 +34,4 @@ def test_claim_and_transition() -> None:
|
||||
with session_factory() as session:
|
||||
loaded = session.execute(select(ReviewJob).where(ReviewJob.id == job.id)).scalar_one()
|
||||
assert loaded.status == JobStatus.succeeded
|
||||
assert loaded.result_json is not None
|
||||
assert loaded.result_json is not None
|
||||
|
||||
@@ -6,8 +6,11 @@ import json
|
||||
from typing import Any
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy import select
|
||||
|
||||
from gitea_codex_bot.main import app
|
||||
from gitea_codex_bot.db import get_session_factory
|
||||
from gitea_codex_bot.models import ReviewJob
|
||||
|
||||
|
||||
def _sign(payload: bytes) -> str:
|
||||
@@ -79,6 +82,10 @@ def test_webhook_accepts_review_and_queues(monkeypatch) -> None:
|
||||
assert response.status_code == 200
|
||||
assert response.json()["status"] == "queued"
|
||||
assert posted_comments
|
||||
session_factory = get_session_factory()
|
||||
with session_factory() as session:
|
||||
queued = session.execute(select(ReviewJob).where(ReviewJob.trigger_comment_id == 111)).scalar_one()
|
||||
assert queued.trigger_comment_body == "@codex review security"
|
||||
|
||||
|
||||
def test_webhook_logs_when_no_codex_review_command(monkeypatch) -> None:
|
||||
|
||||
Reference in New Issue
Block a user