Coverage for tests / unit / config / test_config_loaders.py: 100%
74 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 core config loading functions.
3This module contains function-based pytest tests for core config utilities
4including pyproject loading, lintro section parsing, and tool config extraction.
5"""
7from __future__ import annotations
9from typing import Any
10from unittest.mock import patch
12import pytest
13from assertpy import assert_that
15from lintro.utils.config import (
16 _get_lintro_section,
17 clear_pyproject_cache,
18 get_tool_order_config,
19 load_lintro_global_config,
20 load_lintro_tool_config,
21 load_post_checks_config,
22 load_pyproject_config,
23 load_tool_config_from_pyproject,
24)
26# =============================================================================
27# Fixtures
28# =============================================================================
31@pytest.fixture
32def reset_pyproject_cache() -> None:
33 """Clear pyproject-related caches before each test."""
34 clear_pyproject_cache()
37@pytest.fixture
38def mock_empty_pyproject() -> Any:
39 """Provide a mock that returns None for pyproject finding.
41 Returns:
42 Context manager for patching _find_pyproject to return None.
43 """
44 return patch("lintro.utils.config._find_pyproject", return_value=None)
47@pytest.fixture
48def mock_lintro_section() -> Any:
49 """Factory fixture for mocking _get_lintro_section with custom return values.
51 Returns:
52 Function that creates a patch context manager with the given return value.
53 """
55 def _create_mock(return_value: dict[str, Any]) -> Any:
56 return patch(
57 "lintro.utils.config._get_lintro_section",
58 return_value=return_value,
59 )
61 return _create_mock
64# =============================================================================
65# Tests for load_pyproject error handling
66# =============================================================================
69def test_load_pyproject_config_is_alias_for_load_pyproject(
70 reset_pyproject_cache: None,
71 mock_empty_pyproject: Any,
72) -> None:
73 """Verify load_pyproject_config is an alias for load_pyproject.
75 The load_pyproject_config function should return the same result as
76 load_pyproject when no pyproject.toml file is found.
78 Args:
79 reset_pyproject_cache: Fixture to clear caches.
80 mock_empty_pyproject: Mock for patching _find_pyproject to return None.
81 """
82 with mock_empty_pyproject:
83 result = load_pyproject_config()
85 assert_that(result).is_empty()
86 assert_that(result).is_instance_of(dict)
89# =============================================================================
90# Tests for _get_lintro_section
91# =============================================================================
94@pytest.mark.parametrize(
95 ("pyproject_data", "expected_result", "description"),
96 [
97 pytest.param(
98 {"tool": "invalid"},
99 {},
100 "non-dict tool section",
101 id="invalid-tool-section",
102 ),
103 pytest.param(
104 {"tool": {"lintro": "invalid"}},
105 {},
106 "non-dict lintro section",
107 id="invalid-lintro-section",
108 ),
109 pytest.param(
110 {"tool": {"lintro": {"key": "value"}}},
111 {"key": "value"},
112 "valid lintro section",
113 id="valid-lintro-section",
114 ),
115 pytest.param(
116 {"tool": {}},
117 {},
118 "empty tool section",
119 id="empty-tool-section",
120 ),
121 pytest.param(
122 {},
123 {},
124 "empty pyproject",
125 id="empty-pyproject",
126 ),
127 ],
128)
129def test_get_lintro_section_handles_various_inputs(
130 reset_pyproject_cache: None,
131 pyproject_data: dict[str, Any],
132 expected_result: dict[str, Any],
133 description: str,
134) -> None:
135 """Test _get_lintro_section handles various pyproject.toml structures.
137 Args:
138 reset_pyproject_cache: Fixture to clear caches.
139 pyproject_data: The mock pyproject.toml data to test.
140 expected_result: The expected return value.
141 description: Human-readable description of the test case.
142 """
143 with patch("lintro.utils.config.load_pyproject", return_value=pyproject_data):
144 result = _get_lintro_section()
146 assert_that(result).is_equal_to(expected_result)
147 assert_that(result).is_instance_of(dict)
150# =============================================================================
151# Tests for load_lintro_global_config
152# =============================================================================
155def test_load_lintro_global_config_filters_tool_sections(
156 mock_lintro_section: Any,
157) -> None:
158 """Verify load_lintro_global_config filters out tool-specific sections.
160 Tool-specific sections like 'ruff' and 'black' should be excluded from
161 the global config, leaving only non-tool settings.
163 Args:
164 mock_lintro_section: Factory fixture for mocking _get_lintro_section.
165 """
166 mock_data = {
167 "global_setting": "value",
168 "ruff": {"line_length": 88},
169 "black": {"line_length": 88},
170 "another_global": 42,
171 }
172 with mock_lintro_section(mock_data):
173 result = load_lintro_global_config()
175 assert_that(result).is_equal_to({"global_setting": "value", "another_global": 42})
176 assert_that(result).does_not_contain_key("ruff")
177 assert_that(result).does_not_contain_key("black")
180def test_load_lintro_global_config_returns_empty_when_no_globals() -> None:
181 """Verify load_lintro_global_config returns empty dict when only tools present."""
182 with patch(
183 "lintro.utils.config._get_lintro_section",
184 return_value={"ruff": {}, "black": {}, "mypy": {}},
185 ):
186 result = load_lintro_global_config()
188 assert_that(result).is_empty()
191# =============================================================================
192# Tests for load_lintro_tool_config
193# =============================================================================
196@pytest.mark.parametrize(
197 ("section_data", "tool_name", "expected"),
198 [
199 pytest.param(
200 {"ruff": "invalid"},
201 "ruff",
202 {},
203 id="non-dict-tool-config",
204 ),
205 pytest.param(
206 {"ruff": {"line_length": 100}},
207 "ruff",
208 {"line_length": 100},
209 id="valid-tool-config",
210 ),
211 pytest.param(
212 {"black": {"line_length": 88}},
213 "ruff",
214 {},
215 id="tool-not-present",
216 ),
217 pytest.param(
218 {},
219 "ruff",
220 {},
221 id="empty-section",
222 ),
223 ],
224)
225def test_load_lintro_tool_config_handles_various_inputs(
226 section_data: dict[str, Any],
227 tool_name: str,
228 expected: dict[str, Any],
229) -> None:
230 """Test load_lintro_tool_config with various section configurations.
232 Args:
233 section_data: Mock data for _get_lintro_section.
234 tool_name: Name of the tool to load config for.
235 expected: Expected return value.
236 """
237 with patch("lintro.utils.config._get_lintro_section", return_value=section_data):
238 result = load_lintro_tool_config(tool_name)
240 assert_that(result).is_equal_to(expected)
241 assert_that(result).is_instance_of(dict)
244# =============================================================================
245# Tests for get_tool_order_config
246# =============================================================================
249def test_get_tool_order_config_returns_defaults_when_not_configured() -> None:
250 """Verify get_tool_order_config returns default values when not configured.
252 The default strategy should be 'priority' with empty custom_order and
253 priority_overrides.
254 """
255 with patch("lintro.utils.config.load_lintro_global_config", return_value={}):
256 result = get_tool_order_config()
258 assert_that(result["strategy"]).is_equal_to("priority")
259 assert_that(result["custom_order"]).is_empty()
260 assert_that(result["custom_order"]).is_instance_of(list)
261 assert_that(result["priority_overrides"]).is_empty()
262 assert_that(result["priority_overrides"]).is_instance_of(dict)
265def test_get_tool_order_config_returns_custom_values_when_configured() -> None:
266 """Verify get_tool_order_config returns custom values when configured.
268 When tool_order, tool_order_custom, and tool_priorities are set in the
269 config, they should be returned in the result dictionary.
270 """
271 mock_config = {
272 "tool_order": "custom",
273 "tool_order_custom": ["ruff", "black", "mypy"],
274 "tool_priorities": {"ruff": 100, "black": 50},
275 }
276 with patch(
277 "lintro.utils.config.load_lintro_global_config",
278 return_value=mock_config,
279 ):
280 result = get_tool_order_config()
282 assert_that(result["strategy"]).is_equal_to("custom")
283 assert_that(result["custom_order"]).is_equal_to(["ruff", "black", "mypy"])
284 assert_that(result["custom_order"]).is_length(3)
285 assert_that(result["priority_overrides"]).is_equal_to({"ruff": 100, "black": 50})
286 assert_that(result["priority_overrides"]).contains_key("ruff")
289# =============================================================================
290# Tests for load_post_checks_config
291# =============================================================================
294@pytest.mark.parametrize(
295 ("section_data", "expected"),
296 [
297 pytest.param(
298 {"post_checks": {"enabled": True, "tools": ["black"]}},
299 {"enabled": True, "tools": ["black"]},
300 id="valid-post-checks-section",
301 ),
302 pytest.param(
303 {"post_checks": "invalid"},
304 {},
305 id="non-dict-post-checks",
306 ),
307 pytest.param(
308 {},
309 {},
310 id="missing-post-checks",
311 ),
312 pytest.param(
313 {"post_checks": {}},
314 {},
315 id="empty-post-checks",
316 ),
317 ],
318)
319def test_load_post_checks_config_handles_various_inputs(
320 section_data: dict[str, Any],
321 expected: dict[str, Any],
322) -> None:
323 """Test load_post_checks_config with various section configurations.
325 Args:
326 section_data: Mock data for _get_lintro_section.
327 expected: Expected return value.
328 """
329 with patch("lintro.utils.config._get_lintro_section", return_value=section_data):
330 result = load_post_checks_config()
332 assert_that(result).is_equal_to(expected)
333 assert_that(result).is_instance_of(dict)
336# =============================================================================
337# Tests for load_tool_config_from_pyproject
338# =============================================================================
341@pytest.mark.parametrize(
342 ("pyproject_data", "tool_name", "expected"),
343 [
344 pytest.param(
345 {"tool": {"ruff": "invalid"}},
346 "ruff",
347 {},
348 id="non-dict-tool-config",
349 ),
350 pytest.param(
351 {"tool": {"ruff": {"line-length": 100}}},
352 "ruff",
353 {"line-length": 100},
354 id="valid-tool-config",
355 ),
356 pytest.param(
357 {"tool": {}},
358 "ruff",
359 {},
360 id="tool-not-in-pyproject",
361 ),
362 ],
363)
364def test_load_tool_config_from_pyproject_handles_various_inputs(
365 pyproject_data: dict[str, Any],
366 tool_name: str,
367 expected: dict[str, Any],
368) -> None:
369 """Test load_tool_config_from_pyproject with various configurations.
371 Args:
372 pyproject_data: Mock pyproject.toml data.
373 tool_name: Name of the tool to load config for.
374 expected: Expected return value.
375 """
376 with patch("lintro.utils.config.load_pyproject", return_value=pyproject_data):
377 result = load_tool_config_from_pyproject(tool_name)
379 assert_that(result).is_equal_to(expected)
380 assert_that(result).is_instance_of(dict)