Coverage for tests / unit / tools / semgrep / test_error_handling.py: 100%
63 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"""Unit tests for Semgrep plugin error handling."""
3from __future__ import annotations
5import json
6import subprocess
7from pathlib import Path
8from typing import TYPE_CHECKING
9from unittest.mock import patch
11from assertpy import assert_that
13from lintro.parsers.semgrep.semgrep_parser import parse_semgrep_output
14from lintro.tools.definitions.semgrep import SemgrepPlugin
16if TYPE_CHECKING:
17 pass
20# =============================================================================
21# Tests for timeout handling
22# =============================================================================
25def test_check_with_timeout(
26 semgrep_plugin: SemgrepPlugin,
27 tmp_path: Path,
28) -> None:
29 """Check handles timeout correctly.
31 Args:
32 semgrep_plugin: The SemgrepPlugin instance to test.
33 tmp_path: Temporary directory path for test files.
34 """
35 test_file = tmp_path / "large_file.py"
36 test_file.write_text('"""Large file that takes too long."""\n')
38 with patch(
39 "lintro.plugins.execution_preparation.verify_tool_version",
40 return_value=None,
41 ):
42 with patch.object(
43 semgrep_plugin,
44 "_run_subprocess",
45 side_effect=subprocess.TimeoutExpired(cmd=["semgrep"], timeout=120),
46 ):
47 result = semgrep_plugin.check([str(test_file)], {})
49 assert_that(result.success).is_false()
50 assert_that(result.output).contains("timed out")
53def test_check_with_json_parse_error(
54 semgrep_plugin: SemgrepPlugin,
55 tmp_path: Path,
56) -> None:
57 """Check handles JSON parse errors gracefully.
59 Args:
60 semgrep_plugin: The SemgrepPlugin instance to test.
61 tmp_path: Temporary directory path for test files.
62 """
63 test_file = tmp_path / "test_module.py"
64 test_file.write_text('"""Test module."""\n')
66 # Invalid JSON output
67 invalid_output = "Error: Something went wrong\n{invalid json"
69 with patch(
70 "lintro.plugins.execution_preparation.verify_tool_version",
71 return_value=None,
72 ):
73 with patch.object(
74 semgrep_plugin,
75 "_run_subprocess",
76 return_value=(False, invalid_output),
77 ):
78 result = semgrep_plugin.check([str(test_file)], {})
80 assert_that(result.success).is_false()
81 assert_that(result.issues_count).is_equal_to(0)
84def test_check_with_semgrep_errors(
85 semgrep_plugin: SemgrepPlugin,
86 tmp_path: Path,
87) -> None:
88 """Check handles Semgrep errors in response.
90 Args:
91 semgrep_plugin: The SemgrepPlugin instance to test.
92 tmp_path: Temporary directory path for test files.
93 """
94 test_file = tmp_path / "test_module.py"
95 test_file.write_text('"""Test module."""\n')
97 # Semgrep JSON output with errors
98 semgrep_output = json.dumps(
99 {
100 "results": [],
101 "errors": [
102 {"message": "Failed to fetch rules from registry"},
103 ],
104 },
105 )
107 with patch(
108 "lintro.plugins.execution_preparation.verify_tool_version",
109 return_value=None,
110 ):
111 with patch.object(
112 semgrep_plugin,
113 "_run_subprocess",
114 return_value=(False, semgrep_output),
115 ):
116 result = semgrep_plugin.check([str(test_file)], {})
118 assert_that(result.success).is_false()
121# =============================================================================
122# Tests for output parsing
123# =============================================================================
126def test_parse_semgrep_output_single_issue() -> None:
127 """Parse single issue from Semgrep output."""
128 output = json.dumps(
129 {
130 "results": [
131 {
132 "check_id": "python.lang.security.audit.eval-usage",
133 "path": "test.py",
134 "start": {"line": 10, "col": 1},
135 "end": {"line": 10, "col": 15},
136 "extra": {
137 "message": "Detected use of eval()",
138 "severity": "WARNING",
139 "metadata": {"category": "security"},
140 },
141 },
142 ],
143 },
144 )
145 issues = parse_semgrep_output(output)
147 assert_that(issues).is_length(1)
148 assert_that(issues[0].file).is_equal_to("test.py")
149 assert_that(issues[0].line).is_equal_to(10)
150 assert_that(issues[0].check_id).is_equal_to("python.lang.security.audit.eval-usage")
151 assert_that(issues[0].message).contains("eval()")
154def test_parse_semgrep_output_multiple_issues() -> None:
155 """Parse multiple issues from Semgrep output."""
156 output = json.dumps(
157 {
158 "results": [
159 {
160 "check_id": "rule1",
161 "path": "file1.py",
162 "start": {"line": 5, "col": 1},
163 "end": {"line": 5, "col": 10},
164 "extra": {
165 "message": "Issue 1",
166 "severity": "ERROR",
167 "metadata": {},
168 },
169 },
170 {
171 "check_id": "rule2",
172 "path": "file2.py",
173 "start": {"line": 15, "col": 1},
174 "end": {"line": 15, "col": 20},
175 "extra": {
176 "message": "Issue 2",
177 "severity": "WARNING",
178 "metadata": {},
179 },
180 },
181 ],
182 },
183 )
184 issues = parse_semgrep_output(output)
186 assert_that(issues).is_length(2)
187 assert_that(issues[0].check_id).is_equal_to("rule1")
188 assert_that(issues[1].check_id).is_equal_to("rule2")
191def test_parse_semgrep_output_empty() -> None:
192 """Parse empty output returns empty list."""
193 issues = parse_semgrep_output("")
195 assert_that(issues).is_empty()
198def test_parse_semgrep_output_empty_results() -> None:
199 """Parse output with no results returns empty list."""
200 output = json.dumps({"results": []})
201 issues = parse_semgrep_output(output)
203 assert_that(issues).is_empty()
206def test_parse_semgrep_output_with_cwe() -> None:
207 """Parse output with CWE information."""
208 output = json.dumps(
209 {
210 "results": [
211 {
212 "check_id": "security-rule",
213 "path": "app.py",
214 "start": {"line": 20, "col": 1},
215 "end": {"line": 20, "col": 30},
216 "extra": {
217 "message": "SQL injection vulnerability",
218 "severity": "ERROR",
219 "metadata": {
220 "category": "security",
221 "cwe": ["CWE-89"],
222 },
223 },
224 },
225 ],
226 },
227 )
228 issues = parse_semgrep_output(output)
230 assert_that(issues).is_length(1)
231 assert_that(issues[0].cwe).contains("CWE-89")
234def test_parse_semgrep_output_none_input() -> None:
235 """Parse None input returns empty list."""
236 issues = parse_semgrep_output(None)
238 assert_that(issues).is_empty()