import io import os import re import shutil import subprocess import sys import tempfile import unittest from unittest import mock from werkrevisor import draftreview, reviewreport SETTINGS = {"base_url": "https://git.example.org", "repository": "team/app", "config": "config/werkrevisor.toml", "read_token_variable": "EXAMPLE_READ_TOKEN"} REPORT = """## AI review of `6d1842aa` (werkrevisor and Codex) ### werkrevisor: 🟠ORANGE / FINDINGS ### Findings 1. **[medium] Missing check** at `src/main/java/net/example/App.java:12-14` ORANGE; Jev: real 0.86 ### Codex """ def write(path, text): with open(path, "w", encoding="utf-8") as f: f.write(text) class LinkTest(unittest.TestCase): def test_a_link_to_the_tool_finds_the_package_of_the_checkout(self): """Scenario#132.01""" root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) with tempfile.TemporaryDirectory() as tmp: link = os.path.join(tmp, "hs-review") os.symlink(os.path.join(root, "tools", "draft-review"), link) proc = subprocess.run([sys.executable, link, "7"], capture_output=True, text=True, cwd=tmp, env={"PATH": os.environ.get("PATH", ""), draftreview.CONFIG_VARIABLE: os.path.join(tmp, "none.toml")}) self.assertNotIn("ModuleNotFoundError", proc.stderr) self.assertIn("draft-review: no local config", proc.stderr) class SettingsTest(unittest.TestCase): def setUp(self): tmp = tempfile.TemporaryDirectory() self.addCleanup(tmp.cleanup) self.path = os.path.join(tmp.name, "draft-review.toml") def test_the_local_config_names_server_repository_config_and_tokens(self): """Scenario#132.01""" write(self.path, 'base_url = "https://git.example.org/"\nrepository = "team/app"\n' 'config = "config/werkrevisor.toml"\nread_token_variable = "EXAMPLE_READ_TOKEN"\n') self.assertEqual(draftreview.load_settings(self.path)["base_url"], "https://git.example.org") def test_a_missing_or_incomplete_local_config_is_named(self): with self.assertRaisesRegex(draftreview.DraftReviewError, "no local config"): draftreview.load_settings(self.path) write(self.path, 'base_url = "http://git.example.org"\nrepository = "app"\nextra = 1\n') with self.assertRaisesRegex(draftreview.DraftReviewError, "unknown keys: extra; missing: config, read_token_variable"): draftreview.load_settings(self.path) def test_secrets_are_read_like_a_shell_would(self): write(self.path, '# tokens\nexport A="one"\nB=\'two\'\nC=three\n\nnot a line\n') self.assertEqual(draftreview.read_secrets(self.path), {"A": "one", "B": "two", "C": "three"}) class ResolveTest(unittest.TestCase): def test_a_number_means_a_pr_of_the_default_repository(self): """Scenario#132.01""" self.assertEqual(draftreview.resolve("343", SETTINGS), ("https://git.example.org/team/app/pulls/343", "team", "app", 343)) def test_a_url_may_name_another_repository_of_the_configured_server(self): url = "https://git.example.org/team/other/pulls/7" self.assertEqual(draftreview.resolve(url, SETTINGS), (url, "team", "other", 7)) def test_a_pr_on_another_server_is_refused(self): """Scenario#132.02""" for pr in ("https://git.example.org.evil.test/team/app/pulls/7", "https://other.example/team/app/pulls/7", "http://git.example.org/team/app/pulls/7"): with self.subTest(pr), self.assertRaises(draftreview.DraftReviewError): draftreview.resolve(pr, SETTINGS) class SummaryTest(unittest.TestCase): def test_the_summary_names_the_verdict_and_its_strongest_finding(self): self.assertEqual(draftreview.summary(REPORT), "verdict: ORANGE — [medium] Missing check (App.java:12-14)") def test_a_finding_without_a_judgment_of_jev_is_named_too(self): self.assertEqual(draftreview.summary(REPORT.replace("ORANGE; Jev: real 0.86", "ORANGE")), "verdict: ORANGE — [medium] Missing check (App.java:12-14)") def test_a_failed_review_names_its_cause(self): text = "### werkrevisor\n\nThe review did not complete: werkrevisor: cannot read the PR\n" self.assertEqual(draftreview.summary(text), "verdict: none — The review did not complete: werkrevisor: cannot read the PR") class HtmlTest(unittest.TestCase): def test_the_report_gets_an_html_page_with_clickable_diagrams(self): """Scenario#132.03""" calls = [] with tempfile.TemporaryDirectory() as tmp: md = os.path.join(tmp, "report.md") def fake_pandoc(cmd, **kw): calls.append(cmd) write(cmd[cmd.index("--output") + 1], "
report
") html = draftreview.write_html(md, "Draft review", fake_pandoc, lambda name: "/usr/bin/pandoc") leftovers = os.listdir(tmp) report = reviewreport.compose("6d1842aa", None, None, codex_error="x", diagrams=["pr-lens-flow.svg"]) self.assertEqual(html, os.path.join(tmp, "report.html")) self.assertEqual(calls[0][:2], ["pandoc", md]) self.assertEqual(leftovers, ["report.html"]) self.assertIn("[](pr-lens-flow.svg)", report) def test_the_page_runs_no_script_of_the_draft(self): """Scenario#132.04""" calls, templates = [], [] def fake_pandoc(cmd, **kw): calls.append(cmd) with open(cmd[cmd.index("--template") + 1], encoding="utf-8") as f: templates.append(f.read()) write(cmd[cmd.index("--output") + 1], 'xa') with tempfile.TemporaryDirectory() as tmp: html = draftreview.write_html(os.path.join(tmp, "report.md"), "t", fake_pandoc, lambda name: "/usr/bin/pandoc") with open(html, encoding="utf-8") as f: page = f.read() self.assertEqual(page, 'xa') cmd, template = calls[0], templates[0] self.assertEqual(cmd[cmd.index("--from") + 1], "markdown_strict-raw_html+pipe_tables") head = template.split("")[0] self.assertLess(head.index("Content-Security-Policy"), head.index("