Marco Ricci commited on 2025-08-15 06:51:10
              Zeige 1 geänderte Dateien mit 163 Einfügungen und 21 Löschungen.
            
Implement alluded to, but missing, tests for `derivepassphrase vault`: passphrase usage based on stored configuration, passphrase usage based on the command-line, and exporting configurations that were originally smudged upon import.
| ... | ... | 
                      @@ -273,6 +273,9 @@ def assert_vault_config_is_indented_and_line_broken(  | 
                  
| 273 | 273 | 
                        class Parametrize(types.SimpleNamespace):  | 
                    
| 274 | 274 | 
                        """Common test parametrizations."""  | 
                    
| 275 | 275 | 
                         | 
                    
| 276 | 
                        + AUTO_PROMPT = pytest.mark.parametrize(  | 
                    |
| 277 | 
                        + "auto_prompt", [False, True], ids=["normal_prompt", "auto_prompt"]  | 
                    |
| 278 | 
                        + )  | 
                    |
| 276 | 279 | 
                        CHARSET_NAME = pytest.mark.parametrize(  | 
                    
| 277 | 280 | 
                        "charset_name", ["lower", "upper", "number", "space", "dash", "symbol"]  | 
                    
| 278 | 281 | 
                        )  | 
                    
| ... | ... | 
                      @@ -428,6 +431,34 @@ class Parametrize(types.SimpleNamespace):  | 
                  
| 428 | 431 | 
                        ),  | 
                    
| 429 | 432 | 
                        ],  | 
                    
| 430 | 433 | 
                        )  | 
                    
| 434 | 
                        + CONFIG_WITH_PHRASE = pytest.mark.parametrize(  | 
                    |
| 435 | 
                        + "config",  | 
                    |
| 436 | 
                        + [  | 
                    |
| 437 | 
                        + pytest.param(  | 
                    |
| 438 | 
                        +                {
                       | 
                    |
| 439 | 
                        +                    "global": {"phrase": DUMMY_PASSPHRASE.rstrip("\n")},
                       | 
                    |
| 440 | 
                        +                    "services": {DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS},
                       | 
                    |
| 441 | 
                        + },  | 
                    |
| 442 | 
                        + id="global",  | 
                    |
| 443 | 
                        + ),  | 
                    |
| 444 | 
                        + pytest.param(  | 
                    |
| 445 | 
                        +                {
                       | 
                    |
| 446 | 
                        +                    "global": {
                       | 
                    |
| 447 | 
                        +                        "phrase": DUMMY_PASSPHRASE.rstrip("\n")
                       | 
                    |
| 448 | 
                        + + "XXX"  | 
                    |
| 449 | 
                        +                        + DUMMY_PASSPHRASE.rstrip("\n")
                       | 
                    |
| 450 | 
                        + },  | 
                    |
| 451 | 
                        +                    "services": {
                       | 
                    |
| 452 | 
                        +                        DUMMY_SERVICE: {
                       | 
                    |
| 453 | 
                        +                            "phrase": DUMMY_PASSPHRASE.rstrip("\n"),
                       | 
                    |
| 454 | 
                        + **DUMMY_CONFIG_SETTINGS,  | 
                    |
| 455 | 
                        + }  | 
                    |
| 456 | 
                        + },  | 
                    |
| 457 | 
                        + },  | 
                    |
| 458 | 
                        + id="service",  | 
                    |
| 459 | 
                        + ),  | 
                    |
| 460 | 
                        + ],  | 
                    |
| 461 | 
                        + )  | 
                    |
| 431 | 462 | 
                        VALID_TEST_CONFIGS = pytest.mark.parametrize(  | 
                    
| 432 | 463 | 
                        "config",  | 
                    
| 433 | 464 | 
                        [conf.config for conf in TEST_CONFIGS if conf.is_valid()],  | 
                    
| ... | ... | 
                      @@ -786,29 +817,85 @@ class TestDerivedPassphraseConstraints:  | 
                  
| 786 | 817 | 
                        class TestPhraseBasic:  | 
                    
| 787 | 818 | 
                        """Tests for master passphrase configuration: basic."""  | 
                    
| 788 | 819 | 
                         | 
                    
| 789 | 
                        - @pytest.mark.xfail(  | 
                    |
| 790 | 
                        - True,  | 
                    |
| 791 | 
                        - reason="not implemented yet",  | 
                    |
| 792 | 
                        - raises=NotImplementedError,  | 
                    |
| 793 | 
                        - strict=True,  | 
                    |
| 794 | 
                        - )  | 
                    |
| 820 | 
                        + @Parametrize.CONFIG_WITH_PHRASE  | 
                    |
| 795 | 821 | 
                        def test_phrase_from_config(  | 
                    
| 796 | 822 | 
                        self,  | 
                    
| 823 | 
                        + config: _types.VaultConfig,  | 
                    |
| 797 | 824 | 
                        ) -> None:  | 
                    
| 798 | 825 | 
                        """A stored configured master passphrase will be used."""  | 
                    
| 799 | 
                        - raise NotImplementedError  | 
                    |
| 826 | 
                        + runner = machinery.CliRunner(mix_stderr=False)  | 
                    |
| 827 | 
                        + # TODO(the-13th-letter): Rewrite using parenthesized  | 
                    |
| 828 | 
                        + # with-statements.  | 
                    |
| 829 | 
                        + # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9  | 
                    |
| 830 | 
                        + with contextlib.ExitStack() as stack:  | 
                    |
| 831 | 
                        + monkeypatch = stack.enter_context(pytest.MonkeyPatch.context())  | 
                    |
| 832 | 
                        + stack.enter_context(  | 
                    |
| 833 | 
                        + pytest_machinery.isolated_vault_config(  | 
                    |
| 834 | 
                        + monkeypatch=monkeypatch,  | 
                    |
| 835 | 
                        + runner=runner,  | 
                    |
| 836 | 
                        + vault_config=config,  | 
                    |
| 837 | 
                        + )  | 
                    |
| 838 | 
                        + )  | 
                    |
| 800 | 839 | 
                         | 
                    
| 801 | 
                        - @pytest.mark.xfail(  | 
                    |
| 802 | 
                        - True,  | 
                    |
| 803 | 
                        - reason="not implemented yet",  | 
                    |
| 804 | 
                        - raises=NotImplementedError,  | 
                    |
| 805 | 
                        - strict=True,  | 
                    |
| 840 | 
                        + def phrase_from_key(*_args: Any, **_kwargs: Any) -> NoReturn:  | 
                    |
| 841 | 
                        +                pytest.fail("Attempted to use a key in a phrase-based test!")
                       | 
                    |
| 842 | 
                        +  | 
                    |
| 843 | 
                        + monkeypatch.setattr(  | 
                    |
| 844 | 
                        + vault.Vault, "phrase_from_key", phrase_from_key  | 
                    |
| 845 | 
                        + )  | 
                    |
| 846 | 
                        + result = runner.invoke(  | 
                    |
| 847 | 
                        + cli.derivepassphrase_vault,  | 
                    |
| 848 | 
                        + ["--", DUMMY_SERVICE],  | 
                    |
| 849 | 
                        + catch_exceptions=False,  | 
                    |
| 806 | 850 | 
                        )  | 
                    
| 851 | 
                        + assert result.clean_exit(empty_stderr=True), (  | 
                    |
| 852 | 
                        + "expected clean exit and empty stderr"  | 
                    |
| 853 | 
                        + )  | 
                    |
| 854 | 
                        + assert result.stdout  | 
                    |
| 855 | 
                        + assert (  | 
                    |
| 856 | 
                        +            result.stdout.rstrip("\n").encode("UTF-8")
                       | 
                    |
| 857 | 
                        + == DUMMY_RESULT_PASSPHRASE  | 
                    |
| 858 | 
                        + ), "expected known output"  | 
                    |
| 859 | 
                        +  | 
                    |
| 860 | 
                        + @Parametrize.AUTO_PROMPT  | 
                    |
| 807 | 861 | 
                        def test_phrase_from_command_line(  | 
                    
| 808 | 862 | 
                        self,  | 
                    
| 863 | 
                        + auto_prompt: bool,  | 
                    |
| 809 | 864 | 
                        ) -> None:  | 
                    
| 810 | 865 | 
                        """A master passphrase requested on the command-line will be used."""  | 
                    
| 811 | 
                        - raise NotImplementedError  | 
                    |
| 866 | 
                        + runner = machinery.CliRunner(mix_stderr=False)  | 
                    |
| 867 | 
                        + # TODO(the-13th-letter): Rewrite using parenthesized  | 
                    |
| 868 | 
                        + # with-statements.  | 
                    |
| 869 | 
                        + # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9  | 
                    |
| 870 | 
                        + with contextlib.ExitStack() as stack:  | 
                    |
| 871 | 
                        + monkeypatch = stack.enter_context(pytest.MonkeyPatch.context())  | 
                    |
| 872 | 
                        + stack.enter_context(  | 
                    |
| 873 | 
                        + pytest_machinery.isolated_vault_config(  | 
                    |
| 874 | 
                        + monkeypatch=monkeypatch,  | 
                    |
| 875 | 
                        + runner=runner,  | 
                    |
| 876 | 
                        +                    vault_config={
                       | 
                    |
| 877 | 
                        +                        "services": {DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS}
                       | 
                    |
| 878 | 
                        + },  | 
                    |
| 879 | 
                        + )  | 
                    |
| 880 | 
                        + )  | 
                    |
| 881 | 
                        + if auto_prompt:  | 
                    |
| 882 | 
                        + monkeypatch.setattr(  | 
                    |
| 883 | 
                        + cli_helpers,  | 
                    |
| 884 | 
                        + "prompt_for_passphrase",  | 
                    |
| 885 | 
                        + callables.auto_prompt,  | 
                    |
| 886 | 
                        + )  | 
                    |
| 887 | 
                        + result = runner.invoke(  | 
                    |
| 888 | 
                        + cli.derivepassphrase_vault,  | 
                    |
| 889 | 
                        + ["-p", "--", DUMMY_SERVICE],  | 
                    |
| 890 | 
                        + input=None if auto_prompt else DUMMY_PASSPHRASE,  | 
                    |
| 891 | 
                        + catch_exceptions=False,  | 
                    |
| 892 | 
                        + )  | 
                    |
| 893 | 
                        + assert result.clean_exit(), "expected clean exit"  | 
                    |
| 894 | 
                        + assert result.stdout, "expected program output"  | 
                    |
| 895 | 
                        + last_line = result.stdout.splitlines(True)[-1]  | 
                    |
| 896 | 
                        + assert (  | 
                    |
| 897 | 
                        +            last_line.rstrip("\n").encode("UTF-8") == DUMMY_RESULT_PASSPHRASE
                       | 
                    |
| 898 | 
                        + ), "expected known output"  | 
                    |
| 812 | 899 | 
                         | 
                    
| 813 | 900 | 
                         | 
                    
| 814 | 901 | 
                        class TestKeyBasic:  | 
                    
| ... | ... | 
                      @@ -1542,17 +1629,72 @@ class TestExportConfigValid:  | 
                  
| 1542 | 1629 | 
                        ), "unexpected error output"  | 
                    
| 1543 | 1630 | 
                        assert_vault_config_is_indented_and_line_broken(result.stdout)  | 
                    
| 1544 | 1631 | 
                         | 
                    
| 1545 | 
                        - @pytest.mark.xfail(  | 
                    |
| 1546 | 
                        - True,  | 
                    |
| 1547 | 
                        - reason="not implemented yet",  | 
                    |
| 1548 | 
                        - raises=NotImplementedError,  | 
                    |
| 1549 | 
                        - strict=True,  | 
                    |
| 1632 | 
                        + @hypothesis.settings(  | 
                    |
| 1633 | 
                        + suppress_health_check=[  | 
                    |
| 1634 | 
                        + *hypothesis.settings().suppress_health_check,  | 
                    |
| 1635 | 
                        + hypothesis.HealthCheck.function_scoped_fixture,  | 
                    |
| 1636 | 
                        + ],  | 
                    |
| 1550 | 1637 | 
                        )  | 
                    
| 1551 | 
                        - def test_export_smudged_config(  | 
                    |
| 1638 | 
                        + @hypothesis.given(  | 
                    |
| 1639 | 
                        + conf=hypothesis_machinery.smudged_vault_test_config(  | 
                    |
| 1640 | 
                        + strategies.sampled_from([  | 
                    |
| 1641 | 
                        + conf for conf in data.TEST_CONFIGS if conf.is_valid()  | 
                    |
| 1642 | 
                        + ])  | 
                    |
| 1643 | 
                        + )  | 
                    |
| 1644 | 
                        + )  | 
                    |
| 1645 | 
                        + def test_reexport_smudged_config(  | 
                    |
| 1552 | 1646 | 
                        self,  | 
                    
| 1647 | 
                        + caplog: pytest.LogCaptureFixture,  | 
                    |
| 1648 | 
                        + conf: data.VaultTestConfig,  | 
                    |
| 1553 | 1649 | 
                        ) -> None:  | 
                    
| 1554 | 
                        - """Exporting a smudged configuration works."""  | 
                    |
| 1555 | 
                        - raise NotImplementedError  | 
                    |
| 1650 | 
                        + """Re-exporting a smudged configuration works.  | 
                    |
| 1651 | 
                        +  | 
                    |
| 1652 | 
                        + Tested via hypothesis.  | 
                    |
| 1653 | 
                        +  | 
                    |
| 1654 | 
                        + """  | 
                    |
| 1655 | 
                        + config = conf.config  | 
                    |
| 1656 | 
                        + config2 = copy.deepcopy(config)  | 
                    |
| 1657 | 
                        + _types.clean_up_falsy_vault_config_values(config2)  | 
                    |
| 1658 | 
                        + # Reset caplog between hypothesis runs.  | 
                    |
| 1659 | 
                        + caplog.clear()  | 
                    |
| 1660 | 
                        + runner = machinery.CliRunner(mix_stderr=False)  | 
                    |
| 1661 | 
                        + # TODO(the-13th-letter): Rewrite using parenthesized  | 
                    |
| 1662 | 
                        + # with-statements.  | 
                    |
| 1663 | 
                        + # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9  | 
                    |
| 1664 | 
                        + with contextlib.ExitStack() as stack:  | 
                    |
| 1665 | 
                        + monkeypatch = stack.enter_context(pytest.MonkeyPatch.context())  | 
                    |
| 1666 | 
                        + stack.enter_context(  | 
                    |
| 1667 | 
                        + pytest_machinery.isolated_vault_config(  | 
                    |
| 1668 | 
                        + monkeypatch=monkeypatch,  | 
                    |
| 1669 | 
                        + runner=runner,  | 
                    |
| 1670 | 
                        +                    vault_config={"services": {}},
                       | 
                    |
| 1671 | 
                        + )  | 
                    |
| 1672 | 
                        + )  | 
                    |
| 1673 | 
                        + result1 = runner.invoke(  | 
                    |
| 1674 | 
                        + cli.derivepassphrase_vault,  | 
                    |
| 1675 | 
                        + ["--import", "-"],  | 
                    |
| 1676 | 
                        + input=json.dumps(config),  | 
                    |
| 1677 | 
                        + catch_exceptions=False,  | 
                    |
| 1678 | 
                        + )  | 
                    |
| 1679 | 
                        + assert result1.clean_exit(empty_stderr=False), (  | 
                    |
| 1680 | 
                        + "expected clean exit"  | 
                    |
| 1681 | 
                        + )  | 
                    |
| 1682 | 
                        + assert not result1.stderr or all(  | 
                    |
| 1683 | 
                        + map(is_harmless_config_import_warning, caplog.record_tuples)  | 
                    |
| 1684 | 
                        + ), "unexpected error output"  | 
                    |
| 1685 | 
                        + result2 = runner.invoke(  | 
                    |
| 1686 | 
                        + cli.derivepassphrase_vault,  | 
                    |
| 1687 | 
                        + ["--export", "-"],  | 
                    |
| 1688 | 
                        + catch_exceptions=False,  | 
                    |
| 1689 | 
                        + )  | 
                    |
| 1690 | 
                        + assert result2.clean_exit(empty_stderr=False), (  | 
                    |
| 1691 | 
                        + "expected clean exit"  | 
                    |
| 1692 | 
                        + )  | 
                    |
| 1693 | 
                        + assert not result2.stderr or all(  | 
                    |
| 1694 | 
                        + map(is_harmless_config_import_warning, caplog.record_tuples)  | 
                    |
| 1695 | 
                        + ), "unexpected error output"  | 
                    |
| 1696 | 
                        + config3 = json.loads(result2.stdout)  | 
                    |
| 1697 | 
                        + assert config3 == config2, "config not exported correctly"  | 
                    |
| 1556 | 1698 | 
                         | 
                    
| 1557 | 1699 | 
                        @Parametrize.EXPORT_FORMAT_OPTIONS  | 
                    
| 1558 | 1700 | 
                        def test_export_config_no_stored_settings(  | 
                    
| 1559 | 1701 |