Coverage for tests / unit / tools / semgrep / test_execution.py: 100%
42 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 execution."""
3from __future__ import annotations
5import json
6from pathlib import Path
7from typing import cast
8from unittest.mock import patch
10import pytest
11from assertpy import assert_that
13from lintro.parsers.semgrep.semgrep_issue import SemgrepIssue
14from lintro.tools.definitions.semgrep import SemgrepPlugin
16# =============================================================================
17# Tests for SemgrepPlugin.check method
18# =============================================================================
21def test_check_with_mocked_subprocess_success(
22 semgrep_plugin: SemgrepPlugin,
23 tmp_path: Path,
24) -> None:
25 """Check returns success when no issues found.
27 Args:
28 semgrep_plugin: The SemgrepPlugin instance to test.
29 tmp_path: Temporary directory path for test files.
30 """
31 test_file = tmp_path / "clean_code.py"
32 test_file.write_text('"""Clean module with no security issues."""\n')
34 # Semgrep JSON output with no results
35 semgrep_output = json.dumps({"results": [], "errors": []})
37 with patch.object(
38 semgrep_plugin,
39 "_run_subprocess",
40 return_value=(True, semgrep_output),
41 ):
42 result = semgrep_plugin.check([str(test_file)], {})
44 assert_that(result.success).is_true()
45 assert_that(result.issues_count).is_equal_to(0)
48def test_check_with_mocked_subprocess_findings(
49 semgrep_plugin: SemgrepPlugin,
50 tmp_path: Path,
51) -> None:
52 """Check returns issues when Semgrep finds security problems.
54 Args:
55 semgrep_plugin: The SemgrepPlugin instance to test.
56 tmp_path: Temporary directory path for test files.
57 """
58 test_file = tmp_path / "vulnerable.py"
59 test_file.write_text("import os\nos.system(user_input)\n")
61 # Semgrep JSON output with findings
62 semgrep_output = json.dumps(
63 {
64 "results": [
65 {
66 "check_id": "python.lang.security.audit.dangerous-system-call",
67 "path": str(test_file),
68 "start": {"line": 2, "col": 1},
69 "end": {"line": 2, "col": 25},
70 "extra": {
71 "message": "Detected dangerous system call with user input",
72 "severity": "ERROR",
73 "metadata": {
74 "category": "security",
75 "cwe": ["CWE-78"],
76 },
77 },
78 },
79 ],
80 "errors": [],
81 },
82 )
84 with patch.object(
85 semgrep_plugin,
86 "_run_subprocess",
87 return_value=(False, semgrep_output),
88 ):
89 result = semgrep_plugin.check([str(test_file)], {})
91 # Scan succeeded; findings don't cause failure
92 assert_that(result.success).is_true()
93 assert_that(result.issues_count).is_equal_to(1)
94 assert_that(result.issues).is_not_none()
95 issues = cast(list[SemgrepIssue], result.issues)
96 assert_that(issues).is_length(1)
97 issue = issues[0]
98 assert_that(issue).is_instance_of(SemgrepIssue)
99 assert_that(issue.check_id).is_equal_to(
100 "python.lang.security.audit.dangerous-system-call",
101 )
104def test_check_with_multiple_findings(
105 semgrep_plugin: SemgrepPlugin,
106 tmp_path: Path,
107) -> None:
108 """Check handles multiple findings correctly.
110 Args:
111 semgrep_plugin: The SemgrepPlugin instance to test.
112 tmp_path: Temporary directory path for test files.
113 """
114 test_file = tmp_path / "multiple_issues.py"
115 test_file.write_text("import os\nos.system(x)\neval(y)\n")
117 # Semgrep JSON output with multiple findings
118 semgrep_output = json.dumps(
119 {
120 "results": [
121 {
122 "check_id": "python.lang.security.audit.dangerous-system-call",
123 "path": str(test_file),
124 "start": {"line": 2, "col": 1},
125 "end": {"line": 2, "col": 15},
126 "extra": {
127 "message": "Dangerous system call",
128 "severity": "ERROR",
129 "metadata": {"category": "security"},
130 },
131 },
132 {
133 "check_id": "python.lang.security.audit.eval-usage",
134 "path": str(test_file),
135 "start": {"line": 3, "col": 1},
136 "end": {"line": 3, "col": 8},
137 "extra": {
138 "message": "Use of eval() detected",
139 "severity": "WARNING",
140 "metadata": {"category": "security"},
141 },
142 },
143 ],
144 "errors": [],
145 },
146 )
148 with patch.object(
149 semgrep_plugin,
150 "_run_subprocess",
151 return_value=(False, semgrep_output),
152 ):
153 result = semgrep_plugin.check([str(test_file)], {})
155 assert_that(result.issues_count).is_equal_to(2)
156 assert_that(result.issues).is_length(2)
159# =============================================================================
160# Tests for SemgrepPlugin.fix method
161# =============================================================================
164def test_fix_raises_not_implemented(semgrep_plugin: SemgrepPlugin) -> None:
165 """Fix method raises NotImplementedError.
167 Args:
168 semgrep_plugin: The SemgrepPlugin instance to test.
169 """
170 with pytest.raises(NotImplementedError, match="cannot automatically fix"):
171 semgrep_plugin.fix(["src/"], {})