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

1"""Tests for the SARIF bridge reconstruction helpers.""" 

2 

3from __future__ import annotations 

4 

5import json 

6 

7from assertpy import assert_that 

8 

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 

15 

16 

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 ) 

41 

42 suggestions = suggestions_from_results([result]) 

43 

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) 

56 

57 

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) 

61 

62 assert_that(suggestions_from_results([result])).is_empty() 

63 

64 

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 ) 

72 

73 assert_that(suggestions_from_results([result])).is_empty() 

74 

75 

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 ) 

94 

95 summary = summary_from_results([result]) 

96 

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

103 

104 

105def test_summary_from_results_no_metadata() -> None: 

106 """Return None when no summary metadata is present.""" 

107 result = ToolResult(name="ruff", success=True) 

108 

109 assert_that(summary_from_results([result])).is_none() 

110 

111 

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 ) 

124 

125 summary = summary_from_results([result1, result2]) 

126 

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

130 

131 

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 ) 

160 

161 from lintro.ai.output.sarif import render_fixes_sarif 

162 

163 suggestions = suggestions_from_results([result]) 

164 summary = summary_from_results([result]) 

165 sarif_json = render_fixes_sarif(suggestions, summary) 

166 

167 sarif = json.loads(sarif_json) 

168 

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) 

172 

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) 

176 

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

185 

186 assert_that(run["properties"]["aiSummary"]["overview"]).is_equal_to( 

187 "Found 1 issue.", 

188 )