Add tests for help and version options, and color forcing
Marco Ricci

Marco Ricci commited on 2025-01-01 14:08:48
Zeige 2 geänderte Dateien mit 120 Einfügungen und 7 Löschungen.


These sections no longer need be excluded from coverage.
... ...
@@ -993,7 +993,7 @@ def version_option_callback(
993 993
     ctx: click.Context,
994 994
     param: click.Parameter,
995 995
     value: bool,  # noqa: FBT001
996
-) -> None:  # pragma: no cover
996
+) -> None:
997 997
     del param
998 998
     if value and not ctx.resilient_parsing:
999 999
         click.echo(
... ...
@@ -1027,9 +1027,9 @@ def color_forcing_callback(
1027 1027
 ) -> None:
1028 1028
     """Force the `click` context to honor `NO_COLOR` and `FORCE_COLOR`."""
1029 1029
     del param, value
1030
-    if os.environ.get('NO_COLOR'):  # pragma: no cover
1030
+    if os.environ.get('NO_COLOR'):
1031 1031
         ctx.color = False
1032
-    if os.environ.get('FORCE_COLOR'):  # pragma: no cover
1032
+    if os.environ.get('FORCE_COLOR'):
1033 1033
         ctx.color = True
1034 1034
 
1035 1035
 
... ...
@@ -2730,13 +2730,13 @@ def derivepassphrase_vault(  # noqa: C901,PLR0912,PLR0913,PLR0914,PLR0915
2730 2730
                 group = LoggingOption
2731 2731
             elif isinstance(param, CompatibilityOption):
2732 2732
                 group = CompatibilityOption
2733
-            elif isinstance(param, StandardOption):  # pragma: no branch
2733
+            elif isinstance(param, StandardOption):
2734 2734
                 group = StandardOption
2735 2735
             elif isinstance(param, OptionGroupOption):  # pragma: no cover
2736 2736
                 raise AssertionError(  # noqa: DOC501,TRY003,TRY004
2737 2737
                     f'Unknown option group for {param!r}'  # noqa: EM102
2738 2738
                 )
2739
-            else:  # pragma: no cover
2739
+            else:
2740 2740
                 group = click.Option
2741 2741
             options_in_group.setdefault(group, []).append(param)
2742 2742
         params_by_str[param.human_readable_name] = param
... ...
@@ -280,15 +280,106 @@ def vault_config_exporter_shell_interpreter(  # noqa: C901
280 280
         )
281 281
 
282 282
 
283
+class TestAllCLI:
284
+    @pytest.mark.parametrize(
285
+        ['command', 'non_eager_arguments'],
286
+        [
287
+            ([], []),
288
+            ([], ['export']),
289
+            (['export'], []),
290
+            (['export'], ['vault']),
291
+            (['export', 'vault'], []),
292
+            (['export', 'vault'], ['--format', 'this-format-doesnt-exist']),
293
+            (['vault'], []),
294
+            (['vault'], ['--export', './']),
295
+        ]
296
+    )
297
+    @pytest.mark.parametrize('arguments', [['--help'], ['--version']])
298
+    def test_200_eager_options(
299
+        self,
300
+        monkeypatch: pytest.MonkeyPatch,
301
+        command: list[str],
302
+        arguments: list[str],
303
+        non_eager_arguments: list[str],
304
+    ) -> None:
305
+        runner = click.testing.CliRunner(mix_stderr=False)
306
+        with tests.isolated_config(
307
+            monkeypatch=monkeypatch,
308
+            runner=runner,
309
+        ):
310
+            _result = runner.invoke(
311
+                cli.derivepassphrase,
312
+                [*command, *arguments, *non_eager_arguments],
313
+                catch_exceptions=False,
314
+            )
315
+            result = tests.ReadableResult.parse(_result)
316
+        assert result.clean_exit(empty_stderr=True), 'expected clean exit'
317
+
318
+    @pytest.mark.parametrize('no_color', [False, True])
319
+    @pytest.mark.parametrize('force_color', [False, True])
320
+    @pytest.mark.parametrize('isatty', [False, True])
321
+    @pytest.mark.parametrize(
322
+        ['command_line', 'input'],
323
+        [
324
+            (
325
+                ['vault', '--import', '-'],
326
+                '{"services": {"": {"length": 20}}}',
327
+            ),
328
+        ]
329
+    )
330
+    def test_201_no_color_force_color(
331
+        self,
332
+        monkeypatch: pytest.MonkeyPatch,
333
+        no_color: bool,
334
+        force_color: bool,
335
+        isatty: bool,
336
+        command_line: list[str],
337
+        input: str | None,
338
+    ) -> None:
339
+        # Force color on if force_color.  Otherwise force color off if
340
+        # no_color.  Otherwise set color if and only if we have a TTY.
341
+        color = force_color or not no_color if isatty else force_color
342
+        runner = click.testing.CliRunner(mix_stderr=False)
343
+        with tests.isolated_config(
344
+            monkeypatch=monkeypatch,
345
+            runner=runner,
346
+        ):
347
+            if no_color:
348
+                monkeypatch.setenv('NO_COLOR', 'yes')
349
+            if force_color:
350
+                monkeypatch.setenv('FORCE_COLOR', 'yes')
351
+            _result = runner.invoke(
352
+                cli.derivepassphrase,
353
+                command_line,
354
+                input=input,
355
+                catch_exceptions=False,
356
+                color=isatty,
357
+            )
358
+            result = tests.ReadableResult.parse(_result)
359
+        assert (
360
+            not color
361
+            or '\x1b[0m' in result.stderr
362
+            or '\x1b[m' in result.stderr
363
+        ), 'Expected color, but found no ANSI reset sequence'
364
+        assert (
365
+            color or '\x1b[' not in result.stderr
366
+        ), 'Expected no color, but found an ANSI control sequence'
367
+
368
+
283 369
 class TestCLI:
284
-    def test_200_help_output(self, monkeypatch: pytest.MonkeyPatch) -> None:
370
+    def test_200_help_output(
371
+        self,
372
+        monkeypatch: pytest.MonkeyPatch,
373
+    ) -> None:
285 374
         runner = click.testing.CliRunner(mix_stderr=False)
286 375
         with tests.isolated_config(
287 376
             monkeypatch=monkeypatch,
288 377
             runner=runner,
289 378
         ):
290 379
             _result = runner.invoke(
291
-                cli.derivepassphrase_vault, ['--help'], catch_exceptions=False
380
+                cli.derivepassphrase_vault,
381
+                ['--help'],
382
+                catch_exceptions=False,
292 383
             )
293 384
             result = tests.ReadableResult.parse(_result)
294 385
         assert result.clean_exit(
... ...
@@ -298,6 +389,28 @@ class TestCLI:
298 389
             empty_stderr=True, output='Use $VISUAL or $EDITOR to configure'
299 390
         ), 'expected clean exit, and option group epilog in help text'
300 391
 
392
+    def test_200a_version_output(
393
+        self,
394
+        monkeypatch: pytest.MonkeyPatch,
395
+    ) -> None:
396
+        runner = click.testing.CliRunner(mix_stderr=False)
397
+        with tests.isolated_config(
398
+            monkeypatch=monkeypatch,
399
+            runner=runner,
400
+        ):
401
+            _result = runner.invoke(
402
+                cli.derivepassphrase_vault,
403
+                ['--version'],
404
+                catch_exceptions=False,
405
+            )
406
+            result = tests.ReadableResult.parse(_result)
407
+        assert result.clean_exit(
408
+            empty_stderr=True, output=cli.PROG_NAME
409
+        ), 'expected clean exit, and program name in version text'
410
+        assert result.clean_exit(
411
+            empty_stderr=True, output=cli.__version__
412
+        ), 'expected clean exit, and version in help text'
413
+
301 414
     @pytest.mark.parametrize(
302 415
         'charset_name', ['lower', 'upper', 'number', 'space', 'dash', 'symbol']
303 416
     )
304 417