feat. Review foot note, docker fix, pass message to reviewer , update tests
Some checks failed
ci / test (push) Failing after 16s
ci / publish (push) Has been skipped

This commit is contained in:
Space-Banane
2026-05-22 22:16:09 +02:00
parent b32bf9eb82
commit e7c7d82f84
18 changed files with 322 additions and 14 deletions

View File

@@ -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

View File

@@ -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."

View File

@@ -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"

View File

@@ -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._")

View File

@@ -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

View File

@@ -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

View File

@@ -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: