Files
claude-code 9c7ab5aa83
werkator build succeeded after 00:09
werkator/werkrevisor-statistics build succeeded after 00:30
werkrevisor 0.30.0: each repository chooses and tunes its risk categories in .werkrevisor.toml (#63)
Co-authored-by: claude-code <claude-code@javagil.de>
2026-10-08 22:09:37 +02:00

107 lines
5.0 KiB
Python

import os
import tomllib
import unittest
from tests.fakes import ROOT, FakeJev, FakeModel
from tests.test_flow import run
from tests.test_pathjudge import APP, APP_TEST, all_gitea
from werkrevisor import risk
from werkrevisor.config import ConfigError, build_config
from werkrevisor.verdict import Verdict
TEMPLATE = "jvm-spring-backend"
OWN = {"name": "build_slots", "title": "Build slots", "question": "Does the change alter how build slots are taken?",
"yellow_min": 0.9, "action": "yellow"}
def host(**path_judge):
with open(os.path.join(ROOT, "config", "werkprobe.toml"), "rb") as f:
data = tomllib.load(f)
data["engine"] = {"mode": "combined", "model_role": "advises"}
data["gate"].update(yellow_gate="block", require_wip_transition=False, automerge=False)
data["hard_yellow"].update(paths=[], judged_paths=["server/*.gradle.kts"])
data["path_judge"] = {"judge": "jev", "scope": "all", **path_judge}
return data
def repo_config(categories=(), template=TEMPLATE, **host_path_judge):
repo = {"path_judge": {"risk_template": template}} if template else {}
if categories:
repo["risk"] = {"categories": list(categories)}
return build_config(host(**host_path_judge), repo)
def repo_run(jev, cfg):
return run(all_gitea(APP, APP_TEST), FakeModel(prompt_files=[APP, APP_TEST]), cfg=cfg, jev=jev)
def app_questions(jev):
return next(q for s, q in jev.requests if s.startswith(f"Changed file: {APP}"))
def reason(outcome, code):
return next(r for r in outcome.result.reasons if r.code == code)
class RiskInRepositoryConfigTest(unittest.TestCase):
def test_the_repository_chooses_the_template(self):
"""Scenario#63.01"""
jev = FakeJev()
repo_run(jev, repo_config())
self.assertLessEqual({c.name for c in risk.load(TEMPLATE)}, set(app_questions(jev)))
def test_the_repository_changes_the_threshold_or_the_consequence_of_a_category(self):
"""Scenario#63.02"""
cfg = repo_config([{"name": "concurrency", "action": "hint"}])
outcome, _ = repo_run(FakeJev(problems={(APP, "concurrency"): 0.97}), cfg)
self.assertIs(outcome.result.verdict, Verdict.GREEN)
self.assertIs(reason(outcome, "risk_concurrency").verdict, Verdict.GREEN)
cfg = repo_config([{"name": "authorization", "yellow_min": 0.5}])
outcome, _ = repo_run(FakeJev(problems={(APP, "authorization"): 0.6}), cfg)
self.assertIs(outcome.result.verdict, Verdict.YELLOW)
def test_the_repository_switches_a_category_off(self):
"""Scenario#63.03"""
jev = FakeJev()
repo_run(jev, repo_config([{"name": "sql_injection", "action": "off"}]))
self.assertNotIn("sql_injection", app_questions(jev))
self.assertIn("authorization", app_questions(jev))
def test_the_repository_adds_an_own_category(self):
"""Scenario#63.04"""
outcome, _ = repo_run(FakeJev(problems={(APP, "build_slots"): 0.95}), repo_config([OWN]))
self.assertIs(outcome.result.verdict, Verdict.YELLOW)
self.assertTrue(reason(outcome, "risk_build_slots").text.startswith(f"Build slots according to Jev: {APP} (0.95)"))
# Without a template, the own categories are all there is.
jev = FakeJev()
repo_run(jev, repo_config([OWN], template=None))
self.assertEqual(set(app_questions(jev)), {"weakens_checks", "security", "build_slots"})
def test_the_host_can_forbid_the_tuning(self):
"""Scenario#63.05"""
for categories, template in (((), TEMPLATE), ([OWN], None)):
with self.subTest(categories=categories, template=template):
with self.assertRaisesRegex(ConfigError, r"must not set \[(path_judge|risk)\] (risk_template|categories): "
r"the host config does not allow it"):
repo_config(categories, template, repository_may_tune_risks=False)
class RiskOverrideValidationTest(unittest.TestCase):
def test_invalid_entries_are_refused(self):
for entry, message in (({"name": "concurrency", "question": "Other?"}, r"concurrency: only yellow_min, orange_min "
r"and action can change a template category"),
({"name": "own", "action": "yellow"}, r"own: title must be a non-empty string"),
({"name": "concurrency", "action": "maybe"}, r"action must be one of yellow, hint, off"),
({"action": "hint"}, r"name must be an identifier")):
with self.subTest(entry=entry):
with self.assertRaisesRegex(ConfigError, message):
repo_config([entry])
def test_the_host_keeps_the_switch(self):
with self.assertRaisesRegex(ConfigError, r"must not set \[path_judge\] repository_may_tune_risks"):
build_config(host(), {"path_judge": {"repository_may_tune_risks": True}})
if __name__ == "__main__":
unittest.main()