Coverage for tests / integration / tools / test_semgrep_integration.py: 100%

48 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-04-03 18:53 +0000

1"""Integration tests for Semgrep tool definition. 

2 

3These tests require semgrep to be installed and available in PATH. 

4They verify the SemgrepPlugin definition, check command, and set_options method. 

5""" 

6 

7from __future__ import annotations 

8 

9import shutil 

10from collections.abc import Callable 

11from pathlib import Path 

12from typing import TYPE_CHECKING 

13 

14import pytest 

15from assertpy import assert_that 

16 

17if TYPE_CHECKING: 

18 from lintro.plugins.base import BaseToolPlugin 

19 

20# Skip all tests if semgrep is not installed 

21pytestmark = pytest.mark.skipif( 

22 shutil.which("semgrep") is None, 

23 reason="semgrep not installed", 

24) 

25 

26 

27@pytest.fixture 

28def temp_python_file_with_security_issues(tmp_path: Path) -> str: 

29 """Create a temporary Python file with security issues. 

30 

31 Creates a file containing code with deliberate security vulnerabilities 

32 that semgrep should detect, including: 

33 - Use of eval() with user input 

34 - Hardcoded secrets 

35 - SQL injection patterns 

36 

37 Args: 

38 tmp_path: Pytest fixture providing a temporary directory. 

39 

40 Returns: 

41 Path to the created file as a string. 

42 """ 

43 file_path = tmp_path / "insecure.py" 

44 file_path.write_text( 

45 """\ 

46import os 

47 

48def execute_code(user_input): 

49 # Dangerous: eval with user input 

50 result = eval(user_input) 

51 return result 

52 

53def get_password(): 

54 # Hardcoded secret 

55 api_key = "AKIAIOSFODNN7EXAMPLE" 

56 return api_key 

57 

58def query_db(user_id): 

59 # SQL injection vulnerability 

60 query = "SELECT * FROM users WHERE id = " + user_id 

61 return query 

62""", 

63 ) 

64 return str(file_path) 

65 

66 

67@pytest.fixture 

68def temp_python_file_secure(tmp_path: Path) -> str: 

69 """Create a temporary Python file with no security issues. 

70 

71 Creates a file containing secure Python code that should pass 

72 semgrep security analysis without issues. 

73 

74 Args: 

75 tmp_path: Pytest fixture providing a temporary directory. 

76 

77 Returns: 

78 Path to the created file as a string. 

79 """ 

80 file_path = tmp_path / "secure.py" 

81 file_path.write_text( 

82 '''\ 

83"""A secure module.""" 

84 

85import secrets 

86import os 

87 

88 

89def generate_token() -> str: 

90 """Generate a secure token.""" 

91 return secrets.token_hex(32) 

92 

93 

94def get_env_var(name: str) -> str: 

95 """Get environment variable safely.""" 

96 return os.environ.get(name, "") 

97 

98 

99def add_numbers(a: int, b: int) -> int: 

100 """Add two numbers.""" 

101 return a + b 

102''', 

103 ) 

104 return str(file_path) 

105 

106 

107# --- Tests for SemgrepPlugin definition --- 

108 

109 

110@pytest.mark.parametrize( 

111 ("attr", "expected"), 

112 [ 

113 ("name", "semgrep"), 

114 ("can_fix", False), 

115 ], 

116 ids=["name", "can_fix"], 

117) 

118def test_definition_attributes( 

119 get_plugin: Callable[[str], BaseToolPlugin], 

120 attr: str, 

121 expected: object, 

122) -> None: 

123 """Verify SemgrepPlugin definition has correct attribute values. 

124 

125 Tests that the plugin definition exposes the expected values for 

126 name and can_fix attributes. 

127 

128 Args: 

129 get_plugin: Fixture factory to get plugin instances. 

130 attr: The attribute name to check on the definition. 

131 expected: The expected value of the attribute. 

132 """ 

133 semgrep_plugin = get_plugin("semgrep") 

134 assert_that(getattr(semgrep_plugin.definition, attr)).is_equal_to(expected) 

135 

136 

137def test_definition_file_patterns( 

138 get_plugin: Callable[[str], BaseToolPlugin], 

139) -> None: 

140 """Verify SemgrepPlugin definition includes expected file patterns. 

141 

142 Tests that the plugin is configured to handle Python and other files. 

143 

144 Args: 

145 get_plugin: Fixture factory to get plugin instances. 

146 """ 

147 semgrep_plugin = get_plugin("semgrep") 

148 assert_that(semgrep_plugin.definition.file_patterns).contains("*.py") 

149 

150 

151def test_definition_tool_type( 

152 get_plugin: Callable[[str], BaseToolPlugin], 

153) -> None: 

154 """Verify SemgrepPlugin is a security tool type. 

155 

156 Args: 

157 get_plugin: Fixture factory to get plugin instances. 

158 """ 

159 from lintro.enums.tool_type import ToolType 

160 

161 semgrep_plugin = get_plugin("semgrep") 

162 # Use flag containment check since tool_type is a flags enum 

163 assert_that( 

164 semgrep_plugin.definition.tool_type & ToolType.SECURITY, 

165 ).is_equal_to(ToolType.SECURITY) 

166 

167 

168# --- Integration tests for semgrep check command --- 

169 

170 

171def test_check_file_with_security_issues( 

172 get_plugin: Callable[[str], BaseToolPlugin], 

173 temp_python_file_with_security_issues: str, 

174) -> None: 

175 """Verify semgrep check detects security issues in problematic files. 

176 

177 Runs semgrep on a file containing deliberate security vulnerabilities 

178 and verifies that issues are found. 

179 

180 Args: 

181 get_plugin: Fixture factory to get plugin instances. 

182 temp_python_file_with_security_issues: Path to file with security issues. 

183 """ 

184 semgrep_plugin = get_plugin("semgrep") 

185 result = semgrep_plugin.check([temp_python_file_with_security_issues], {}) 

186 

187 assert_that(result).is_not_none() 

188 assert_that(result.name).is_equal_to("semgrep") 

189 # Semgrep with auto config should detect at least one issue 

190 # Note: Results depend on semgrep's rule set 

191 

192 

193def test_check_secure_file( 

194 get_plugin: Callable[[str], BaseToolPlugin], 

195 temp_python_file_secure: str, 

196) -> None: 

197 """Verify semgrep check passes on secure files. 

198 

199 Runs semgrep on a properly secured file and verifies minimal issues. 

200 

201 Args: 

202 get_plugin: Fixture factory to get plugin instances. 

203 temp_python_file_secure: Path to file with no security issues. 

204 """ 

205 semgrep_plugin = get_plugin("semgrep") 

206 result = semgrep_plugin.check([temp_python_file_secure], {}) 

207 

208 assert_that(result).is_not_none() 

209 assert_that(result.name).is_equal_to("semgrep") 

210 

211 

212def test_check_empty_directory( 

213 get_plugin: Callable[[str], BaseToolPlugin], 

214 tmp_path: Path, 

215) -> None: 

216 """Verify semgrep check handles empty directories gracefully. 

217 

218 Runs semgrep on an empty directory and verifies a result is returned 

219 without errors. 

220 

221 Args: 

222 get_plugin: Fixture factory to get plugin instances. 

223 tmp_path: Pytest fixture providing a temporary directory. 

224 """ 

225 semgrep_plugin = get_plugin("semgrep") 

226 result = semgrep_plugin.check([str(tmp_path)], {}) 

227 

228 assert_that(result).is_not_none() 

229 

230 

231# --- Tests for SemgrepPlugin.set_options method --- 

232 

233 

234@pytest.mark.parametrize( 

235 ("option_name", "option_value"), 

236 [ 

237 ("config", "auto"), 

238 ("config", "p/python"), 

239 ("severity", "ERROR"), 

240 ("exclude", ["test_*.py", "vendor/*"]), 

241 ], 

242 ids=["config_auto", "config_python", "severity_error", "exclude_patterns"], 

243) 

244def test_set_options( 

245 get_plugin: Callable[[str], BaseToolPlugin], 

246 option_name: str, 

247 option_value: object, 

248) -> None: 

249 """Verify SemgrepPlugin.set_options correctly sets various options. 

250 

251 Tests that plugin options can be set and retrieved correctly. 

252 

253 Args: 

254 get_plugin: Fixture factory to get plugin instances. 

255 option_name: Name of the option to set. 

256 option_value: Value to set for the option. 

257 """ 

258 semgrep_plugin = get_plugin("semgrep") 

259 semgrep_plugin.set_options(**{option_name: option_value}) 

260 assert_that(semgrep_plugin.options.get(option_name)).is_equal_to(option_value)