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
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
1"""Integration tests for Semgrep tool definition.
3These tests require semgrep to be installed and available in PATH.
4They verify the SemgrepPlugin definition, check command, and set_options method.
5"""
7from __future__ import annotations
9import shutil
10from collections.abc import Callable
11from pathlib import Path
12from typing import TYPE_CHECKING
14import pytest
15from assertpy import assert_that
17if TYPE_CHECKING:
18 from lintro.plugins.base import BaseToolPlugin
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)
27@pytest.fixture
28def temp_python_file_with_security_issues(tmp_path: Path) -> str:
29 """Create a temporary Python file with security issues.
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
37 Args:
38 tmp_path: Pytest fixture providing a temporary directory.
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
48def execute_code(user_input):
49 # Dangerous: eval with user input
50 result = eval(user_input)
51 return result
53def get_password():
54 # Hardcoded secret
55 api_key = "AKIAIOSFODNN7EXAMPLE"
56 return api_key
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)
67@pytest.fixture
68def temp_python_file_secure(tmp_path: Path) -> str:
69 """Create a temporary Python file with no security issues.
71 Creates a file containing secure Python code that should pass
72 semgrep security analysis without issues.
74 Args:
75 tmp_path: Pytest fixture providing a temporary directory.
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."""
85import secrets
86import os
89def generate_token() -> str:
90 """Generate a secure token."""
91 return secrets.token_hex(32)
94def get_env_var(name: str) -> str:
95 """Get environment variable safely."""
96 return os.environ.get(name, "")
99def add_numbers(a: int, b: int) -> int:
100 """Add two numbers."""
101 return a + b
102''',
103 )
104 return str(file_path)
107# --- Tests for SemgrepPlugin definition ---
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.
125 Tests that the plugin definition exposes the expected values for
126 name and can_fix attributes.
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)
137def test_definition_file_patterns(
138 get_plugin: Callable[[str], BaseToolPlugin],
139) -> None:
140 """Verify SemgrepPlugin definition includes expected file patterns.
142 Tests that the plugin is configured to handle Python and other files.
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")
151def test_definition_tool_type(
152 get_plugin: Callable[[str], BaseToolPlugin],
153) -> None:
154 """Verify SemgrepPlugin is a security tool type.
156 Args:
157 get_plugin: Fixture factory to get plugin instances.
158 """
159 from lintro.enums.tool_type import ToolType
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)
168# --- Integration tests for semgrep check command ---
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.
177 Runs semgrep on a file containing deliberate security vulnerabilities
178 and verifies that issues are found.
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], {})
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
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.
199 Runs semgrep on a properly secured file and verifies minimal issues.
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], {})
208 assert_that(result).is_not_none()
209 assert_that(result.name).is_equal_to("semgrep")
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.
218 Runs semgrep on an empty directory and verifies a result is returned
219 without errors.
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)], {})
228 assert_that(result).is_not_none()
231# --- Tests for SemgrepPlugin.set_options method ---
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.
251 Tests that plugin options can be set and retrieved correctly.
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)