Coverage for tests / unit / ai / test_sarif_bridge.py: 100%
64 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
1"""Tests for the SARIF bridge reconstruction helpers."""
3from __future__ import annotations
5import json
7from assertpy import assert_that
9from lintro.ai.models import AISummary
10from lintro.ai.output.sarif_bridge import (
11 suggestions_from_results,
12 summary_from_results,
13)
14from lintro.models.core.tool_result import ToolResult
17def test_suggestions_from_results_with_metadata() -> None:
18 """Reconstruct AIFixSuggestion objects from ai_metadata dicts."""
19 result = ToolResult(
20 name="ruff",
21 success=True,
22 ai_metadata={
23 "fix_suggestions": [
24 {
25 "file": "src/main.py",
26 "line": 10,
27 "code": "B101",
28 "tool_name": "bandit",
29 "original_code": "assert x > 0",
30 "suggested_code": "if not x > 0:\n raise ValueError",
31 "explanation": "Replace assert with if/raise",
32 "confidence": "high",
33 "risk_level": "safe-style",
34 "input_tokens": 150,
35 "output_tokens": 80,
36 "cost_estimate": 0.002,
37 },
38 ],
39 },
40 )
42 suggestions = suggestions_from_results([result])
44 assert_that(suggestions).is_length(1)
45 s = suggestions[0]
46 assert_that(s.file).is_equal_to("src/main.py")
47 assert_that(s.line).is_equal_to(10)
48 assert_that(s.code).is_equal_to("B101")
49 assert_that(s.tool_name).is_equal_to("bandit")
50 assert_that(s.explanation).is_equal_to("Replace assert with if/raise")
51 assert_that(s.confidence).is_equal_to("high")
52 assert_that(s.risk_level).is_equal_to("safe-style")
53 assert_that(s.input_tokens).is_equal_to(150)
54 assert_that(s.output_tokens).is_equal_to(80)
55 assert_that(s.cost_estimate).is_equal_to(0.002)
58def test_suggestions_from_results_no_metadata() -> None:
59 """Return empty list when no AI metadata is present."""
60 result = ToolResult(name="ruff", success=True)
62 assert_that(suggestions_from_results([result])).is_empty()
65def test_suggestions_from_results_empty_fix_suggestions() -> None:
66 """Return empty list when fix_suggestions key is an empty list."""
67 result = ToolResult(
68 name="ruff",
69 success=True,
70 ai_metadata={"fix_suggestions": []},
71 )
73 assert_that(suggestions_from_results([result])).is_empty()
76def test_summary_from_results_with_metadata() -> None:
77 """Reconstruct AISummary from ai_metadata dict."""
78 result = ToolResult(
79 name="ruff",
80 success=True,
81 ai_metadata={
82 "summary": {
83 "overview": "Found 3 issues across 2 files.",
84 "key_patterns": ["assert usage", "line length"],
85 "priority_actions": ["Replace asserts"],
86 "triage_suggestions": ["E501 may be intentional"],
87 "estimated_effort": "30 minutes",
88 "input_tokens": 500,
89 "output_tokens": 200,
90 "cost_estimate": 0.01,
91 },
92 },
93 )
95 summary = summary_from_results([result])
97 assert_that(summary).is_not_none()
98 assert summary is not None # narrow type for mypy
99 assert_that(summary).is_instance_of(AISummary)
100 assert_that(summary.overview).is_equal_to("Found 3 issues across 2 files.")
101 assert_that(summary.key_patterns).is_equal_to(["assert usage", "line length"])
102 assert_that(summary.estimated_effort).is_equal_to("30 minutes")
105def test_summary_from_results_no_metadata() -> None:
106 """Return None when no summary metadata is present."""
107 result = ToolResult(name="ruff", success=True)
109 assert_that(summary_from_results([result])).is_none()
112def test_summary_from_results_picks_first() -> None:
113 """Return summary from the first result that has one."""
114 result1 = ToolResult(
115 name="ruff",
116 success=True,
117 ai_metadata={"summary": {"overview": "First summary"}},
118 )
119 result2 = ToolResult(
120 name="mypy",
121 success=True,
122 ai_metadata={"summary": {"overview": "Second summary"}},
123 )
125 summary = summary_from_results([result1, result2])
127 assert_that(summary).is_not_none()
128 assert summary is not None # narrow type for mypy
129 assert_that(summary.overview).is_equal_to("First summary")
132def test_sarif_format_end_to_end() -> None:
133 """ToolResult with AI metadata produces valid SARIF JSON output."""
134 result = ToolResult(
135 name="ruff",
136 success=True,
137 ai_metadata={
138 "fix_suggestions": [
139 {
140 "file": "src/main.py",
141 "line": 10,
142 "code": "B101",
143 "tool_name": "bandit",
144 "original_code": "assert x > 0",
145 "suggested_code": "if not x > 0:\n raise ValueError",
146 "explanation": "Replace assert with if/raise",
147 "confidence": "high",
148 "risk_level": "safe-style",
149 },
150 ],
151 "summary": {
152 "overview": "Found 1 issue.",
153 "key_patterns": ["assert usage"],
154 "priority_actions": ["Replace asserts"],
155 "triage_suggestions": [],
156 "estimated_effort": "5 minutes",
157 },
158 },
159 )
161 from lintro.ai.output.sarif import render_fixes_sarif
163 suggestions = suggestions_from_results([result])
164 summary = summary_from_results([result])
165 sarif_json = render_fixes_sarif(suggestions, summary)
167 sarif = json.loads(sarif_json)
169 assert_that(sarif["version"]).is_equal_to("2.1.0")
170 assert_that(sarif).contains_key("$schema")
171 assert_that(sarif["runs"]).is_length(1)
173 run = sarif["runs"][0]
174 assert_that(run["tool"]["driver"]["name"]).is_equal_to("lintro-ai")
175 assert_that(run["results"]).is_length(1)
177 sarif_result = run["results"][0]
178 assert_that(sarif_result["ruleId"]).is_equal_to("bandit/B101")
179 assert_that(sarif_result["message"]["text"]).is_equal_to(
180 "Replace assert with if/raise",
181 )
182 assert_that(
183 sarif_result["locations"][0]["physicalLocation"]["artifactLocation"]["uri"],
184 ).is_equal_to("src/main.py")
186 assert_that(run["properties"]["aiSummary"]["overview"]).is_equal_to(
187 "Found 1 issue.",
188 )