"""Unit tests for the opencode engine glue (no network, no opencode run).""" import io import json import os import sys import tarfile HERE = os.path.dirname(os.path.abspath(__file__)) ROOT = os.path.abspath(os.path.join(HERE, "..", "..", "..")) sys.path.insert(0, os.path.join(ROOT, "pilot")) import opencode_review as oc # noqa: E402 # --------------------------------------------------------------------------- # write_brief # --------------------------------------------------------------------------- def test_no_surface_response_parses_as_an_empty_review(): # The skip path must return the same shape every other path returns. # A bare "" landed in ai_review's unparseable-output branch and posted # "AI review produced no parseable output" — a malfunction, not a verdict. import ai_review text, usage = oc._no_surface_response("o/r", "9", "abc12345", 3) assert usage is None summary, findings, _changes, _risks, _walkthrough, _risk_verdict, _test_coverage = ( ai_review.parse_review_output(text) ) assert findings == [] assert summary # non-empty, so ai_review does NOT take the salvage branch assert "no review surface" in summary.lower() assert "3 configured lens" in summary def test_no_surface_response_zero_lenses_wording(): import ai_review text, _ = oc._no_surface_response("o/r", "9", "abc12345", 0) summary, findings, _c, _r, _w, _rv, _tc = ai_review.parse_review_output(text) assert findings == [] assert "after path filtering" in summary # --------------------------------------------------------------------------- # _synthesize_summary_fields — Task 8: real Python fallback implementation # --------------------------------------------------------------------------- def test_synthesize_walkthrough_groups_findings_by_path(): findings = [ {"path": "a.py", "line": 1, "severity": "medium", "problem": "fix x"}, {"path": "b.py", "line": 2, "severity": "high", "problem": "fix y"}, ] w, _, _ = oc._synthesize_summary_fields(findings, "") assert any("a.py" in line for line in w) assert any("b.py" in line for line in w) def test_synthesize_walkthrough_empty_when_no_findings_uses_changed_files(): w, _, _ = oc._synthesize_summary_fields( [], "diff --git a/x.py b/x.py\n@@ -1 +1 @@\n-old\n+new\n+++ b/x.py\n", ) assert any("x.py" in line for line in w) def test_synthesize_risk_verdict_critical(): findings = [{"severity": "critical"}] _, rv, _ = oc._synthesize_summary_fields(findings, "") assert "Critical risk" in rv def test_synthesize_risk_verdict_clean(): _, rv, _ = oc._synthesize_summary_fields([], "") assert "Low risk" in rv def test_synthesize_test_coverage_with_test_path(): _, _, tc = oc._synthesize_summary_fields( [], "+diff\n", changed_paths=["pilot/foo.py", "tests/test_foo.py"]) assert tc == "Tests changed" def test_synthesize_test_coverage_missing_tests(): _, _, tc = oc._synthesize_summary_fields( [], "+diff\n", changed_paths=["pilot/foo.py"]) assert "No tests for behavioral change" in tc def test_synthesize_walkthrough_picks_peak_severity_per_path(): # Three findings on the same path, with mixed severities. The walkthrough # headline should use the PEAK severity's emoji (critical = 🔴), not the # lexicographic-first severity (low). findings = [ {"path": "x.py", "line": 1, "severity": "low", "problem": "minor nit"}, {"path": "x.py", "line": 5, "severity": "critical", "problem": "sql injection"}, {"path": "x.py", "line": 9, "severity": "high", "problem": "auth bypass"}, ] w, _, _ = oc._synthesize_summary_fields(findings, "") assert len(w) == 1 line = w[0] assert "`x.py`" in line assert "🔴" in line # critical = 🔴 assert "🟡" not in line assert "🔵" not in line assert "sql injection" in line # critical finding's problem, not low's def test_synthesize_summary_fields_none_findings_safe(): # Old code crashed in risk_verdict with `for f in findings:` on None. # After the `findings = findings or []` guard, None behaves like []. w, rv, tc = oc._synthesize_summary_fields(None, "") assert isinstance(w, list) assert rv.startswith("Low risk") # walkthrough should fall through to the diff-derived path list — empty # diff produces no lines, but no crash is the point. assert tc == "" def test_synthesize_walkthrough_empty_problem_does_not_crash(): # An empty `problem` should render as "`a.py` — emoji" with a trailing # space, not raise. Regression guard for splitlines()[0][:80].strip(). findings = [{"path": "a.py", "line": 1, "severity": "low", "problem": ""}] w, _, _ = oc._synthesize_summary_fields(findings, "") assert len(w) == 1 assert "`a.py`" in w[0] assert "🔵" in w[0] # low severity emoji