Coverage for tests / unit / utils / config / test_manager_configuration.py: 100%

74 statements  

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

1"""Unit tests for UnifiedConfigManager apply_config, reporting, and integration.""" 

2 

3from __future__ import annotations 

4 

5from unittest.mock import MagicMock, patch 

6 

7import pytest 

8from assertpy import assert_that 

9 

10from lintro.utils.unified_config import UnifiedConfigManager 

11 

12# ============================================================================= 

13# Tests for apply_config_to_tool method 

14# ============================================================================= 

15 

16 

17def test_manager_apply_config_does_nothing_for_tool_without_name( 

18 manager: UnifiedConfigManager, 

19) -> None: 

20 """Verify apply_config_to_tool skips tools without a name. 

21 

22 When tool.name is empty, set_options should not be called. 

23 

24 

25 Args: 

26 manager: Configuration manager instance. 

27 """ 

28 mock_tool = MagicMock() 

29 mock_tool.name = "" 

30 

31 manager.apply_config_to_tool(mock_tool) 

32 

33 mock_tool.set_options.assert_not_called() 

34 

35 

36def test_manager_apply_config_calls_set_options_with_effective_config( 

37 manager: UnifiedConfigManager, 

38 mock_tool: MagicMock, 

39) -> None: 

40 """Verify apply_config_to_tool calls set_options on the tool. 

41 

42 The tool's set_options method should be called with merged config 

43 from all sources. 

44 

45 mock_tool: Mock tool instance. 

46 

47 mock_tool: Mock tool instance. 

48 

49 

50 Args: 

51 manager: Configuration manager instance. 

52 mock_tool: Mock tool instance. 

53 """ 

54 with ( 

55 patch( 

56 "lintro.utils.unified_config_manager.is_tool_injectable", 

57 return_value=True, 

58 ), 

59 patch.object(manager, "get_effective_line_length", return_value=100), 

60 patch( 

61 "lintro.utils.unified_config_manager.load_lintro_tool_config", 

62 return_value={"strict": True}, 

63 ), 

64 ): 

65 manager.apply_config_to_tool(mock_tool, cli_overrides={"debug": True}) 

66 

67 mock_tool.set_options.assert_called_once() 

68 

69 

70def test_manager_apply_config_includes_line_length_for_injectable_tools( 

71 manager: UnifiedConfigManager, 

72 mock_tool: MagicMock, 

73) -> None: 

74 """Verify line_length is included for injectable tools. 

75 

76 When is_tool_injectable returns True, line_length should be passed 

77 to set_options. 

78 

79 mock_tool: Mock tool instance. 

80 

81 mock_tool: Mock tool instance. 

82 

83 

84 Args: 

85 manager: Configuration manager instance. 

86 mock_tool: Mock tool instance. 

87 """ 

88 with ( 

89 patch( 

90 "lintro.utils.unified_config_manager.is_tool_injectable", 

91 return_value=True, 

92 ), 

93 patch.object(manager, "get_effective_line_length", return_value=100), 

94 patch( 

95 "lintro.utils.unified_config_manager.load_lintro_tool_config", 

96 return_value={}, 

97 ), 

98 ): 

99 manager.apply_config_to_tool(mock_tool) 

100 

101 call_kwargs = mock_tool.set_options.call_args[1] 

102 assert_that(call_kwargs).contains_key("line_length") 

103 assert_that(call_kwargs["line_length"]).is_equal_to(100) 

104 

105 

106def test_manager_apply_config_cli_overrides_take_precedence( 

107 manager: UnifiedConfigManager, 

108 mock_tool: MagicMock, 

109) -> None: 

110 """Verify CLI overrides have highest priority. 

111 

112 When cli_overrides conflict with other config sources, CLI values 

113 should win. 

114 

115 mock_tool: Mock tool instance. 

116 

117 mock_tool: Mock tool instance. 

118 

119 

120 Args: 

121 manager: Configuration manager instance. 

122 mock_tool: Mock tool instance. 

123 """ 

124 with ( 

125 patch( 

126 "lintro.utils.unified_config_manager.is_tool_injectable", 

127 return_value=True, 

128 ), 

129 patch.object(manager, "get_effective_line_length", return_value=100), 

130 patch( 

131 "lintro.utils.unified_config_manager.load_lintro_tool_config", 

132 return_value={"line_length": 80}, 

133 ), 

134 ): 

135 manager.apply_config_to_tool(mock_tool, cli_overrides={"line_length": 120}) 

136 

137 call_kwargs = mock_tool.set_options.call_args[1] 

138 assert_that(call_kwargs["line_length"]).is_equal_to(120) 

139 

140 

141def test_manager_apply_config_raises_value_error_from_tool( 

142 manager: UnifiedConfigManager, 

143 mock_tool: MagicMock, 

144) -> None: 

145 """Verify ValueError from tool.set_options is re-raised. 

146 

147 Configuration errors (ValueError, TypeError) should propagate to the caller. 

148 

149 mock_tool: Mock tool instance. 

150 

151 mock_tool: Mock tool instance. 

152 

153 

154 Args: 

155 manager: Configuration manager instance. 

156 mock_tool: Mock tool instance. 

157 """ 

158 mock_tool.set_options.side_effect = ValueError("Invalid value") 

159 

160 with ( 

161 patch( 

162 "lintro.utils.unified_config_manager.is_tool_injectable", 

163 return_value=True, 

164 ), 

165 patch.object(manager, "get_effective_line_length", return_value=100), 

166 patch( 

167 "lintro.utils.unified_config_manager.load_lintro_tool_config", 

168 return_value={}, 

169 ), 

170 ): 

171 with pytest.raises(ValueError, match="Invalid value"): 

172 manager.apply_config_to_tool(mock_tool) 

173 

174 

175def test_manager_apply_config_raises_type_error_from_tool( 

176 manager: UnifiedConfigManager, 

177 mock_tool: MagicMock, 

178) -> None: 

179 """Verify TypeError from tool.set_options is re-raised. 

180 

181 Configuration errors (ValueError, TypeError) should propagate to the caller. 

182 

183 mock_tool: Mock tool instance. 

184 

185 mock_tool: Mock tool instance. 

186 

187 

188 Args: 

189 manager: Configuration manager instance. 

190 mock_tool: Mock tool instance. 

191 """ 

192 mock_tool.set_options.side_effect = TypeError("Type mismatch") 

193 

194 with ( 

195 patch( 

196 "lintro.utils.unified_config_manager.is_tool_injectable", 

197 return_value=True, 

198 ), 

199 patch.object(manager, "get_effective_line_length", return_value=100), 

200 patch( 

201 "lintro.utils.unified_config_manager.load_lintro_tool_config", 

202 return_value={}, 

203 ), 

204 ): 

205 with pytest.raises(TypeError, match="Type mismatch"): 

206 manager.apply_config_to_tool(mock_tool) 

207 

208 

209def test_manager_apply_config_handles_other_errors_gracefully( 

210 manager: UnifiedConfigManager, 

211 mock_tool: MagicMock, 

212) -> None: 

213 """Verify non-config errors are caught and logged. 

214 

215 Unexpected errors (not ValueError/TypeError) should be caught and 

216 logged as warnings, not re-raised. 

217 

218 mock_tool: Mock tool instance. 

219 

220 mock_tool: Mock tool instance. 

221 

222 

223 Args: 

224 manager: Configuration manager instance. 

225 mock_tool: Mock tool instance. 

226 """ 

227 mock_tool.set_options.side_effect = RuntimeError("Unexpected") 

228 

229 with ( 

230 patch( 

231 "lintro.utils.unified_config_manager.is_tool_injectable", 

232 return_value=True, 

233 ), 

234 patch.object(manager, "get_effective_line_length", return_value=100), 

235 patch( 

236 "lintro.utils.unified_config_manager.load_lintro_tool_config", 

237 return_value={}, 

238 ), 

239 ): 

240 # Should not raise 

241 manager.apply_config_to_tool(mock_tool) 

242 

243 

244def test_manager_apply_config_skips_non_injectable_line_length( 

245 manager: UnifiedConfigManager, 

246 mock_tool: MagicMock, 

247) -> None: 

248 """Verify line_length is not set for non-injectable tools. 

249 

250 When is_tool_injectable returns False, line_length should not be 

251 included in the options. 

252 

253 mock_tool: Mock tool instance. 

254 

255 mock_tool: Mock tool instance. 

256 

257 

258 Args: 

259 manager: Configuration manager instance. 

260 mock_tool: Mock tool instance. 

261 """ 

262 with ( 

263 patch( 

264 "lintro.utils.unified_config_manager.is_tool_injectable", 

265 return_value=False, 

266 ), 

267 patch.object(manager, "get_effective_line_length", return_value=100), 

268 patch( 

269 "lintro.utils.unified_config_manager.load_lintro_tool_config", 

270 return_value={"other_option": True}, 

271 ), 

272 ): 

273 manager.apply_config_to_tool(mock_tool) 

274 

275 call_kwargs = mock_tool.set_options.call_args[1] 

276 assert_that(call_kwargs).does_not_contain_key("line_length") 

277 assert_that(call_kwargs).contains_key("other_option") 

278 

279 

280# ============================================================================= 

281# Tests for get_report method 

282# ============================================================================= 

283 

284 

285def test_manager_get_report_returns_string(manager: UnifiedConfigManager) -> None: 

286 """Verify get_report returns a string. 

287 

288 The report should be a formatted string containing configuration info. 

289 

290 

291 Args: 

292 manager: Configuration manager instance. 

293 """ 

294 with patch( 

295 "lintro.utils.config_reporting.get_config_report", 

296 return_value="Report", 

297 ): 

298 result = manager.get_report() 

299 

300 assert_that(result).is_equal_to("Report") 

301 

302 

303def test_manager_get_report_delegates_to_config_reporting( 

304 manager: UnifiedConfigManager, 

305) -> None: 

306 """Verify get_report calls the config_reporting module. 

307 

308 The report generation is delegated to get_config_report function. 

309 

310 

311 Args: 

312 manager: Configuration manager instance. 

313 """ 

314 with patch( 

315 "lintro.utils.config_reporting.get_config_report", 

316 return_value="Detailed Report", 

317 ) as mock_report: 

318 manager.get_report() 

319 

320 mock_report.assert_called_once() 

321 

322 

323# ============================================================================= 

324# Tests for print_report method 

325# ============================================================================= 

326 

327 

328def test_manager_print_report_calls_config_reporting( 

329 manager: UnifiedConfigManager, 

330) -> None: 

331 """Verify print_report delegates to print_config_report. 

332 

333 The print_report method should call the print_config_report function 

334 from the config_reporting module. 

335 

336 

337 Args: 

338 manager: Configuration manager instance. 

339 """ 

340 with patch( 

341 "lintro.utils.config_reporting.print_config_report", 

342 ) as mock_print: 

343 manager.print_report() 

344 

345 mock_print.assert_called_once() 

346 

347 

348def test_manager_print_report_does_not_return_value( 

349 manager: UnifiedConfigManager, 

350) -> None: 

351 """Verify print_report can be called successfully. 

352 

353 The method prints to console but doesn't return a value. 

354 

355 

356 Args: 

357 manager: Configuration manager instance. 

358 """ 

359 with patch("lintro.utils.config_reporting.print_config_report"): 

360 manager.print_report() # Should complete without error 

361 

362 

363# ============================================================================= 

364# Integration-style tests for UnifiedConfigManager 

365# ============================================================================= 

366 

367 

368def test_manager_is_dataclass_instance(manager: UnifiedConfigManager) -> None: 

369 """Verify UnifiedConfigManager is a proper dataclass instance. 

370 

371 The manager should be a dataclass with the expected fields. 

372 

373 

374 Args: 

375 manager: Configuration manager instance. 

376 """ 

377 import dataclasses 

378 

379 assert_that(dataclasses.is_dataclass(manager)).is_true() 

380 assert_that(dataclasses.fields(manager)).is_length(3) 

381 

382 

383def test_manager_fields_are_accessible(manager: UnifiedConfigManager) -> None: 

384 """Verify all manager fields are accessible after initialization. 

385 

386 The global_config, tool_configs, and warnings fields should be accessible. 

387 

388 

389 Args: 

390 manager: Configuration manager instance. 

391 """ 

392 assert_that(manager.global_config).is_instance_of(dict) 

393 assert_that(manager.tool_configs).is_instance_of(dict) 

394 assert_that(manager.warnings).is_instance_of(list) 

395 

396 

397def test_manager_can_be_created_with_default_factory_values() -> None: 

398 """Verify manager can be created and has default factory values. 

399 

400 The dataclass default_factory functions should create empty containers. 

401 """ 

402 with ( 

403 patch( 

404 "lintro.utils.unified_config_manager.load_lintro_global_config", 

405 return_value={}, 

406 ), 

407 patch( 

408 "lintro.utils.unified_config_manager.get_tool_config_summary", 

409 return_value={}, 

410 ), 

411 patch( 

412 "lintro.utils.unified_config_manager.validate_config_consistency", 

413 return_value=[], 

414 ), 

415 ): 

416 manager = UnifiedConfigManager() 

417 

418 assert_that(manager.global_config).is_empty() 

419 assert_that(manager.tool_configs).is_empty() 

420 assert_that(manager.warnings).is_empty()