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()