Coverage for tests / unit / cli / test_cli.py: 100%
98 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 lintro/cli.py - CLI entry point and LintroGroup."""
3from __future__ import annotations
5import contextlib
6from unittest.mock import patch
8import pytest
9from assertpy import assert_that
10from click.testing import CliRunner
12from lintro import __version__
13from lintro.cli import LintroGroup, cli, main
15# =============================================================================
16# CLI Entry Point Tests
17# =============================================================================
20def test_cli_version_option(cli_runner: CliRunner) -> None:
21 """Verify --version shows version and exits cleanly.
23 Args:
24 cli_runner: The Click CLI test runner.
25 """
26 result = cli_runner.invoke(cli, ["--version"])
28 assert_that(result.exit_code).is_equal_to(0)
29 assert_that(result.output).contains(__version__)
32def test_cli_help_option(cli_runner: CliRunner) -> None:
33 """Verify --help shows help and exits cleanly.
35 Args:
36 cli_runner: The Click CLI test runner.
37 """
38 result = cli_runner.invoke(cli, ["--help"])
40 assert_that(result.exit_code).is_equal_to(0)
41 # Rich-formatted help contains "Lintro"
42 assert_that(result.output).contains("Lintro")
45def test_cli_no_command_shows_help(cli_runner: CliRunner) -> None:
46 """Verify running cli without command shows help.
48 Args:
49 cli_runner: The Click CLI test runner.
50 """
51 result = cli_runner.invoke(cli, [])
53 assert_that(result.exit_code).is_equal_to(0)
56def test_cli_invalid_command(cli_runner: CliRunner) -> None:
57 """Verify invalid command shows error.
59 Args:
60 cli_runner: The Click CLI test runner.
61 """
62 result = cli_runner.invoke(cli, ["nonexistent-command-xyz"])
64 assert_that(result.exit_code).is_not_equal_to(0)
67def test_main_entry_point() -> None:
68 """Verify main() entry point calls cli()."""
69 with patch("lintro.cli.cli") as mock_cli:
70 mock_cli.return_value = None
71 # main() calls cli() which is a Click command
72 with contextlib.suppress(SystemExit):
73 main()
74 mock_cli.assert_called_once()
77# =============================================================================
78# LintroGroup Tests
79# =============================================================================
82def test_lintro_group_format_help_includes_commands() -> None:
83 """Verify LintroGroup.format_help includes registered commands."""
84 runner = CliRunner()
85 result = runner.invoke(cli, ["--help"])
87 # Should contain command names
88 assert_that(result.output).contains("check")
89 assert_that(result.output).contains("format")
92def test_lintro_group_format_help_includes_aliases() -> None:
93 """Verify LintroGroup.format_help shows command aliases."""
94 runner = CliRunner()
95 result = runner.invoke(cli, ["--help"])
97 # Should contain aliases
98 assert_that(result.output).contains("chk")
99 assert_that(result.output).contains("fmt")
102def test_lintro_group_format_commands_empty() -> None:
103 """Verify format_commands method exists for compatibility."""
104 import click
106 group = LintroGroup()
107 ctx = click.Context(cli)
108 formatter = click.HelpFormatter()
110 # Should not raise
111 group.format_commands(ctx, formatter)
114# =============================================================================
115# Command Registration Tests
116# =============================================================================
119def test_cli_has_check_command(cli_runner: CliRunner) -> None:
120 """Verify check command is registered.
122 Args:
123 cli_runner: The Click CLI test runner.
124 """
125 result = cli_runner.invoke(cli, ["check", "--help"])
127 assert_that(result.exit_code).is_equal_to(0)
128 assert_that(result.output).contains("Check files")
131def test_cli_has_format_command(cli_runner: CliRunner) -> None:
132 """Verify format command is registered.
134 Args:
135 cli_runner: The Click CLI test runner.
136 """
137 result = cli_runner.invoke(cli, ["format", "--help"])
139 assert_that(result.exit_code).is_equal_to(0)
140 assert_that(result.output).contains("Format")
143def test_cli_has_test_command(cli_runner: CliRunner) -> None:
144 """Verify test command is registered.
146 Args:
147 cli_runner: The Click CLI test runner.
148 """
149 result = cli_runner.invoke(cli, ["test", "--help"])
151 assert_that(result.exit_code).is_equal_to(0)
154def test_cli_has_config_command(cli_runner: CliRunner) -> None:
155 """Verify config command is registered.
157 Args:
158 cli_runner: The Click CLI test runner.
159 """
160 result = cli_runner.invoke(cli, ["config", "--help"])
162 assert_that(result.exit_code).is_equal_to(0)
165def test_cli_has_versions_command(cli_runner: CliRunner) -> None:
166 """Verify versions command is registered.
168 Args:
169 cli_runner: The Click CLI test runner.
170 """
171 result = cli_runner.invoke(cli, ["versions", "--help"])
173 assert_that(result.exit_code).is_equal_to(0)
176def test_cli_has_list_tools_command(cli_runner: CliRunner) -> None:
177 """Verify list-tools command is registered.
179 Args:
180 cli_runner: The Click CLI test runner.
181 """
182 result = cli_runner.invoke(cli, ["list-tools", "--help"])
184 assert_that(result.exit_code).is_equal_to(0)
187def test_cli_has_init_command(cli_runner: CliRunner) -> None:
188 """Verify init command is registered.
190 Args:
191 cli_runner: The Click CLI test runner.
192 """
193 result = cli_runner.invoke(cli, ["init", "--help"])
195 assert_that(result.exit_code).is_equal_to(0)
198# =============================================================================
199# Command Alias Tests
200# =============================================================================
203@pytest.mark.parametrize(
204 ("alias", "canonical"),
205 [
206 ("chk", "check"),
207 ("fmt", "format"),
208 ("tst", "test"),
209 ("cfg", "config"),
210 ("ver", "versions"),
211 ("ls", "list-tools"),
212 ],
213 ids=[
214 "chk->check",
215 "fmt->format",
216 "tst->test",
217 "cfg->config",
218 "ver->versions",
219 "ls->list-tools",
220 ],
221)
222def test_cli_alias_resolves_to_command(
223 cli_runner: CliRunner,
224 alias: str,
225 canonical: str,
226) -> None:
227 """Verify command aliases resolve to canonical commands.
229 Args:
230 cli_runner: The Click CLI test runner.
231 alias: The alias command name.
232 canonical: The canonical command name.
233 """
234 result = cli_runner.invoke(cli, [alias, "--help"])
236 assert_that(result.exit_code).is_equal_to(0)
239# =============================================================================
240# Command Chaining Tests
241# =============================================================================
244def test_lintro_group_invoke_normalizes_comma_separated_commands() -> None:
245 """Verify comma-separated commands are normalized."""
246 runner = CliRunner()
247 # This tests the parsing logic - actual execution would require mocking
248 with (
249 patch("lintro.cli_utils.commands.check.run_lint_tools_simple") as mock_check,
250 patch(
251 "lintro.cli_utils.commands.format.run_lint_tools_simple",
252 ) as mock_fmt,
253 ):
254 mock_check.return_value = 0
255 mock_fmt.return_value = 0
256 # Test comma-separated command detection
257 runner.invoke(cli, ["fmt", ",", "chk"])
258 # Both commands should have been invoked for chained execution
259 assert_that(mock_fmt.called).is_true()
260 assert_that(mock_check.called).is_true()
263def test_lintro_group_invoke_single_command() -> None:
264 """Verify single command execution works normally."""
265 runner = CliRunner()
266 with patch("lintro.cli_utils.commands.check.run_lint_tools_simple") as mock:
267 mock.return_value = 0
268 runner.invoke(cli, ["check", "."])
269 mock.assert_called_once()
272def test_lintro_group_invoke_handles_keyboard_interrupt() -> None:
273 """Verify KeyboardInterrupt is re-raised during command chaining."""
274 runner = CliRunner()
275 with patch("lintro.cli_utils.commands.check.run_lint_tools_simple") as mock:
276 mock.side_effect = KeyboardInterrupt()
277 result = runner.invoke(cli, ["check", "."])
278 # CliRunner catches KeyboardInterrupt and sets exit code
279 assert_that(result.exit_code).is_not_equal_to(0)
282def test_lintro_group_invoke_aggregates_exit_codes() -> None:
283 """Verify chained commands aggregate exit codes (max)."""
284 runner = CliRunner()
285 with (
286 patch("lintro.cli_utils.commands.format.run_lint_tools_simple") as mock_fmt,
287 patch("lintro.cli_utils.commands.check.run_lint_tools_simple") as mock_chk,
288 ):
289 # First command succeeds, second fails
290 mock_fmt.return_value = 0
291 mock_chk.return_value = 1
292 result = runner.invoke(cli, ["fmt", ",", "chk"])
293 # Result should be max of exit codes
294 assert_that(result.exit_code).is_equal_to(1)