Coverage for tests / unit / tools / test_plugin_definitions.py: 100%

52 statements  

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

1"""Parametrized tests for plugin definitions. 

2 

3These tests consolidate the repetitive definition tests from individual 

4plugin files, following DRY principles. They verify that each tool plugin 

5has properly configured definitions. 

6""" 

7 

8from __future__ import annotations 

9 

10from typing import cast 

11 

12import pytest 

13from assertpy import assert_that 

14 

15from lintro.enums.tool_name import ToolName 

16from lintro.enums.tool_type import ToolType 

17from lintro.plugins.base import BaseToolPlugin 

18 

19# ============================================================================= 

20# Plugin definition metadata for parametrized tests 

21# ============================================================================= 

22 

23# Each tuple contains: 

24# (ToolName, plugin_class_path, can_fix, tool_type, description_keywords, native_configs) 

25PLUGIN_DEFINITIONS: list[tuple[ToolName, str, bool, ToolType, list[str], list[str]]] = [ 

26 ( 

27 ToolName.RUFF, 

28 "lintro.tools.definitions.ruff.RuffPlugin", 

29 True, 

30 ToolType.LINTER | ToolType.FORMATTER, 

31 ["Python", "linter"], 

32 ["pyproject.toml", "ruff.toml", ".ruff.toml"], 

33 ), 

34 ( 

35 ToolName.BLACK, 

36 "lintro.tools.definitions.black.BlackPlugin", 

37 True, 

38 ToolType.FORMATTER, 

39 ["Python", "formatter"], 

40 ["pyproject.toml"], 

41 ), 

42 ( 

43 ToolName.HADOLINT, 

44 "lintro.tools.definitions.hadolint.HadolintPlugin", 

45 False, 

46 ToolType.LINTER | ToolType.INFRASTRUCTURE, 

47 ["Dockerfile", "best practice"], 

48 [".hadolint.yaml", ".hadolint.yml"], 

49 ), 

50 ( 

51 ToolName.MARKDOWNLINT, 

52 "lintro.tools.definitions.markdownlint.MarkdownlintPlugin", 

53 False, 

54 ToolType.LINTER, 

55 ["Markdown", "linter"], 

56 [".markdownlint.json", ".markdownlint.yaml", ".markdownlint.yml"], 

57 ), 

58 ( 

59 ToolName.YAMLLINT, 

60 "lintro.tools.definitions.yamllint.YamllintPlugin", 

61 False, 

62 ToolType.LINTER, 

63 ["YAML", "linter"], 

64 [".yamllint", ".yamllint.yaml", ".yamllint.yml"], 

65 ), 

66 ( 

67 ToolName.MYPY, 

68 "lintro.tools.definitions.mypy.MypyPlugin", 

69 False, 

70 ToolType.LINTER | ToolType.TYPE_CHECKER, 

71 ["type", "Python"], 

72 ["mypy.ini", "pyproject.toml"], 

73 ), 

74 ( 

75 ToolName.BANDIT, 

76 "lintro.tools.definitions.bandit.BanditPlugin", 

77 False, 

78 ToolType.SECURITY, 

79 ["security", "Python"], 

80 [".bandit", "pyproject.toml"], 

81 ), 

82 ( 

83 ToolName.PYTEST, 

84 "lintro.tools.definitions.pytest.PytestPlugin", 

85 False, 

86 ToolType.TEST_RUNNER, 

87 ["test"], 

88 ["pytest.ini", "pyproject.toml"], 

89 ), 

90] 

91 

92 

93def _get_plugin_instance(plugin_class_path: str) -> BaseToolPlugin: 

94 """Dynamically import and instantiate a plugin class. 

95 

96 Args: 

97 plugin_class_path: Full module path to the plugin class. 

98 

99 Returns: 

100 An instance of the plugin class. 

101 """ 

102 module_path, class_name = plugin_class_path.rsplit(".", 1) 

103 import importlib 

104 

105 # Safe: module_path comes from hardcoded PLUGIN_DEFINITIONS, not user input 

106 module = importlib.import_module(module_path) # nosemgrep: non-literal-import 

107 plugin_class = getattr(module, class_name) 

108 return cast(BaseToolPlugin, plugin_class()) 

109 

110 

111# ============================================================================= 

112# Parametrized definition tests 

113# ============================================================================= 

114 

115 

116@pytest.mark.parametrize( 

117 ( 

118 "tool_name", 

119 "plugin_class_path", 

120 "can_fix", 

121 "tool_type", 

122 "keywords", 

123 "configs", 

124 ), 

125 PLUGIN_DEFINITIONS, 

126 ids=[str(t[0]) for t in PLUGIN_DEFINITIONS], 

127) 

128def test_definition_name( 

129 tool_name: ToolName, 

130 plugin_class_path: str, 

131 can_fix: bool, 

132 tool_type: ToolType, 

133 keywords: list[str], 

134 configs: list[str], 

135) -> None: 

136 """Each plugin definition has the correct name. 

137 

138 Args: 

139 tool_name: The expected tool name. 

140 plugin_class_path: Full module path to the plugin class. 

141 can_fix: Whether the tool can fix issues. 

142 tool_type: The type of tool. 

143 keywords: Keywords expected in the description. 

144 configs: Native configuration files supported. 

145 """ 

146 plugin = _get_plugin_instance(plugin_class_path) 

147 assert_that(plugin.definition.name).is_equal_to(tool_name) 

148 

149 

150@pytest.mark.parametrize( 

151 ( 

152 "tool_name", 

153 "plugin_class_path", 

154 "can_fix", 

155 "tool_type", 

156 "keywords", 

157 "configs", 

158 ), 

159 PLUGIN_DEFINITIONS, 

160 ids=[str(t[0]) for t in PLUGIN_DEFINITIONS], 

161) 

162def test_definition_description_not_empty( 

163 tool_name: ToolName, 

164 plugin_class_path: str, 

165 can_fix: bool, 

166 tool_type: ToolType, 

167 keywords: list[str], 

168 configs: list[str], 

169) -> None: 

170 """Each plugin definition has a non-empty description. 

171 

172 Args: 

173 tool_name: The expected tool name. 

174 plugin_class_path: Full module path to the plugin class. 

175 can_fix: Whether the tool can fix issues. 

176 tool_type: The type of tool. 

177 keywords: Keywords expected in the description. 

178 configs: Native configuration files supported. 

179 """ 

180 plugin = _get_plugin_instance(plugin_class_path) 

181 assert_that(plugin.definition.description).is_not_empty() 

182 

183 

184@pytest.mark.parametrize( 

185 ( 

186 "tool_name", 

187 "plugin_class_path", 

188 "can_fix", 

189 "tool_type", 

190 "keywords", 

191 "configs", 

192 ), 

193 PLUGIN_DEFINITIONS, 

194 ids=[str(t[0]) for t in PLUGIN_DEFINITIONS], 

195) 

196def test_definition_description_contains_keywords( 

197 tool_name: ToolName, 

198 plugin_class_path: str, 

199 can_fix: bool, 

200 tool_type: ToolType, 

201 keywords: list[str], 

202 configs: list[str], 

203) -> None: 

204 """Each plugin description contains expected keywords. 

205 

206 Args: 

207 tool_name: The expected tool name. 

208 plugin_class_path: Full module path to the plugin class. 

209 can_fix: Whether the tool can fix issues. 

210 tool_type: The type of tool. 

211 keywords: Keywords expected in the description. 

212 configs: Native configuration files supported. 

213 """ 

214 plugin = _get_plugin_instance(plugin_class_path) 

215 for keyword in keywords: 

216 assert_that(plugin.definition.description.lower()).contains(keyword.lower()) 

217 

218 

219@pytest.mark.parametrize( 

220 ( 

221 "tool_name", 

222 "plugin_class_path", 

223 "can_fix", 

224 "tool_type", 

225 "keywords", 

226 "configs", 

227 ), 

228 PLUGIN_DEFINITIONS, 

229 ids=[str(t[0]) for t in PLUGIN_DEFINITIONS], 

230) 

231def test_definition_can_fix( 

232 tool_name: ToolName, 

233 plugin_class_path: str, 

234 can_fix: bool, 

235 tool_type: ToolType, 

236 keywords: list[str], 

237 configs: list[str], 

238) -> None: 

239 """Each plugin definition has correct can_fix value. 

240 

241 Args: 

242 tool_name: The expected tool name. 

243 plugin_class_path: Full module path to the plugin class. 

244 can_fix: Whether the tool can fix issues. 

245 tool_type: The type of tool. 

246 keywords: Keywords expected in the description. 

247 configs: Native configuration files supported. 

248 """ 

249 plugin = _get_plugin_instance(plugin_class_path) 

250 assert_that(plugin.definition.can_fix).is_equal_to(can_fix) 

251 

252 

253@pytest.mark.parametrize( 

254 ( 

255 "tool_name", 

256 "plugin_class_path", 

257 "can_fix", 

258 "tool_type", 

259 "keywords", 

260 "configs", 

261 ), 

262 PLUGIN_DEFINITIONS, 

263 ids=[str(t[0]) for t in PLUGIN_DEFINITIONS], 

264) 

265def test_definition_tool_type( 

266 tool_name: ToolName, 

267 plugin_class_path: str, 

268 can_fix: bool, 

269 tool_type: ToolType, 

270 keywords: list[str], 

271 configs: list[str], 

272) -> None: 

273 """Each plugin definition has correct tool_type. 

274 

275 Args: 

276 tool_name: The expected tool name. 

277 plugin_class_path: Full module path to the plugin class. 

278 can_fix: Whether the tool can fix issues. 

279 tool_type: The type of tool. 

280 keywords: Keywords expected in the description. 

281 configs: Native configuration files supported. 

282 """ 

283 plugin = _get_plugin_instance(plugin_class_path) 

284 assert_that(plugin.definition.tool_type).is_equal_to(tool_type) 

285 

286 

287@pytest.mark.parametrize( 

288 ( 

289 "tool_name", 

290 "plugin_class_path", 

291 "can_fix", 

292 "tool_type", 

293 "keywords", 

294 "configs", 

295 ), 

296 PLUGIN_DEFINITIONS, 

297 ids=[str(t[0]) for t in PLUGIN_DEFINITIONS], 

298) 

299def test_definition_has_file_patterns( 

300 tool_name: ToolName, 

301 plugin_class_path: str, 

302 can_fix: bool, 

303 tool_type: ToolType, 

304 keywords: list[str], 

305 configs: list[str], 

306) -> None: 

307 """Each plugin definition has non-empty file patterns. 

308 

309 Args: 

310 tool_name: The expected tool name. 

311 plugin_class_path: Full module path to the plugin class. 

312 can_fix: Whether the tool can fix issues. 

313 tool_type: The type of tool. 

314 keywords: Keywords expected in the description. 

315 configs: Native configuration files supported. 

316 """ 

317 plugin = _get_plugin_instance(plugin_class_path) 

318 assert_that(plugin.definition.file_patterns).is_not_empty() 

319 

320 

321@pytest.mark.parametrize( 

322 ( 

323 "tool_name", 

324 "plugin_class_path", 

325 "can_fix", 

326 "tool_type", 

327 "keywords", 

328 "configs", 

329 ), 

330 PLUGIN_DEFINITIONS, 

331 ids=[str(t[0]) for t in PLUGIN_DEFINITIONS], 

332) 

333def test_definition_has_priority( 

334 tool_name: ToolName, 

335 plugin_class_path: str, 

336 can_fix: bool, 

337 tool_type: ToolType, 

338 keywords: list[str], 

339 configs: list[str], 

340) -> None: 

341 """Each plugin definition has a valid priority. 

342 

343 Args: 

344 tool_name: The expected tool name. 

345 plugin_class_path: Full module path to the plugin class. 

346 can_fix: Whether the tool can fix issues. 

347 tool_type: The type of tool. 

348 keywords: Keywords expected in the description. 

349 configs: Native configuration files supported. 

350 """ 

351 plugin = _get_plugin_instance(plugin_class_path) 

352 assert_that(plugin.definition.priority).is_greater_than(0) 

353 

354 

355@pytest.mark.parametrize( 

356 ( 

357 "tool_name", 

358 "plugin_class_path", 

359 "can_fix", 

360 "tool_type", 

361 "keywords", 

362 "configs", 

363 ), 

364 PLUGIN_DEFINITIONS, 

365 ids=[str(t[0]) for t in PLUGIN_DEFINITIONS], 

366) 

367def test_definition_has_default_timeout( 

368 tool_name: ToolName, 

369 plugin_class_path: str, 

370 can_fix: bool, 

371 tool_type: ToolType, 

372 keywords: list[str], 

373 configs: list[str], 

374) -> None: 

375 """Each plugin definition has a positive default timeout. 

376 

377 Args: 

378 tool_name: The expected tool name. 

379 plugin_class_path: Full module path to the plugin class. 

380 can_fix: Whether the tool can fix issues. 

381 tool_type: The type of tool. 

382 keywords: Keywords expected in the description. 

383 configs: Native configuration files supported. 

384 """ 

385 plugin = _get_plugin_instance(plugin_class_path) 

386 assert_that(plugin.definition.default_timeout).is_greater_than(0) 

387 

388 

389@pytest.mark.parametrize( 

390 ( 

391 "tool_name", 

392 "plugin_class_path", 

393 "can_fix", 

394 "tool_type", 

395 "keywords", 

396 "configs", 

397 ), 

398 PLUGIN_DEFINITIONS, 

399 ids=[str(t[0]) for t in PLUGIN_DEFINITIONS], 

400) 

401def test_definition_native_configs_subset( 

402 tool_name: ToolName, 

403 plugin_class_path: str, 

404 can_fix: bool, 

405 tool_type: ToolType, 

406 keywords: list[str], 

407 configs: list[str], 

408) -> None: 

409 """Each plugin definition's native configs contain expected values. 

410 

411 Args: 

412 tool_name: The expected tool name. 

413 plugin_class_path: Full module path to the plugin class. 

414 can_fix: Whether the tool can fix issues. 

415 tool_type: The type of tool. 

416 keywords: Keywords expected in the description. 

417 configs: Native configuration files supported. 

418 """ 

419 plugin = _get_plugin_instance(plugin_class_path) 

420 for config in configs: 

421 assert_that(plugin.definition.native_configs).contains(config)