feat. Enforce repo review config

This commit is contained in:
Space-Banane
2026-05-22 22:37:53 +02:00
parent 71b4341cd4
commit 91401adbed
15 changed files with 254 additions and 28 deletions

View File

@@ -7,6 +7,14 @@ def test_parse_review_command_modes() -> None:
assert cmd.name == "review"
assert cmd.mode == "security"
assert cmd.full is True
assert cmd.mode_explicit is True
def test_parse_review_command_defaults_to_non_explicit_summary_mode() -> None:
cmd = parse_command("@codex review")
assert cmd is not None
assert cmd.mode == "summary"
assert cmd.mode_explicit is False
def test_parse_fix_branch() -> None:
@@ -17,4 +25,4 @@ def test_parse_fix_branch() -> None:
def test_invalid_command_returns_none() -> None:
assert parse_command("hello") is None
assert parse_command("hello") is None

View File

@@ -113,7 +113,7 @@ def test_run_review_ephemeral_chatgpt_does_not_fallback_to_api_key_path(
from gitea_codex_bot.types import ParsedCommand
result = run_review_ephemeral(
result, _repo_cfg = run_review_ephemeral(
settings,
repo="acme/repo",
pr_number=1,

View File

@@ -10,6 +10,7 @@ from gitea_codex_bot.db import get_session_factory
from gitea_codex_bot.models import ReviewJob
from gitea_codex_bot.services.comments import get_persistent_review_comment_id, upsert_persistent_review_comment_id
from gitea_codex_bot.services.jobs import enqueue_job
from gitea_codex_bot.services.repo_config import RepoReviewConfig
from gitea_codex_bot.types import ParsedCommand
from gitea_codex_bot.workers.dispatcher import process_one_job
@@ -37,12 +38,15 @@ def test_process_one_job_recreates_persistent_comment_when_edit_returns_404(monk
monkeypatch.setattr(
"gitea_codex_bot.workers.dispatcher.run_review_ephemeral",
lambda *_args, **_kwargs: {
"verdict": "has_issues",
"confidence": 0.7,
"summary": "runner error",
"findings": [],
},
lambda *_args, **_kwargs: (
{
"verdict": "has_issues",
"confidence": 0.7,
"summary": "runner error",
"findings": [],
},
RepoReviewConfig(configured=True, enabled=True),
),
)
class _FakeGiteaClient:
@@ -91,7 +95,7 @@ def test_process_one_job_passes_full_trigger_message_to_runner(monkeypatch) -> N
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": []}
return {"verdict": "correct", "confidence": 0.9, "summary": "ok", "findings": []}, RepoReviewConfig(configured=True, enabled=True)
class _FakeGiteaClient:
def __init__(self, _settings) -> None:
@@ -113,3 +117,49 @@ def test_process_one_job_passes_full_trigger_message_to_runner(monkeypatch) -> N
processed = process_one_job(settings)
assert processed is True
assert captured["raw"] == "@codex review security --full\nFocus auth/session handling."
def test_process_one_job_skips_review_when_repo_config_disabled(monkeypatch) -> None:
posted_comments: list[str] = []
session_factory = get_session_factory()
with session_factory() as session:
job = enqueue_job(
session,
repo="acme/repo",
pr_number=11,
head_sha="badc0de",
trigger_comment_id=113,
trigger_comment_body="@codex review",
requested_by="alice",
command=ParsedCommand(name="review", raw="@codex review"),
)
monkeypatch.setattr(
"gitea_codex_bot.workers.dispatcher.run_review_ephemeral",
lambda *_args, **_kwargs: (
{"verdict": "correct", "confidence": 1.0, "summary": "ok", "findings": []},
RepoReviewConfig(configured=True, enabled=False),
),
)
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:
posted_comments.append(body)
return 902
monkeypatch.setattr("gitea_codex_bot.workers.dispatcher.GiteaClient", _FakeGiteaClient)
settings = get_settings()
processed = process_one_job(settings)
assert processed is True
assert any("Review is disabled" in body for body in posted_comments)
with session_factory() as session:
stored_job = session.execute(select(ReviewJob).where(ReviewJob.id == job.id)).scalar_one()
assert stored_job.status.value == "skipped"

View File

@@ -55,3 +55,28 @@ def test_format_result_comment_appends_usage_note_for_fallback_layout() -> None:
},
)
assert body.endswith("_Note: model `gpt-5.3-codex`, total `88` tokens used._")
def test_format_result_comment_appends_missing_config_note_for_system_layout() -> None:
body = format_result_comment(
"ff0011",
{
"verdict": "correct",
"confidence": 0.8,
"summary": "No issues.",
"findings": [],
},
repo_configured=False,
)
assert body.endswith(".codex-review.yml is not configured")
def test_format_result_comment_does_not_append_missing_config_note_to_agent_markdown() -> None:
body = format_result_comment(
"ff0011",
{
"markdown_comment": "## Codex Review\n\nLooks fine.",
},
repo_configured=False,
)
assert ".codex-review.yml is not configured" not in body

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 _build_prompt, _fallback_review, run_review_for_pr
from gitea_codex_bot.services.reviewer import _build_prompt, _fallback_review, prepare_review_prompt, run_review_for_pr
from gitea_codex_bot.types import ParsedCommand
@@ -55,3 +55,33 @@ def test_build_prompt_includes_trigger_message() -> None:
)
assert "Trigger message: @codex review security\nPlease focus auth." in prompt
def test_prepare_review_prompt_applies_repo_default_mode_when_command_mode_not_explicit(monkeypatch, tmp_path) -> None:
repo_dir = tmp_path / "repo"
repo_dir.mkdir(parents=True, exist_ok=True)
(repo_dir / ".codex-review.yml").write_text("review:\n default_mode: tests\n", encoding="utf-8")
pr = type(
"PR",
(),
{
"base_sha": "b" * 40,
"head_sha": "a" * 40,
"html_url": "https://gitea.example/pr/1",
},
)()
monkeypatch.setattr("gitea_codex_bot.services.reviewer.checkout_pr", lambda *_args, **_kwargs: repo_dir)
monkeypatch.setattr(
"gitea_codex_bot.services.reviewer.collect_diff_context",
lambda *_args, **_kwargs: {"diff": "", "changed_files": [], "truncated": False},
)
settings = get_settings()
gitea = type("GiteaStub", (), {"get_pull_request": lambda *_args, **_kwargs: pr})()
command = ParsedCommand(name="review", raw="@codex review")
prompt, _diff, _cfg = prepare_review_prompt(settings, gitea, "acme/repo", 9, command)
assert "Mode: tests" in prompt

View File

@@ -64,6 +64,11 @@ def test_webhook_accepts_review_and_queues(monkeypatch) -> None:
return 100
monkeypatch.setattr("gitea_codex_bot.services.gitea.GiteaClient.post_issue_comment", _post_issue_comment)
monkeypatch.setattr(
"gitea_codex_bot.services.gitea.GiteaClient.get_pull_request",
lambda *_args, **_kwargs: type("PR", (), {"head_sha": "abcdef123"})(),
)
monkeypatch.setattr("gitea_codex_bot.services.gitea.GiteaClient.get_file_content", lambda *_args, **_kwargs: None)
client = TestClient(app)
payload_obj = _payload("@codex review security", username="alice", comment_id=111)
@@ -140,3 +145,38 @@ def test_webhook_logs_when_codex_command_is_not_review(monkeypatch) -> None:
assert response.status_code == 200
assert response.json()["status"] == "queued"
assert any("Webhook without @codex review command" in item for item in messages)
def test_webhook_rejects_review_when_repo_config_disabled(monkeypatch) -> None:
posted_comments: list[str] = []
monkeypatch.setattr(
"gitea_codex_bot.services.gitea.GiteaClient.get_pull_request",
lambda *_args, **_kwargs: type("PR", (), {"head_sha": "abcdef123"})(),
)
monkeypatch.setattr(
"gitea_codex_bot.services.gitea.GiteaClient.get_file_content",
lambda *_args, **_kwargs: "enabled: false\n",
)
monkeypatch.setattr(
"gitea_codex_bot.services.gitea.GiteaClient.post_issue_comment",
lambda _self, _repo, _pr, body: posted_comments.append(body) or 100,
)
client = TestClient(app)
payload_obj = _payload("@codex review", username="alice", comment_id=224)
raw = json.dumps(payload_obj).encode()
response = client.post(
"/webhook/gitea",
content=raw,
headers={
"X-Gitea-Event": "issue_comment",
"X-Gitea-Delivery": "d-5",
"X-Gitea-Signature": _sign(raw),
"Content-Type": "application/json",
},
)
assert response.status_code == 200
assert response.json()["reason"] == "review disabled by repo config"
assert any("Review is disabled" in body for body in posted_comments)