Coverage for tests / unit / utils / console / test_logger_output_methods.py: 100%

42 statements  

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

1"""Unit tests for ThreadSafeConsoleLogger console output and log file methods. 

2 

3Tests cover the console_output method with various color options and 

4the save_console_log file creation functionality. 

5""" 

6 

7from __future__ import annotations 

8 

9from pathlib import Path 

10from unittest.mock import patch 

11 

12import pytest 

13from assertpy import assert_that 

14 

15from lintro.utils.console.logger import ThreadSafeConsoleLogger 

16 

17# ============================================================================= 

18# Console Output Method Tests 

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

20 

21 

22def test_console_output_no_color(logger: ThreadSafeConsoleLogger) -> None: 

23 """Verify console_output calls click.echo with plain text when no color specified. 

24 

25 Without a color argument, the text should be passed directly to click.echo 

26 without any styling applied. 

27 

28 Args: 

29 logger: ThreadSafeConsoleLogger instance fixture. 

30 """ 

31 with patch("click.echo") as mock_echo: 

32 logger.console_output("test message") 

33 mock_echo.assert_called_once_with("test message") 

34 

35 

36def test_console_output_with_color(logger: ThreadSafeConsoleLogger) -> None: 

37 """Verify console_output applies color styling when color argument provided. 

38 

39 When a color is specified, click.style should be called to wrap the text 

40 with the appropriate color, then click.echo displays the styled result. 

41 

42 Args: 

43 logger: ThreadSafeConsoleLogger instance fixture. 

44 """ 

45 with ( 

46 patch("click.echo") as mock_echo, 

47 patch("click.style", return_value="styled text") as mock_style, 

48 ): 

49 logger.console_output("test message", color="red") 

50 mock_style.assert_called_once_with("test message", fg="red") 

51 mock_echo.assert_called_once_with("styled text") 

52 

53 

54@pytest.mark.parametrize( 

55 ("color", "expected_fg"), 

56 [ 

57 pytest.param("red", "red", id="red"), 

58 pytest.param("green", "green", id="green"), 

59 pytest.param("yellow", "yellow", id="yellow"), 

60 pytest.param("cyan", "cyan", id="cyan"), 

61 pytest.param("blue", "blue", id="blue"), 

62 pytest.param("magenta", "magenta", id="magenta"), 

63 ], 

64) 

65def test_console_output_various_colors( 

66 logger: ThreadSafeConsoleLogger, 

67 color: str, 

68 expected_fg: str, 

69) -> None: 

70 """Verify console_output correctly applies various color options. 

71 

72 Different color values should be passed through to click.style's fg parameter 

73 without modification. 

74 

75 Args: 

76 logger: ThreadSafeConsoleLogger instance fixture. 

77 color: The color to apply to output. 

78 expected_fg: The expected foreground color value. 

79 """ 

80 with patch("click.echo"), patch("click.style") as mock_style: 

81 logger.console_output("test", color=color) 

82 mock_style.assert_called_once_with("test", fg=expected_fg) 

83 

84 

85# ============================================================================= 

86# Console Log File Tests 

87# ============================================================================= 

88 

89 

90def test_save_console_log_creates_file(tmp_path: Path) -> None: 

91 """Verify save_console_log creates console.log file in run directory. 

92 

93 When a run_dir is configured, save_console_log should create a console.log 

94 file marker in that directory. 

95 

96 Args: 

97 tmp_path: Temporary directory path for test files. 

98 """ 

99 logger = ThreadSafeConsoleLogger(run_dir=tmp_path) 

100 logger.save_console_log() 

101 log_file = tmp_path / "console.log" 

102 assert_that(log_file.exists()).is_true() 

103 

104 

105def test_save_console_log_no_run_dir_is_noop(logger: ThreadSafeConsoleLogger) -> None: 

106 """Verify save_console_log does nothing when no run directory configured. 

107 

108 Without a run_dir, there's nowhere to save the log file, so the method 

109 should complete without error and without side effects. 

110 

111 Args: 

112 logger: ThreadSafeConsoleLogger instance fixture. 

113 """ 

114 # Should not raise any exception 

115 logger.save_console_log() 

116 

117 

118def test_save_console_log_handles_os_error(tmp_path: Path) -> None: 

119 """Verify save_console_log handles OSError gracefully with error log. 

120 

121 When file creation fails due to OS-level issues, the error should be 

122 caught and logged rather than propagating as an exception. 

123 

124 Args: 

125 tmp_path: Temporary directory path for test files. 

126 """ 

127 logger = ThreadSafeConsoleLogger(run_dir=tmp_path) 

128 logger._messages = ["Test message"] 

129 with ( 

130 patch("builtins.open", side_effect=OSError("Permission denied")), 

131 patch("lintro.utils.console.logger.logger.error") as mock_error, 

132 ): 

133 logger.save_console_log() 

134 mock_error.assert_called_once() 

135 error_message = str(mock_error.call_args) 

136 assert_that(error_message).contains( 

137 "Failed to save console log", 

138 ) 

139 

140 

141def test_save_console_log_handles_permission_error(tmp_path: Path) -> None: 

142 """Verify save_console_log handles PermissionError gracefully. 

143 

144 Permission errors during file creation should be caught and logged 

145 without crashing the application. 

146 

147 Args: 

148 tmp_path: Temporary directory path for test files. 

149 """ 

150 logger = ThreadSafeConsoleLogger(run_dir=tmp_path) 

151 logger._messages = ["Test message"] 

152 with ( 

153 patch( 

154 "builtins.open", 

155 side_effect=PermissionError("Access denied"), 

156 ), 

157 patch("lintro.utils.console.logger.logger.error") as mock_error, 

158 ): 

159 logger.save_console_log() 

160 mock_error.assert_called_once() 

161 assert_that(str(mock_error.call_args)).contains( 

162 "Failed to save console log", 

163 )