import contextlib import dataclasses import io import json import os import tempfile import unittest from tests.fakes import HEAD, ROOT, FakeGitea, FakeModel, config, deps, diff_for, gitea_file, pr_ref from werkrevisor.cli import execute, main from werkrevisor.redact import Redactor from werkrevisor.verdict import Verdict def prompt_run(gitea, model, cfg): path = os.path.join(tempfile.mkdtemp(prefix="werkrevisor-test-"), "prompt.json") d = dataclasses.replace(deps(gitea, model), prompt_out=path) outcome = execute(cfg, pr_ref(), d, Redactor(), publish=False) with open(path, encoding="utf-8") as f: return outcome, json.load(f), path class PromptOutTest(unittest.TestCase): def test_the_prompt_goes_to_the_file_instead_of_the_model(self): """Scenario#65.01""" gitea = FakeGitea(files=[gitea_file("src/a.py")], diff=diff_for("src/a.py", added=["x"])) model = FakeModel(prompt_files=["src/a.py"]) outcome, written, path = prompt_run(gitea, model, config(gate={"yellow_gate": "block"})) self.assertEqual(model.calls, []) self.assertEqual((written["system"], written["user"]), model.prompts[-1]) self.assertEqual(outcome.result.llm_status, "skipped (prompt written)") reason = next(r for r in outcome.result.reasons if r.code == "prompt_out") self.assertEqual((reason.text, reason.verdict), (f"model not called: the prompt is in {path}", Verdict.GREEN)) def test_the_file_holds_the_prompt_after_the_size_checks(self): """Scenario#65.02""" text = "".join(f"line number {i} of the large file\n" for i in range(1, 701)).encode() diff = diff_for("src/a.py", added=["x"]).replace("@@ -1,0 +1,1 @@", "@@ -350,0 +350,1 @@") gitea = FakeGitea(files=[gitea_file("src/a.py")], diff=diff) gitea.raw_files["src/a.py"] = text model = FakeModel(prompt_files=["src/a.py"], prompt_words=50) cfg = config(gate={"yellow_gate": "block"}, model={"max_context_tokens": 3000}, review={"full_files": True, "max_full_file_chars": 100000, "max_full_files_chars": 200000, "excerpt_context_lines": 400}) outcome, written, _ = prompt_run(gitea, model, cfg) self.assertGreater(len(model.prompts), 1) self.assertIn("### Full file: 'src/a.py'", model.prompts[0][1]) self.assertEqual(written["user"], model.prompts[-1][1]) self.assertNotIn("line number 1 of the large file", written["user"]) self.assertIn("context_reduced", [r.code for r in outcome.result.reasons]) def test_only_a_dry_run_writes_the_prompt(self): """Scenario#65.03, Scenario#65.04""" with open(os.path.join(ROOT, "config", "werkprobe.toml"), encoding="utf-8") as f: toml = f.read() environ = {"WERKREVISOR_CONFIG_TOML": toml, "GITEA_BOT_TOKEN": "gitea-token-123", "OPENROUTER_API_KEY": "sk-or-v1-test", "TYPESAFE_API_KEY": "ts-test", "WERKATOR_GITEA_BASE_URL": "https://git.example.org", "WERKATOR_GITEA_OWNER": "o", "WERKATOR_GITEA_REPO": "r", "WERKATOR_PR_NUMBER": "1", "WERKATOR_PR_HEAD_SHA": HEAD} for argv in (["--prompt-out", "p.json"], ["--prompt-out", "p.json", "--no-publish", "--no-llm"]): with self.subTest(argv=argv): err = io.StringIO() with contextlib.redirect_stderr(err): self.assertEqual(main(argv, environ), 30) self.assertIn("--prompt-out needs --no-publish and no --no-llm", err.getvalue()) if __name__ == "__main__": unittest.main()