Use hypothesis for notes handling tests
Marco Ricci

Marco Ricci commited on 2025-02-05 14:40:56
Zeige 1 geänderte Dateien mit 109 Einfügungen und 14 Löschungen.


Have the existing or the new notes be generated data (whichever is
appropriate).

Also handle the previously untested case of aborting the notes edit when
there are no stored notes yet.  *This* test does not use hypothesis.
... ...
@@ -29,7 +29,11 @@ from typing_extensions import Any, NamedTuple
29 29
 
30 30
 import tests
31 31
 from derivepassphrase import _types, cli, ssh_agent, vault
32
-from derivepassphrase._internals import cli_helpers, cli_machinery
32
+from derivepassphrase._internals import (
33
+    cli_helpers,
34
+    cli_machinery,
35
+    cli_messages,
36
+)
33 37
 
34 38
 if TYPE_CHECKING:
35 39
     from collections.abc import Callable, Iterable, Iterator, Sequence
... ...
@@ -2464,14 +2468,24 @@ class TestCLI:
2464 2468
             'expected error exit and known error message'
2465 2469
         )
2466 2470
 
2471
+    @hypothesis.given(
2472
+        notes=strategies.text(
2473
+            strategies.characters(
2474
+                min_codepoint=32, max_codepoint=126, include_characters='\n'
2475
+            ),
2476
+            min_size=1,
2477
+            max_size=512,
2478
+        ).filter(str.strip),
2479
+    )
2467 2480
     def test_220_edit_notes_successfully(
2468 2481
         self,
2482
+        notes: str,
2469 2483
     ) -> None:
2470 2484
         """Editing notes works."""
2471
-        edit_result = """
2485
+        edit_result = f"""
2472 2486
 
2473 2487
 # - - - - - >8 - - - - - >8 - - - - - >8 - - - - - >8 - - - - -
2474
-insert witty notes here
2488
+{notes}
2475 2489
 """
2476 2490
         runner = click.testing.CliRunner(mix_stderr=False)
2477 2491
         # TODO(the-13th-letter): Rewrite using parenthesized
... ...
@@ -2503,13 +2517,34 @@ insert witty notes here
2503 2517
                 config = json.load(infile)
2504 2518
             assert config == {
2505 2519
                 'global': {'phrase': 'abc'},
2506
-                'services': {'sv': {'notes': 'insert witty notes here'}},
2520
+                'services': {'sv': {'notes': notes.strip()}},
2507 2521
             }
2508 2522
 
2523
+    @pytest.mark.parametrize('edit_func_name', ['empty', 'space'])
2524
+    @hypothesis.given(
2525
+        notes=strategies.text(
2526
+            strategies.characters(
2527
+                min_codepoint=32, max_codepoint=126, include_characters='\n'
2528
+            ),
2529
+            min_size=1,
2530
+            max_size=512,
2531
+        ).filter(str.strip),
2532
+    )
2509 2533
     def test_221_edit_notes_noop(
2510 2534
         self,
2535
+        edit_func_name: Literal['empty', 'space'],
2536
+        notes: str,
2511 2537
     ) -> None:
2512 2538
         """Abandoning edited notes works."""
2539
+
2540
+        def empty(text: str, *_args: Any, **_kwargs: Any) -> None:
2541
+            del text
2542
+
2543
+        def space(text: str, *_args: Any, **_kwargs: Any) -> str:
2544
+            del text
2545
+            return '       ' + notes + '\n\n\n\n\n\n'
2546
+
2547
+        edit_funcs = {'empty': empty, 'space': space}
2513 2548
         runner = click.testing.CliRunner(mix_stderr=False)
2514 2549
         # TODO(the-13th-letter): Rewrite using parenthesized
2515 2550
         # with-statements.
... ...
@@ -2522,15 +2557,12 @@ insert witty notes here
2522 2557
                     runner=runner,
2523 2558
                     vault_config={
2524 2559
                         'global': {'phrase': 'abc'},
2525
-                        'services': {'sv': {'notes': 'Contents go here'}},
2560
+                        'services': {'sv': {'notes': notes}},
2526 2561
                     },
2527 2562
                 )
2528 2563
             )
2529 2564
 
2530
-            def space(text: str, *_args: Any, **_kwargs: Any) -> str:
2531
-                return '       ' + text + '\n\n\n\n\n\n'
2532
-
2533
-            monkeypatch.setattr(click, 'edit', space)
2565
+            monkeypatch.setattr(click, 'edit', edit_funcs[edit_func_name])
2534 2566
             result_ = runner.invoke(
2535 2567
                 cli.derivepassphrase_vault,
2536 2568
                 ['--config', '--notes', '--', 'sv'],
... ...
@@ -2544,19 +2576,33 @@ insert witty notes here
2544 2576
                 config = json.load(infile)
2545 2577
             assert config == {
2546 2578
                 'global': {'phrase': 'abc'},
2547
-                'services': {'sv': {'notes': 'Contents go here'}},
2579
+                'services': {'sv': {'notes': notes}},
2548 2580
             }
2549 2581
 
2550 2582
     # TODO(the-13th-letter): Keep this behavior or not, with or without
2551 2583
     # warning?
2584
+    @hypothesis.given(
2585
+        notes=strategies.text(
2586
+            strategies.characters(
2587
+                min_codepoint=32, max_codepoint=126, include_characters='\n'
2588
+            ),
2589
+            min_size=1,
2590
+            max_size=512,
2591
+        ).filter(str.strip),
2592
+    )
2552 2593
     def test_222_edit_notes_marker_removed(
2553 2594
         self,
2595
+        notes: str,
2554 2596
     ) -> None:
2555 2597
         """Removing the notes marker still saves the notes.
2556 2598
 
2557 2599
         TODO: Keep this behavior or not, with or without warning?
2558 2600
 
2559 2601
         """
2602
+        notes_marker = cli_messages.TranslatedString(
2603
+            cli_messages.Label.DERIVEPASSPHRASE_VAULT_NOTES_MARKER
2604
+        )
2605
+        hypothesis.assume(str(notes_marker) not in notes.strip())
2560 2606
         runner = click.testing.CliRunner(mix_stderr=False)
2561 2607
         # TODO(the-13th-letter): Rewrite using parenthesized
2562 2608
         # with-statements.
... ...
@@ -2573,7 +2619,7 @@ insert witty notes here
2573 2619
                     },
2574 2620
                 )
2575 2621
             )
2576
-            monkeypatch.setattr(click, 'edit', lambda *_a, **_kw: 'long\ntext')
2622
+            monkeypatch.setattr(click, 'edit', lambda *_a, **_kw: notes)
2577 2623
             result_ = runner.invoke(
2578 2624
                 cli.derivepassphrase_vault,
2579 2625
                 ['--config', '--notes', '--', 'sv'],
... ...
@@ -2587,11 +2633,21 @@ insert witty notes here
2587 2633
                 config = json.load(infile)
2588 2634
             assert config == {
2589 2635
                 'global': {'phrase': 'abc'},
2590
-                'services': {'sv': {'notes': 'long\ntext'}},
2636
+                'services': {'sv': {'notes': notes.strip()}},
2591 2637
             }
2592 2638
 
2639
+    @hypothesis.given(
2640
+        notes=strategies.text(
2641
+            strategies.characters(
2642
+                min_codepoint=32, max_codepoint=126, include_characters='\n'
2643
+            ),
2644
+            min_size=1,
2645
+            max_size=512,
2646
+        ).filter(str.strip),
2647
+    )
2593 2648
     def test_223_edit_notes_abort(
2594 2649
         self,
2650
+        notes: str,
2595 2651
     ) -> None:
2596 2652
         """Aborting editing notes works."""
2597 2653
         runner = click.testing.CliRunner(mix_stderr=False)
... ...
@@ -2606,7 +2662,7 @@ insert witty notes here
2606 2662
                     runner=runner,
2607 2663
                     vault_config={
2608 2664
                         'global': {'phrase': 'abc'},
2609
-                        'services': {'sv': {'notes': 'Contents go here'}},
2665
+                        'services': {'sv': {'notes': notes.strip()}},
2610 2666
                     },
2611 2667
                 )
2612 2668
             )
... ...
@@ -2626,7 +2682,46 @@ insert witty notes here
2626 2682
                 config = json.load(infile)
2627 2683
             assert config == {
2628 2684
                 'global': {'phrase': 'abc'},
2629
-                'services': {'sv': {'notes': 'Contents go here'}},
2685
+                'services': {'sv': {'notes': notes.strip()}},
2686
+            }
2687
+
2688
+    def test_223a_edit_empty_notes_abort(
2689
+        self,
2690
+    ) -> None:
2691
+        """Aborting editing notes works even if no notes are stored yet."""
2692
+        runner = click.testing.CliRunner(mix_stderr=False)
2693
+        # TODO(the-13th-letter): Rewrite using parenthesized
2694
+        # with-statements.
2695
+        # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9
2696
+        with contextlib.ExitStack() as stack:
2697
+            monkeypatch = stack.enter_context(pytest.MonkeyPatch.context())
2698
+            stack.enter_context(
2699
+                tests.isolated_vault_config(
2700
+                    monkeypatch=monkeypatch,
2701
+                    runner=runner,
2702
+                    vault_config={
2703
+                        'global': {'phrase': 'abc'},
2704
+                        'services': {},
2705
+                    },
2706
+                )
2707
+            )
2708
+            monkeypatch.setattr(click, 'edit', lambda *_a, **_kw: '')
2709
+            result_ = runner.invoke(
2710
+                cli.derivepassphrase_vault,
2711
+                ['--config', '--notes', '--', 'sv'],
2712
+                catch_exceptions=False,
2713
+            )
2714
+            result = tests.ReadableResult.parse(result_)
2715
+            assert result.error_exit(error='the user aborted the request'), (
2716
+                'expected known error message'
2717
+            )
2718
+            with cli_helpers.config_filename(subsystem='vault').open(
2719
+                encoding='UTF-8'
2720
+            ) as infile:
2721
+                config = json.load(infile)
2722
+            assert config == {
2723
+                'global': {'phrase': 'abc'},
2724
+                'services': {},
2630 2725
             }
2631 2726
 
2632 2727
     @Parametrize.CONFIG_EDITING_VIA_CONFIG_FLAG
2633 2728