Coverage for tests / unit / tools / conftest.py: 47%

95 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-04-03 18:53 +0000

1"""Shared fixtures for tool unit tests. 

2 

3Note: This file contains core fixtures only. Additional fixtures and tests 

4have been split into separate files: 

5- tests/unit/tools/assertions/conftest.py - Assertion helper fixtures 

6- tests/unit/tools/test_plugin_definitions.py - Parametrized definition tests 

7- tests/unit/tools/test_common_behaviors.py - Common tool behavior tests 

8""" 

9 

10from __future__ import annotations 

11 

12from collections.abc import Callable, Generator 

13from typing import TYPE_CHECKING, Any 

14from unittest.mock import MagicMock, Mock, patch 

15 

16import pytest 

17 

18from lintro.enums.tool_name import ToolName 

19from lintro.models.core.tool_result import ToolResult 

20from lintro.plugins.base import BaseToolPlugin, ExecutionContext 

21 

22if TYPE_CHECKING: 

23 from lintro.tools.definitions.clippy import ClippyPlugin 

24 from lintro.tools.definitions.mypy import MypyPlugin 

25 from lintro.tools.definitions.tsc import TscPlugin 

26 

27 

28@pytest.fixture 

29def mock_subprocess_run() -> Generator[MagicMock, None, None]: 

30 """Mock subprocess.run for tool testing. 

31 

32 Yields: 

33 MagicMock: Configured mock for subprocess operations. 

34 """ 

35 with patch("subprocess.run") as mock_run: 

36 mock_process = Mock() 

37 mock_process.returncode = 0 

38 mock_process.stdout = "" 

39 mock_process.stderr = "" 

40 mock_run.return_value = mock_process 

41 yield mock_run 

42 

43 

44@pytest.fixture 

45def mock_tool_config() -> dict[str, Any]: 

46 """Provide a mock tool configuration. 

47 

48 Returns: 

49 dict: Sample tool configuration dictionary. 

50 """ 

51 return { 

52 "priority": 50, 

53 "file_patterns": ["*.py"], 

54 "tool_type": "linter", 

55 "options": { 

56 "timeout": 30, 

57 "line_length": 88, 

58 }, 

59 } 

60 

61 

62@pytest.fixture 

63def mock_tool_result() -> Mock: 

64 """Provide a mock tool result. 

65 

66 Returns: 

67 Mock: Configured mock tool result with default values. 

68 """ 

69 result = Mock() 

70 result.name = "test_tool" 

71 result.success = True 

72 result.output = "" 

73 result.issues_count = 0 

74 result.issues = [] 

75 return result 

76 

77 

78@pytest.fixture 

79def mypy_plugin() -> MypyPlugin: 

80 """Provide a MypyPlugin instance for testing. 

81 

82 Returns: 

83 A MypyPlugin instance. 

84 """ 

85 from lintro.tools.definitions.mypy import MypyPlugin 

86 

87 return MypyPlugin() 

88 

89 

90@pytest.fixture 

91def clippy_plugin() -> ClippyPlugin: 

92 """Provide a ClippyPlugin instance for testing. 

93 

94 Returns: 

95 A ClippyPlugin instance. 

96 """ 

97 from lintro.tools.definitions.clippy import ClippyPlugin 

98 

99 return ClippyPlugin() 

100 

101 

102@pytest.fixture 

103def tsc_plugin() -> TscPlugin: 

104 """Provide a TscPlugin instance for testing. 

105 

106 Returns: 

107 A TscPlugin instance. 

108 """ 

109 from lintro.tools.definitions.tsc import TscPlugin 

110 

111 return TscPlugin() 

112 

113 

114# ----------------------------------------------------------------------------- 

115# Shared patch fixtures for subprocess and tool availability 

116# ----------------------------------------------------------------------------- 

117 

118 

119@pytest.fixture 

120def patch_subprocess_success() -> Callable[[str, int], Any]: 

121 """Factory for patching subprocess with success result. 

122 

123 Returns: 

124 A factory function that creates a context manager to patch subprocess. 

125 

126 Example: 

127 def test_something(patch_subprocess_success): 

128 with patch_subprocess_success(output="OK"): 

129 # subprocess.run will return success with "OK" output 

130 """ 

131 from contextlib import contextmanager 

132 

133 @contextmanager 

134 def _patch(output: str = "", returncode: int = 0) -> Generator[MagicMock]: 

135 mock_result = MagicMock() 

136 mock_result.stdout = output 

137 mock_result.stderr = "" 

138 mock_result.returncode = returncode 

139 with patch("subprocess.run", return_value=mock_result) as mock_run: 

140 yield mock_run 

141 

142 return _patch 

143 

144 

145@pytest.fixture 

146def patch_tool_available() -> Callable[[], Any]: 

147 """Factory for patching tool availability to return True. 

148 

149 Returns: 

150 A factory function that creates a context manager for patching. 

151 

152 Example: 

153 def test_something(patch_tool_available): 

154 with patch_tool_available(): 

155 # _check_tool_available will return True 

156 """ 

157 from contextlib import contextmanager 

158 

159 @contextmanager 

160 def _patch() -> Generator[MagicMock]: 

161 with patch.object( 

162 BaseToolPlugin, 

163 "_check_tool_available", 

164 return_value=True, 

165 ) as mock_check: 

166 yield mock_check 

167 

168 return _patch 

169 

170 

171# ----------------------------------------------------------------------------- 

172# Mock tool factory fixtures 

173# ----------------------------------------------------------------------------- 

174 

175 

176@pytest.fixture 

177def mock_tool_factory() -> Callable[..., MagicMock]: 

178 """Factory for creating mock tool plugin instances. 

179 

180 Returns: 

181 A factory function that creates configured MagicMock objects 

182 that behave like tool plugins. 

183 

184 Example: 

185 def test_something(mock_tool_factory): 

186 mock_tool = mock_tool_factory( 

187 name=ToolName.RUFF, 

188 file_patterns=["*.py"], 

189 can_fix=True, 

190 ) 

191 assert mock_tool.definition.name == ToolName.RUFF 

192 """ 

193 

194 def _create( 

195 name: ToolName = ToolName.RUFF, 

196 file_patterns: list[str] | None = None, 

197 can_fix: bool = True, 

198 timeout: int = 30, 

199 options: dict[str, Any] | None = None, 

200 exclude_patterns: list[str] | None = None, 

201 include_venv: bool = False, 

202 executable_command: list[str] | None = None, 

203 cwd: str = "/test/project", 

204 ) -> MagicMock: 

205 tool = MagicMock() 

206 tool.definition.name = name 

207 tool.definition.file_patterns = file_patterns or ["*.py"] 

208 tool.definition.can_fix = can_fix 

209 tool.options = options or {"timeout": timeout} 

210 tool.exclude_patterns = exclude_patterns or [] 

211 tool.include_venv = include_venv 

212 tool._default_timeout = timeout 

213 

214 # Mock common methods 

215 tool._get_executable_command.return_value = executable_command or [ 

216 str(name).lower(), 

217 ] 

218 tool._verify_tool_version.return_value = None 

219 tool._validate_paths.return_value = None 

220 tool._get_cwd.return_value = cwd 

221 tool._build_config_args.return_value = [] 

222 tool._get_enforced_settings.return_value = {} 

223 

224 return tool 

225 

226 return _create 

227 

228 

229# ----------------------------------------------------------------------------- 

230# Mock execution context fixtures 

231# ----------------------------------------------------------------------------- 

232 

233 

234@pytest.fixture 

235def mock_execution_context_factory() -> Callable[..., MagicMock]: 

236 """Factory for creating mock ExecutionContext instances. 

237 

238 Returns: 

239 A factory function that creates configured MagicMock objects 

240 that behave like ExecutionContext. 

241 

242 Example: 

243 def test_something(mock_execution_context_factory): 

244 ctx = mock_execution_context_factory(files=["test.py"]) 

245 assert ctx.files == ["test.py"] 

246 """ 

247 

248 def _create( 

249 files: list[str] | None = None, 

250 rel_files: list[str] | None = None, 

251 cwd: str | None = None, 

252 early_result: ToolResult | None = None, 

253 timeout: int | None = None, 

254 should_skip: bool = False, 

255 ) -> MagicMock: 

256 ctx = MagicMock(spec=ExecutionContext) 

257 ctx.files = files if files is not None else [] 

258 ctx.rel_files = rel_files if rel_files is not None else [] 

259 ctx.cwd = cwd 

260 ctx.early_result = early_result 

261 ctx.timeout = timeout if timeout is not None else 30 

262 # should_skip is True if explicitly set OR if early_result is provided 

263 ctx.should_skip = should_skip or (early_result is not None) 

264 return ctx 

265 

266 return _create 

267 

268 

269@pytest.fixture 

270def mock_execution_context_for_tool( 

271 mock_execution_context_factory: Callable[..., MagicMock], 

272) -> Callable[..., MagicMock]: 

273 """Alias for mock_execution_context_factory for tool tests. 

274 

275 This provides backward compatibility for tests using the old fixture name. 

276 

277 Args: 

278 mock_execution_context_factory: Factory function for creating mock execution contexts. 

279 

280 Returns: 

281 The same factory function as mock_execution_context_factory. 

282 """ 

283 return mock_execution_context_factory