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

1"""Unit tests for Semgrep plugin execution.""" 

2 

3from __future__ import annotations 

4 

5import json 

6from pathlib import Path 

7from typing import cast 

8from unittest.mock import patch 

9 

10import pytest 

11from assertpy import assert_that 

12 

13from lintro.parsers.semgrep.semgrep_issue import SemgrepIssue 

14from lintro.tools.definitions.semgrep import SemgrepPlugin 

15 

16# ============================================================================= 

17# Tests for SemgrepPlugin.check method 

18# ============================================================================= 

19 

20 

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. 

26 

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') 

33 

34 # Semgrep JSON output with no results 

35 semgrep_output = json.dumps({"results": [], "errors": []}) 

36 

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)], {}) 

43 

44 assert_that(result.success).is_true() 

45 assert_that(result.issues_count).is_equal_to(0) 

46 

47 

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. 

53 

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") 

60 

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 ) 

83 

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)], {}) 

90 

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 ) 

102 

103 

104def test_check_with_multiple_findings( 

105 semgrep_plugin: SemgrepPlugin, 

106 tmp_path: Path, 

107) -> None: 

108 """Check handles multiple findings correctly. 

109 

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") 

116 

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 ) 

147 

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)], {}) 

154 

155 assert_that(result.issues_count).is_equal_to(2) 

156 assert_that(result.issues).is_length(2) 

157 

158 

159# ============================================================================= 

160# Tests for SemgrepPlugin.fix method 

161# ============================================================================= 

162 

163 

164def test_fix_raises_not_implemented(semgrep_plugin: SemgrepPlugin) -> None: 

165 """Fix method raises NotImplementedError. 

166 

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/"], {})