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
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
1"""Parametrized tests for plugin definitions.
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"""
8from __future__ import annotations
10from typing import cast
12import pytest
13from assertpy import assert_that
15from lintro.enums.tool_name import ToolName
16from lintro.enums.tool_type import ToolType
17from lintro.plugins.base import BaseToolPlugin
19# =============================================================================
20# Plugin definition metadata for parametrized tests
21# =============================================================================
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]
93def _get_plugin_instance(plugin_class_path: str) -> BaseToolPlugin:
94 """Dynamically import and instantiate a plugin class.
96 Args:
97 plugin_class_path: Full module path to the plugin class.
99 Returns:
100 An instance of the plugin class.
101 """
102 module_path, class_name = plugin_class_path.rsplit(".", 1)
103 import importlib
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())
111# =============================================================================
112# Parametrized definition tests
113# =============================================================================
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.
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)
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.
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()
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.
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())
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.
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)
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.
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)
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.
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()
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.
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)
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.
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)
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.
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)