Files
werkrevisor/tests/test_pr_agent_integration.py
claude-code 93b3979350
werkator build succeeded after 00:09
werkator/werkrevisor-statistics build succeeded after 00:30
werkrevisor 0.20.4: Gradle build files reach the model (#35)
Co-authored-by: claude-code <claude-code@javagil.de>
2026-10-06 17:25:18 +02:00

122 lines
6.5 KiB
Python

import unittest
from tests.fakes import config, pr_ref
from werkrevisor.review import ModelRun, coverage_reasons, files_in_prompt, pr_agent_settings, unreadable_files
from werkrevisor.rules import ChangedFile
try:
from pr_agent.config_loader import get_settings
from pr_agent.algo.language_handler import is_valid_file
HAVE_PR_AGENT = True
except ImportError:
HAVE_PR_AGENT = False
@unittest.skipUnless(HAVE_PR_AGENT, "runs inside the PR-Agent image")
class PrAgentSettingsTest(unittest.TestCase):
def test_every_forced_setting_exists_in_pr_agent(self):
values = pr_agent_settings(config(), pr_ref(), "t" * 20, "k" * 20, "extra")
settings = get_settings()
# num_retries is only commented out in configuration.toml but read via get(); the keys are secrets.
not_in_defaults = {"config.num_retries", "openrouter.key", "gitea.personal_access_token"}
for key in values:
if key in not_in_defaults:
continue
with self.subTest(key=key):
section, name = key.split(".", 1)
self.assertIn(name, settings.get(section), f"{key} is not a PR-Agent 0.47.0 setting")
def test_settings_reach_pr_agent(self):
from werkrevisor.review import apply_settings
settings = get_settings()
apply_settings(settings, pr_agent_settings(config(), pr_ref(), "t" * 20, "k" * 20, "extra"))
self.assertEqual(settings.get("GITEA.URL"), "https://git.example.org")
self.assertEqual(settings.get("GITEA.PERSONAL_ACCESS_TOKEN"), "t" * 20)
self.assertEqual(settings.get("OPENROUTER.KEY"), "k" * 20)
self.assertEqual(settings.config.fallback_models, [])
self.assertFalse(settings.config.publish_output)
self.assertEqual(settings.pr_reviewer.extra_instructions, "extra")
self.assertEqual(settings.config.model, config().model.id)
def test_token_limits_reach_the_litellm_handler(self):
from pr_agent.algo.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
from pr_agent.algo.token_budget import get_max_tokens
from werkrevisor.review import apply_settings
cfg = config()
apply_settings(get_settings(), pr_agent_settings(cfg, pr_ref(), "t" * 20, "k" * 20, ""))
handler = LiteLLMAIHandler()
self.assertEqual(handler.get_output_token_limit(cfg.model.id), cfg.model.max_output_tokens)
# Twice the input limit, so a too long prompt reaches werkrevisor's count unshortened.
self.assertEqual(get_max_tokens(cfg.model.id), 2 * 100000 + cfg.model.max_output_tokens)
cfg = config(model={"context_tokens": 120000})
apply_settings(get_settings(), pr_agent_settings(cfg, pr_ref(), "t" * 20, "k" * 20, ""))
self.assertEqual(get_max_tokens(cfg.model.id), 120000)
def test_reviewer_exposes_the_attributes_we_read(self):
from pr_agent.algo import pr_processing
from pr_agent.tools.pr_reviewer import PRReviewer
self.assertTrue(callable(pr_processing.pr_generate_compressed_diff))
self.assertTrue(callable(PRReviewer._load_review_yaml))
self.assertIsNone(PRReviewer.prediction_data)
def test_binary_files_are_not_reviewable(self):
self.assertFalse(is_valid_file("doc/img.png"))
self.assertTrue(is_valid_file("src/main/java/A.java"))
def test_gradle_files_are_reviewable_but_binary_files_are_not(self):
from werkrevisor.review import is_reviewable_by_pr_agent
for path, reviewable in (("settings.gradle", True), ("build.gradle", True), ("build.gradle.kts", True),
("gradle/wrapper/gradle-wrapper.jar", False), ("doc/img.png", False)):
with self.subTest(path=path):
self.assertEqual(is_reviewable_by_pr_agent(path), reviewable)
def test_reviewability_does_not_follow_changed_settings(self):
from werkrevisor.review import is_reviewable_by_pr_agent
settings = get_settings()
original = list(settings.bad_extensions.default)
try:
settings.set("bad_extensions.default", ["py"])
self.assertTrue(is_reviewable_by_pr_agent("src/a.py"))
self.assertFalse(is_reviewable_by_pr_agent("doc/img.png"))
finally:
settings.set("bad_extensions.default", original)
class PromptCoverageTest(unittest.TestCase):
DIFF = ("\n\n## File: 'src/a.py'\n\n__new hunk__\n1 +x\n"
"\n\n## File 'old.py' was deleted\n"
"\n\n## File: 'bad.py'\n\n> **This file could not be read.** PR-Agent failed\n")
def test_files_in_prompt(self):
self.assertEqual(files_in_prompt(self.DIFF), {"src/a.py", "old.py", "bad.py"})
self.assertEqual(unreadable_files(self.DIFF), ["bad.py"])
def test_notice_text_inside_diff_or_full_files_is_content(self):
from werkrevisor import fullfiles
source = 'UNREADABLE = "This file could not be read."\n> **This file could not be read.** PR-Agent failed\n'
diff = ("\n\n## File: 'werkrevisor/review.py'\n\n__new hunk__\n"
"15 UNREADABLE = \"This file could not be read.\"\n16 +x = 1\n"
"\n\n## File: 'docs/prs/2026-10-05-PR#9-x.md'\n\n__new hunk__\n1 +> **This file could not be read.**\n")
section = fullfiles.render_section([fullfiles.render_file("werkrevisor/review.py", source)], "a" * 40)
self.assertEqual(unreadable_files(diff + section), [])
self.assertEqual(files_in_prompt(diff + section), {"werkrevisor/review.py", "docs/prs/2026-10-05-PR#9-x.md"})
@unittest.skipUnless(HAVE_PR_AGENT, "runs inside the PR-Agent image")
def test_real_notice_of_pr_agent_is_detected(self):
from types import SimpleNamespace
from pr_agent.algo.pr_processing import _unreadable_file_notice
notice = _unreadable_file_notice(SimpleNamespace(filename="docs/prs/2026-10-05-PR#9-x.md"))
diff = "\n\n## File: 'src/a.py'\n\n__new hunk__\n1 +x\n" + notice
self.assertEqual(unreadable_files(diff), ["docs/prs/2026-10-05-PR#9-x.md"])
def test_coverage(self):
run = ModelRun(data={}, details={"completion_tokens": 10}, prompt_files={"a.py"})
files = [ChangedFile("a.py", "modified", 1), ChangedFile("b.py", "modified", 2),
ChangedFile("c.png", "modified", 0), ChangedFile("d.py", "renamed", 0)]
codes = [r.code for r in coverage_reasons(files, run, lambda p: not p.endswith(".png"), 4000)]
self.assertEqual(codes, ["skipped_files", "unreviewable_files", "no_text_diff"])
if __name__ == "__main__":
unittest.main()