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

1"""Unit tests for core config loading functions. 

2 

3This module contains function-based pytest tests for core config utilities 

4including pyproject loading, lintro section parsing, and tool config extraction. 

5""" 

6 

7from __future__ import annotations 

8 

9from typing import Any 

10from unittest.mock import patch 

11 

12import pytest 

13from assertpy import assert_that 

14 

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) 

25 

26# ============================================================================= 

27# Fixtures 

28# ============================================================================= 

29 

30 

31@pytest.fixture 

32def reset_pyproject_cache() -> None: 

33 """Clear pyproject-related caches before each test.""" 

34 clear_pyproject_cache() 

35 

36 

37@pytest.fixture 

38def mock_empty_pyproject() -> Any: 

39 """Provide a mock that returns None for pyproject finding. 

40 

41 Returns: 

42 Context manager for patching _find_pyproject to return None. 

43 """ 

44 return patch("lintro.utils.config._find_pyproject", return_value=None) 

45 

46 

47@pytest.fixture 

48def mock_lintro_section() -> Any: 

49 """Factory fixture for mocking _get_lintro_section with custom return values. 

50 

51 Returns: 

52 Function that creates a patch context manager with the given return value. 

53 """ 

54 

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 ) 

60 

61 return _create_mock 

62 

63 

64# ============================================================================= 

65# Tests for load_pyproject error handling 

66# ============================================================================= 

67 

68 

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. 

74 

75 The load_pyproject_config function should return the same result as 

76 load_pyproject when no pyproject.toml file is found. 

77 

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

84 

85 assert_that(result).is_empty() 

86 assert_that(result).is_instance_of(dict) 

87 

88 

89# ============================================================================= 

90# Tests for _get_lintro_section 

91# ============================================================================= 

92 

93 

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. 

136 

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

145 

146 assert_that(result).is_equal_to(expected_result) 

147 assert_that(result).is_instance_of(dict) 

148 

149 

150# ============================================================================= 

151# Tests for load_lintro_global_config 

152# ============================================================================= 

153 

154 

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. 

159 

160 Tool-specific sections like 'ruff' and 'black' should be excluded from 

161 the global config, leaving only non-tool settings. 

162 

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

174 

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

178 

179 

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

187 

188 assert_that(result).is_empty() 

189 

190 

191# ============================================================================= 

192# Tests for load_lintro_tool_config 

193# ============================================================================= 

194 

195 

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. 

231 

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) 

239 

240 assert_that(result).is_equal_to(expected) 

241 assert_that(result).is_instance_of(dict) 

242 

243 

244# ============================================================================= 

245# Tests for get_tool_order_config 

246# ============================================================================= 

247 

248 

249def test_get_tool_order_config_returns_defaults_when_not_configured() -> None: 

250 """Verify get_tool_order_config returns default values when not configured. 

251 

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

257 

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) 

263 

264 

265def test_get_tool_order_config_returns_custom_values_when_configured() -> None: 

266 """Verify get_tool_order_config returns custom values when configured. 

267 

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

281 

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

287 

288 

289# ============================================================================= 

290# Tests for load_post_checks_config 

291# ============================================================================= 

292 

293 

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. 

324 

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

331 

332 assert_that(result).is_equal_to(expected) 

333 assert_that(result).is_instance_of(dict) 

334 

335 

336# ============================================================================= 

337# Tests for load_tool_config_from_pyproject 

338# ============================================================================= 

339 

340 

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. 

370 

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) 

378 

379 assert_that(result).is_equal_to(expected) 

380 assert_that(result).is_instance_of(dict)