Coverage for tests / unit / tools / cargo_audit / test_cargo_audit_plugin.py: 100%
74 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 cargo-audit plugin."""
3from __future__ import annotations
5from pathlib import Path
6from subprocess import TimeoutExpired
7from unittest.mock import patch
9import pytest
10from assertpy import assert_that
12from lintro.enums.tool_type import ToolType
13from lintro.tools.definitions.cargo_audit import (
14 CARGO_AUDIT_DEFAULT_TIMEOUT,
15 CargoAuditPlugin,
16)
19@pytest.fixture
20def cargo_audit_plugin() -> CargoAuditPlugin:
21 """Provide a CargoAuditPlugin instance for testing.
23 Returns:
24 A CargoAuditPlugin instance.
25 """
26 return CargoAuditPlugin()
29def test_definition_name(cargo_audit_plugin: CargoAuditPlugin) -> None:
30 """Verify the tool name.
32 Args:
33 cargo_audit_plugin: The plugin instance.
34 """
35 assert_that(cargo_audit_plugin.definition.name).is_equal_to("cargo_audit")
38def test_definition_can_fix(cargo_audit_plugin: CargoAuditPlugin) -> None:
39 """Verify the tool cannot fix issues.
41 Args:
42 cargo_audit_plugin: The plugin instance.
43 """
44 assert_that(cargo_audit_plugin.definition.can_fix).is_false()
47def test_definition_tool_type(cargo_audit_plugin: CargoAuditPlugin) -> None:
48 """Verify the tool type is SECURITY.
50 Args:
51 cargo_audit_plugin: The plugin instance.
52 """
53 assert_that(cargo_audit_plugin.definition.tool_type).is_equal_to(ToolType.SECURITY)
56def test_definition_file_patterns(cargo_audit_plugin: CargoAuditPlugin) -> None:
57 """Verify the file patterns.
59 Args:
60 cargo_audit_plugin: The plugin instance.
61 """
62 patterns = cargo_audit_plugin.definition.file_patterns
63 assert_that(patterns).contains("Cargo.lock")
66def test_definition_priority(cargo_audit_plugin: CargoAuditPlugin) -> None:
67 """Verify the priority is 95.
69 Args:
70 cargo_audit_plugin: The plugin instance.
71 """
72 assert_that(cargo_audit_plugin.definition.priority).is_equal_to(95)
75def test_definition_timeout(cargo_audit_plugin: CargoAuditPlugin) -> None:
76 """Verify the default timeout.
78 Args:
79 cargo_audit_plugin: The plugin instance.
80 """
81 assert_that(cargo_audit_plugin.definition.default_timeout).is_equal_to(
82 CARGO_AUDIT_DEFAULT_TIMEOUT,
83 )
86def test_definition_native_configs(cargo_audit_plugin: CargoAuditPlugin) -> None:
87 """Verify the native config files.
89 Args:
90 cargo_audit_plugin: The plugin instance.
91 """
92 configs = cargo_audit_plugin.definition.native_configs
93 assert_that(configs).contains(".cargo/audit.toml")
96def test_set_options_timeout(cargo_audit_plugin: CargoAuditPlugin) -> None:
97 """Verify timeout option can be set.
99 Args:
100 cargo_audit_plugin: The plugin instance.
101 """
102 cargo_audit_plugin.set_options(timeout=180)
103 assert_that(cargo_audit_plugin.options.get("timeout")).is_equal_to(180)
106def test_set_options_invalid_timeout(cargo_audit_plugin: CargoAuditPlugin) -> None:
107 """Verify negative integer timeout raises ValueError.
109 Args:
110 cargo_audit_plugin: The plugin instance.
111 """
112 with pytest.raises(ValueError, match="non-negative"):
113 cargo_audit_plugin.set_options(timeout=-1)
116def test_set_options_negative_float_timeout(
117 cargo_audit_plugin: CargoAuditPlugin,
118) -> None:
119 """Verify negative float timeout raises ValueError.
121 Args:
122 cargo_audit_plugin: The plugin instance.
123 """
124 with pytest.raises(ValueError, match="non-negative"):
125 cargo_audit_plugin.set_options(timeout=-1.5)
128def test_set_options_non_numeric_timeout(
129 cargo_audit_plugin: CargoAuditPlugin,
130) -> None:
131 """Verify non-numeric timeout raises ValueError.
133 Args:
134 cargo_audit_plugin: The plugin instance.
135 """
136 with pytest.raises(ValueError, match="must be a number"):
137 cargo_audit_plugin.set_options(timeout="invalid")
140def test_fix_raises_not_implemented(cargo_audit_plugin: CargoAuditPlugin) -> None:
141 """Verify fix raises NotImplementedError.
143 Args:
144 cargo_audit_plugin: The plugin instance.
145 """
146 with pytest.raises(NotImplementedError) as exc_info:
147 cargo_audit_plugin.fix(["Cargo.lock"], {})
148 assert_that(str(exc_info.value)).contains("cannot automatically fix")
151def test_check_no_cargo_lock(
152 cargo_audit_plugin: CargoAuditPlugin,
153 tmp_path: Path,
154) -> None:
155 """Check skips gracefully when no Cargo.lock found.
157 Args:
158 cargo_audit_plugin: The plugin instance.
159 tmp_path: Temporary directory path.
160 """
161 # Pass the directory itself, not a file
162 # This simulates a directory without Cargo.lock
164 with patch(
165 "lintro.plugins.execution_preparation.verify_tool_version",
166 return_value=None,
167 ):
168 result = cargo_audit_plugin.check([str(tmp_path)], {})
170 # Either no files found or no Cargo.lock found message
171 assert_that(result.success).is_true()
172 assert_that(result.issues_count).is_equal_to(0)
175def test_check_no_vulnerabilities(
176 cargo_audit_plugin: CargoAuditPlugin,
177 tmp_path: Path,
178) -> None:
179 """Check returns success when no vulnerabilities found.
181 Args:
182 cargo_audit_plugin: The plugin instance.
183 tmp_path: Temporary directory path.
184 """
185 cargo_lock = tmp_path / "Cargo.lock"
186 cargo_lock.write_text('[[package]]\nname = "test"\nversion = "1.0.0"')
188 mock_output = """{
189 "vulnerabilities": {
190 "count": 0,
191 "list": []
192 }
193 }"""
195 with patch(
196 "lintro.plugins.execution_preparation.verify_tool_version",
197 return_value=None,
198 ):
199 with patch.object(
200 cargo_audit_plugin,
201 "_run_subprocess",
202 return_value=(True, mock_output),
203 ):
204 result = cargo_audit_plugin.check([str(cargo_lock)], {})
206 assert_that(result.success).is_true()
207 assert_that(result.issues_count).is_equal_to(0)
210def test_check_with_vulnerabilities(
211 cargo_audit_plugin: CargoAuditPlugin,
212 tmp_path: Path,
213) -> None:
214 """Check returns issues when vulnerabilities found.
216 Args:
217 cargo_audit_plugin: The plugin instance.
218 tmp_path: Temporary directory path.
219 """
220 cargo_lock = tmp_path / "Cargo.lock"
221 cargo_lock.write_text('[[package]]\nname = "test"\nversion = "1.0.0"')
223 mock_output = """{
224 "vulnerabilities": {
225 "count": 1,
226 "list": [
227 {
228 "advisory": {
229 "id": "RUSTSEC-2021-0001",
230 "title": "Test vulnerability",
231 "severity": "HIGH"
232 },
233 "package": {
234 "name": "vulnerable-crate",
235 "version": "1.0.0"
236 }
237 }
238 ]
239 }
240 }"""
242 with patch(
243 "lintro.plugins.execution_preparation.verify_tool_version",
244 return_value=None,
245 ):
246 with patch.object(
247 cargo_audit_plugin,
248 "_run_subprocess",
249 return_value=(False, mock_output),
250 ):
251 result = cargo_audit_plugin.check([str(cargo_lock)], {})
253 assert_that(result.success).is_false()
254 assert_that(result.issues_count).is_greater_than(0)
257def test_check_timeout(
258 cargo_audit_plugin: CargoAuditPlugin,
259 tmp_path: Path,
260) -> None:
261 """Check handles timeout correctly.
263 Args:
264 cargo_audit_plugin: The plugin instance.
265 tmp_path: Temporary directory path.
266 """
267 cargo_lock = tmp_path / "Cargo.lock"
268 cargo_lock.write_text('[[package]]\nname = "test"\nversion = "1.0.0"')
270 with patch(
271 "lintro.plugins.execution_preparation.verify_tool_version",
272 return_value=None,
273 ):
274 with patch.object(
275 cargo_audit_plugin,
276 "_run_subprocess",
277 side_effect=TimeoutExpired(
278 cmd=["cargo", "audit"],
279 timeout=120,
280 ),
281 ):
282 result = cargo_audit_plugin.check([str(cargo_lock)], {})
284 assert_that(result.success).is_false()
285 assert_that(result.output).contains("timed out")