Coverage for tests / integration / tools / test_bandit_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 Bandit tool definition.
3These tests require bandit to be installed and available in PATH.
4They verify the BanditPlugin 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 bandit is not installed
21pytestmark = pytest.mark.skipif(
22 shutil.which("bandit") is None,
23 reason="bandit 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 bandit should detect, including:
33 - B602: subprocess call with shell=True
34 - B105: Hardcoded password
35 - B311: Use of standard pseudo-random generators
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 subprocess
47import os
49def run_command(cmd):
50 # B602: subprocess call with shell=True
51 subprocess.call(cmd, shell=True)
53def read_file(filename):
54 # Potential path traversal
55 with open(filename) as f:
56 return f.read()
58# B105: Hardcoded password
59password = "secret123"
61# B311: Standard pseudo-random generators not suitable for security
62import random
63token = random.randint(0, 1000000)
64""",
65 )
66 return str(file_path)
69@pytest.fixture
70def temp_python_file_secure(tmp_path: Path) -> str:
71 """Create a temporary Python file with no security issues.
73 Creates a file containing secure Python code that should pass
74 bandit security analysis without issues.
76 Args:
77 tmp_path: Pytest fixture providing a temporary directory.
79 Returns:
80 Path to the created file as a string.
81 """
82 file_path = tmp_path / "secure.py"
83 file_path.write_text(
84 """\
85\"\"\"A secure module.\"\"\"
87import secrets
90def generate_token() -> str:
91 \"\"\"Generate a secure token.\"\"\"
92 return secrets.token_hex(32)
95def add_numbers(a: int, b: int) -> int:
96 \"\"\"Add two numbers securely.\"\"\"
97 return a + b
98""",
99 )
100 return str(file_path)
103# --- Tests for BanditPlugin definition ---
106@pytest.mark.parametrize(
107 ("attr", "expected"),
108 [
109 ("name", "bandit"),
110 ("can_fix", False),
111 ],
112 ids=["name", "can_fix"],
113)
114def test_definition_attributes(
115 get_plugin: Callable[[str], BaseToolPlugin],
116 attr: str,
117 expected: object,
118) -> None:
119 """Verify BanditPlugin definition has correct attribute values.
121 Tests that the plugin definition exposes the expected values for
122 name and can_fix attributes.
124 Args:
125 get_plugin: Fixture factory to get plugin instances.
126 attr: The attribute name to check on the definition.
127 expected: The expected value of the attribute.
128 """
129 bandit_plugin = get_plugin("bandit")
130 assert_that(getattr(bandit_plugin.definition, attr)).is_equal_to(expected)
133def test_definition_file_patterns(get_plugin: Callable[[str], BaseToolPlugin]) -> None:
134 """Verify BanditPlugin definition includes Python file patterns.
136 Tests that the plugin is configured to handle Python files (*.py).
138 Args:
139 get_plugin: Fixture factory to get plugin instances.
140 """
141 bandit_plugin = get_plugin("bandit")
142 assert_that(bandit_plugin.definition.file_patterns).contains("*.py")
145# --- Integration tests for bandit check command ---
148def test_check_file_with_security_issues(
149 get_plugin: Callable[[str], BaseToolPlugin],
150 temp_python_file_with_security_issues: str,
151) -> None:
152 """Verify bandit check detects security issues in problematic files.
154 Runs bandit on a file containing deliberate security vulnerabilities
155 and verifies that issues are found.
157 Args:
158 get_plugin: Fixture factory to get plugin instances.
159 temp_python_file_with_security_issues: Path to file with security issues.
160 """
161 bandit_plugin = get_plugin("bandit")
162 result = bandit_plugin.check([temp_python_file_with_security_issues], {})
164 assert_that(result).is_not_none()
165 assert_that(result.name).is_equal_to("bandit")
166 assert_that(result.issues_count).is_greater_than(0)
169def test_check_secure_file(
170 get_plugin: Callable[[str], BaseToolPlugin],
171 temp_python_file_secure: str,
172) -> None:
173 """Verify bandit check passes on secure files.
175 Runs bandit on a properly secured file and verifies no issues are found.
177 Args:
178 get_plugin: Fixture factory to get plugin instances.
179 temp_python_file_secure: Path to file with no security issues.
180 """
181 bandit_plugin = get_plugin("bandit")
182 result = bandit_plugin.check([temp_python_file_secure], {})
184 assert_that(result).is_not_none()
185 assert_that(result.name).is_equal_to("bandit")
186 assert_that(result.issues_count).is_equal_to(0)
189def test_check_empty_directory(
190 get_plugin: Callable[[str], BaseToolPlugin],
191 tmp_path: Path,
192) -> None:
193 """Verify bandit check handles empty directories gracefully.
195 Runs bandit on an empty directory and verifies a result is returned
196 without errors.
198 Args:
199 get_plugin: Fixture factory to get plugin instances.
200 tmp_path: Pytest fixture providing a temporary directory.
201 """
202 bandit_plugin = get_plugin("bandit")
203 result = bandit_plugin.check([str(tmp_path)], {})
205 assert_that(result).is_not_none()
208# --- Tests for BanditPlugin.set_options method ---
211@pytest.mark.parametrize(
212 ("option_name", "option_value"),
213 [
214 ("severity", "high"),
215 ("confidence", "high"),
216 ("skip", ["B101", "B102"]),
217 ],
218 ids=["severity", "confidence", "skip_tests"],
219)
220def test_set_options(
221 get_plugin: Callable[[str], BaseToolPlugin],
222 option_name: str,
223 option_value: object,
224) -> None:
225 """Verify BanditPlugin.set_options correctly sets various options.
227 Tests that plugin options can be set and retrieved correctly.
229 Args:
230 get_plugin: Fixture factory to get plugin instances.
231 option_name: Name of the option to set.
232 option_value: Value to set for the option.
233 """
234 bandit_plugin = get_plugin("bandit")
235 bandit_plugin.set_options(**{option_name: option_value})
236 assert_that(bandit_plugin.options.get(option_name)).is_not_none()
237 if option_name == "skip":
238 assert_that(bandit_plugin.options.get(option_name)).is_equal_to(option_value)