"""Tests for recognising mechanically-fixable defects in console evidence.""" from __future__ import annotations from ariadne.services import hermes_code_defects as module CFG = {"allowed_path_prefixes": ["ariadne/", "src/"], "allowed_suffixes": [".py", ".go", ".ts"]} def _bundle(*lines: str) -> dict: return {"jenkins": {"console_failures": [{"text": "\n".join(lines)}], "console_tail": ""}} def test_ruff_diagnostic_is_extracted_whole() -> None: found = module.extract_lint_defects( _bundle("ariadne/app.py:12:5: E501 Line too long (95 > 88)"), CFG ) assert found == [ { "category": module.LINT_VIOLATION, "tool": "ruff", "path": "ariadne/app.py", "line": 12, "rule": "E501", "message": "Line too long (95 > 88)", } ] def test_golangci_diagnostic_is_extracted() -> None: found = module.extract_lint_defects(_bundle("src/main.go:9:2: unused variable x (govet)"), CFG) assert found[0]["tool"] == "golangci" assert (found[0]["path"], found[0]["line"], found[0]["rule"]) == ("src/main.go", 9, "govet") def test_eslint_diagnostics_inherit_the_preceding_file_header() -> None: """eslint prints the path once, then indented diagnostics beneath it.""" found = module.extract_lint_defects( _bundle("src/panel.ts", " 12:5 error Unexpected console statement no-console"), CFG ) assert (found[0]["path"], found[0]["line"], found[0]["rule"]) == ("src/panel.ts", 12, "no-console") def test_a_file_outside_the_write_allowlist_is_dropped() -> None: """Reporting a defect the patcher could never write is worse than silence.""" assert module.extract_lint_defects(_bundle("vendor/x.py:1:1: E501 Line too long"), CFG) == [] assert module.extract_lint_defects(_bundle("ariadne/x.rb:1:1: E501 Line too long"), CFG) == [] def test_duplicate_diagnostics_are_collapsed() -> None: line = "ariadne/app.py:12:5: E501 Line too long (95 > 88)" assert len(module.extract_lint_defects(_bundle(line, line, line), CFG)) == 1 def test_extraction_is_bounded() -> None: lines = [f"ariadne/f{i}.py:{i}:1: E501 Line too long" for i in range(50)] assert len(module.extract_lint_defects(_bundle(*lines), CFG)) == module._MAX_DEFECTS def test_ordinary_console_noise_matches_nothing() -> None: noise = _bundle("Running tests...", "OK", "+ ruff check ariadne", "All checks passed!") assert module.extract_lint_defects(noise, CFG) == [] def test_never_raises_on_hostile_input() -> None: for bad in (None, {}, {"jenkins": None}, {"jenkins": {"console_failures": "no"}}, 7): assert module.extract_lint_defects(bad, CFG) == [] assert module.extract_lint_defects(_bundle("ariadne/a.py:1:1: E1 x"), None) == [] def test_instruction_names_rule_and_line() -> None: defects = module.extract_lint_defects(_bundle("ariadne/app.py:12:5: E501 Line too long"), CFG) text = module.defect_instruction(defects) assert "ariadne/app.py line 12: E501" in text assert "do not suppress the rule" in text def test_no_defects_yields_no_instruction() -> None: """The caller must fall back to the open-ended request, not an empty header.""" assert module.defect_instruction([]) == "" def test_the_patch_prompt_carries_the_linter_diagnosis(monkeypatch) -> None: """The instruction must reach Hermes, not just be computed.""" from ariadne.services import hermes_code_prompt bundle = { "jenkins": { "console_failures": [{"text": "ariadne/app.py:12:5: E501 Line too long (95 > 88)"}], "console_tail": "", } } cfg = { "owner": "bstein", "repo": "ariadne", "base_branch": "master", "allowed_path_prefixes": ["ariadne/"], "allowed_suffixes": [".py"], } prompt = hermes_code_prompt.build_patch_prompt("ariadne/1", bundle, cfg, {"ariadne/app.py": "x"}) assert "ariadne/app.py line 12: E501" in prompt assert "do not suppress the rule" in prompt def test_the_patch_prompt_is_unchanged_without_diagnostics() -> None: """A build with no linter output must fall back to the open request.""" from ariadne.services import hermes_code_prompt bundle = {"jenkins": {"console_failures": [{"text": "boom"}], "console_tail": ""}} cfg = {"owner": "b", "repo": "r", "base_branch": "main", "allowed_path_prefixes": ["ariadne/"], "allowed_suffixes": [".py"]} prompt = hermes_code_prompt.build_patch_prompt("a/1", bundle, cfg, {"ariadne/a.py": "x"}) assert "A linter reported these violations" not in prompt assert "MINIMAL source fix" in prompt