from __future__ import annotations 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, prepare_review_prompt, run_review_for_pr from gitea_codex_bot.types import ParsedCommand def test_fallback_review_surfaces_failure_reason() -> None: result = _fallback_review({"diff": ""}, failure_reason="OpenAI API HTTP 401: invalid_api_key") assert result["verdict"] == "has_issues" assert result["summary"] == "OpenAI review failed. Error: OpenAI API HTTP 401: invalid_api_key" assert result["findings"][0]["title"] == "OpenAI review request failed" assert result["findings"][0]["body"] == "OpenAI API HTTP 401: invalid_api_key" def test_run_review_for_pr_uses_openai_http_error_in_fallback(monkeypatch) -> None: def _fake_prepare(*_args, **_kwargs): return "prompt", {"diff": "TODO: tighten validation"}, RepoReviewConfig() def _raise_http_error(*_args, **_kwargs): request = httpx.Request("POST", "https://api.openai.com/v1/responses") response = httpx.Response(429, request=request, text='{"error":{"message":"rate_limited"}}') raise httpx.HTTPStatusError("rate limited", request=request, response=response) monkeypatch.setattr("gitea_codex_bot.services.reviewer.prepare_review_prompt", _fake_prepare) monkeypatch.setattr("gitea_codex_bot.services.reviewer._call_openai_review", _raise_http_error) settings = get_settings() command = ParsedCommand(name="review", raw="@codex review") result, _repo_cfg = run_review_for_pr(settings, object(), "acme/repo", 9, command) assert result["summary"].startswith("OpenAI review failed. Error: OpenAI API HTTP 429:") 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 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