Coverage for tests / unit / cli / test_cli_lintro_group.py: 100%
143 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"""Tests for LintroGroup and CLI module functionality."""
3from unittest.mock import patch
5from assertpy import assert_that
6from click.testing import CliRunner
8from lintro.cli import cli
11def test_format_commands_displays_canonical_names() -> None:
12 """Test that format_commands displays canonical command names."""
13 runner = CliRunner()
14 result = runner.invoke(cli, ["--help"])
15 assert_that(result.exit_code).is_equal_to(0)
16 assert_that(result.output).contains("check")
17 assert_that(result.output).contains("format")
18 assert_that(result.output).contains("test")
19 assert_that(result.output).contains("list-tools")
22def test_format_commands_with_aliases() -> None:
23 """Test that format_commands includes aliases in help."""
24 runner = CliRunner()
25 result = runner.invoke(cli, ["--help"])
26 # The output should contain commands with or without aliases
27 assert_that(result.exit_code).is_equal_to(0)
28 # Check that the Commands table is displayed (Rich table format)
29 assert_that(result.output).contains("Commands")
30 assert_that(result.output).contains("Alias")
33def test_check_command_help_displays() -> None:
34 """Test that check command help displays properly."""
35 runner = CliRunner()
36 result = runner.invoke(cli, ["check", "--help"])
37 assert_that(result.exit_code).is_equal_to(0)
38 assert_that(result.output).contains("Check files for issues")
41def test_format_command_help_displays() -> None:
42 """Test that format command help displays properly."""
43 runner = CliRunner()
44 result = runner.invoke(cli, ["format", "--help"])
45 assert_that(result.exit_code).is_equal_to(0)
46 assert_that(result.output).contains("Format code")
49def test_test_command_help_displays() -> None:
50 """Test that test command help displays properly."""
51 runner = CliRunner()
52 result = runner.invoke(cli, ["test", "--help"])
53 assert_that(result.exit_code).is_equal_to(0)
54 assert_that(result.output).contains("Run tests")
57def test_list_tools_command_help_displays() -> None:
58 """Test that list-tools command help displays properly."""
59 runner = CliRunner()
60 result = runner.invoke(cli, ["list-tools", "--help"])
61 assert_that(result.exit_code).is_equal_to(0)
64def test_invoke_single_command_execution() -> None:
65 """Test that invoke executes single command correctly."""
66 runner = CliRunner()
67 with patch("lintro.cli_utils.commands.check.run_lint_tools_simple") as mock_run:
68 mock_run.return_value = 0
69 result = runner.invoke(cli, ["check", "."])
70 assert_that(result.exit_code).is_equal_to(0)
71 mock_run.assert_called_once()
74def test_invoke_with_comma_separated_commands() -> None:
75 """Test that invoke handles comma-separated command chaining."""
76 runner = CliRunner()
77 with (
78 patch("lintro.cli_utils.commands.check.run_lint_tools_simple") as mock_check,
79 patch("lintro.cli_utils.commands.format.run_lint_tools_simple") as mock_fmt,
80 ):
81 mock_check.return_value = 0
82 mock_fmt.return_value = 0
83 result = runner.invoke(cli, ["check", ".", ",", "format", "."])
84 # Verify exit code is success
85 assert_that(result.exit_code).is_equal_to(0)
86 # Both commands should have been called exactly once
87 assert_that(mock_check.call_count).is_equal_to(1)
88 assert_that(mock_fmt.call_count).is_equal_to(1)
89 # Verify both commands were called with the expected path argument
90 mock_check.assert_any_call(
91 action="check",
92 paths=["."],
93 tools=None,
94 tool_options=None,
95 exclude=None,
96 include_venv=False,
97 group_by="file",
98 output_format="grid",
99 verbose=False,
100 raw_output=False,
101 output_file=None,
102 incremental=False,
103 debug=False,
104 stream=False,
105 no_log=False,
106 auto_install=False,
107 yes=False,
108 ai_fix=False,
109 ignore_conflicts=False,
110 )
111 mock_fmt.assert_any_call(
112 action="fmt",
113 paths=["."],
114 tools=None,
115 tool_options=None,
116 exclude=None,
117 include_venv=False,
118 group_by="auto",
119 output_format="grid",
120 verbose=False,
121 raw_output=False,
122 output_file=None,
123 debug=False,
124 stream=False,
125 no_log=False,
126 auto_install=False,
127 yes=False,
128 )
131def test_invoke_aggregates_exit_codes_success() -> None:
132 """Test that invoke aggregates exit codes from chained commands."""
133 runner = CliRunner()
134 with patch("lintro.tools.tool_manager.get_all_tools") as mock_get:
135 mock_get.return_value = {}
136 result = runner.invoke(cli, ["list-tools"])
137 # Should return 0 when command succeeds
138 assert_that(result.exit_code).is_equal_to(0)
141def test_invoke_with_version_flag() -> None:
142 """Test that invoke handles --version flag."""
143 runner = CliRunner()
144 result = runner.invoke(cli, ["--version"])
145 assert_that(result.exit_code).is_equal_to(0)
146 assert_that(result.output).contains("version")
149def test_invoke_with_help_flag() -> None:
150 """Test that invoke handles --help flag."""
151 runner = CliRunner()
152 result = runner.invoke(cli, ["--help"])
153 assert_that(result.exit_code).is_equal_to(0)
154 assert_that(result.output).contains("Lintro")
157def test_invoke_without_command_shows_help() -> None:
158 """Test that invoking cli without command succeeds."""
159 runner = CliRunner()
160 result = runner.invoke(cli, [])
161 # Should succeed (exit code 0) when invoked without command
162 assert_that(result.exit_code).is_equal_to(0)
165def test_invoke_with_invalid_command() -> None:
166 """Test that invoke handles invalid commands."""
167 runner = CliRunner()
168 result = runner.invoke(cli, ["invalid-command"])
169 assert_that(result.exit_code).is_not_equal_to(0)
172def test_invoke_command_not_found() -> None:
173 """Test error handling for non-existent command."""
174 runner = CliRunner()
175 result = runner.invoke(cli, ["nonexistent"])
176 assert_that(result.exit_code).is_not_equal_to(0)
177 assert_that(result.output).contains("No such command")
180def test_chaining_ignores_empty_command_groups() -> None:
181 """Test that chaining ignores empty command groups."""
182 runner = CliRunner()
183 with patch("lintro.cli_utils.commands.check.run_lint_tools_simple") as mock_run:
184 mock_run.return_value = 0
185 # Multiple commas in a row would create empty groups
186 result = runner.invoke(cli, ["check", ".", ",", ",", "check", "."])
187 # Should still execute the check commands, ignoring empty groups
188 assert_that(result.exit_code).is_equal_to(0)
189 assert_that(mock_run.call_count).is_equal_to(2)
190 # Verify both calls were for the expected path
191 assert_that(mock_run.call_args_list[0][1]["paths"]).is_equal_to(["."])
192 assert_that(mock_run.call_args_list[1][1]["paths"]).is_equal_to(["."])
195def test_chaining_with_flags() -> None:
196 """Test that chaining preserves flags for each command."""
197 runner = CliRunner()
198 with patch("lintro.cli_utils.commands.check.run_lint_tools_simple") as mock_run:
199 mock_run.return_value = 0
200 result = runner.invoke(
201 cli,
202 ["check", ".", "--tools", "ruff", ",", "check", "."],
203 )
204 # Verify exit code is success
205 assert_that(result.exit_code).is_equal_to(0)
206 # Both commands should have been called
207 assert_that(mock_run.call_count).is_equal_to(2)
208 # Validate first call preserves the --tools flag
209 assert_that(mock_run.call_args_list[0][1]["paths"]).is_equal_to(["."])
210 assert_that(mock_run.call_args_list[0][1]["tools"]).is_equal_to("ruff")
211 # Validate second call reflects the second command invocation with default tools
212 assert_that(mock_run.call_args_list[1][1]["paths"]).is_equal_to(["."])
213 assert_that(mock_run.call_args_list[1][1]["tools"]).is_none()
216def test_invoke_with_realistic_comma_separated_inputs() -> None:
217 """Test that invoke handles realistic comma-separated command inputs.
219 Tests inputs like fmt,chk. This test intentionally uses the single-token
220 "fmt,chk" syntax (as opposed to the multi-argument comma syntax used elsewhere).
221 """
222 runner = CliRunner()
223 with (
224 patch("lintro.cli_utils.commands.format.run_lint_tools_simple") as mock_fmt,
225 patch("lintro.cli_utils.commands.check.run_lint_tools_simple") as mock_check,
226 ):
227 mock_fmt.return_value = 0
228 mock_check.return_value = 0
229 # Test realistic input: fmt,chk (comma-separated in single token)
230 result = runner.invoke(cli, ["fmt,chk", "."])
231 assert_that(result.exit_code).is_equal_to(0)
232 # Both commands should have been called exactly once
233 assert_that(mock_fmt.call_count).is_equal_to(1)
234 assert_that(mock_check.call_count).is_equal_to(1)
237def test_invoke_with_chk_tst_input() -> None:
238 """Test that invoke handles chk,tst style input."""
239 runner = CliRunner()
240 with (
241 patch("lintro.cli_utils.commands.check.run_lint_tools_simple") as mock_check,
242 patch("lintro.cli_utils.commands.test.run_lint_tools_simple") as mock_test,
243 ):
244 mock_check.return_value = 0
245 mock_test.return_value = 0
246 # Test realistic input: chk,tst (comma-separated in single token)
247 result = runner.invoke(cli, ["chk,tst", "."])
248 assert_that(result.exit_code).is_equal_to(0)
249 # Both commands should have been called exactly once
250 assert_that(mock_check.call_count).is_equal_to(1)
251 assert_that(mock_test.call_count).is_equal_to(1)
254def test_invoke_handles_system_exit() -> None:
255 """Test that invoke properly handles SystemExit exceptions."""
256 runner = CliRunner()
257 with patch("lintro.cli_utils.commands.check.run_lint_tools_simple") as mock_run:
258 mock_run.side_effect = SystemExit(1)
259 result = runner.invoke(cli, ["check", "."])
260 # Should handle SystemExit gracefully
261 assert_that(isinstance(result.exception, SystemExit)).is_true()
262 assert_that(result.exit_code).is_equal_to(1)
263 mock_run.assert_called_once()
266def test_invoke_preserves_max_exit_code() -> None:
267 """Test that chained command execution preserves the max exit code."""
268 runner = CliRunner()
269 with patch("lintro.cli_utils.commands.check.run_lint_tools_simple") as mock_run:
270 # Simulate different exit codes from multiple runs
271 mock_run.side_effect = [0, 1]
272 result = runner.invoke(cli, ["check", ".", ",", "check", "."])
273 # Result should reflect the maximum exit code (1)
274 assert_that(result.exit_code).is_equal_to(1)
277def test_invoke_with_exception_in_command() -> None:
278 """Test error handling when command raises exception."""
279 runner = CliRunner()
280 with patch("lintro.cli_utils.commands.check.run_lint_tools_simple") as mock_run:
281 mock_run.side_effect = Exception("Test error")
282 result = runner.invoke(cli, ["check", "."])
283 # Should handle exceptions gracefully with non-zero exit code
284 assert_that(result.exit_code).is_not_equal_to(0)
285 # Verify the mocked function was called
286 mock_run.assert_called_once()