Marco Ricci commited on 2024-07-28 15:02:34
              Zeige 4 geänderte Dateien mit 102 Einfügungen und 1 Löschungen.
            
Do not error out during the first attempt at saving the configuration, where the configuration directory probably does not yet exist. On an unrelated note, fix one typo in `derivepassphrase.cli` and a missing dependency in the hatch environment for type checking.
| ... | ... | 
                      @@ -97,7 +97,7 @@ def _load_config() -> dpp_types.VaultConfig:  | 
                  
| 97 | 97 | 
                         | 
                    
| 98 | 98 | 
                         | 
                    
| 99 | 99 | 
                        def _save_config(config: dpp_types.VaultConfig, /) -> None:  | 
                    
| 100 | 
                        - """Save a vault(1)-compatbile config to the application directory.  | 
                    |
| 100 | 
                        + """Save a vault(1)-compatible config to the application directory.  | 
                    |
| 101 | 101 | 
                         | 
                    
| 102 | 102 | 
                        The filename is obtained via  | 
                    
| 103 | 103 | 
                        [`derivepassphrase.cli._config_filename`][]. The config will be  | 
                    
| ... | ... | 
                      @@ -117,6 +117,12 @@ def _save_config(config: dpp_types.VaultConfig, /) -> None:  | 
                  
| 117 | 117 | 
                        if not dpp_types.is_vault_config(config):  | 
                    
| 118 | 118 | 
                        raise ValueError(_INVALID_VAULT_CONFIG)  | 
                    
| 119 | 119 | 
                        filename = _config_filename()  | 
                    
| 120 | 
                        + filedir = os.path.dirname(os.path.abspath(filename))  | 
                    |
| 121 | 
                        + try:  | 
                    |
| 122 | 
                        + os.makedirs(filedir, exist_ok=False)  | 
                    |
| 123 | 
                        + except FileExistsError:  | 
                    |
| 124 | 
                        + if not os.path.isdir(filedir):  | 
                    |
| 125 | 
                        + raise  | 
                    |
| 120 | 126 | 
                        with open(filename, 'w', encoding='UTF-8') as fileobj:  | 
                    
| 121 | 127 | 
                        json.dump(config, fileobj)  | 
                    
| 122 | 128 | 
                         | 
                    
| ... | ... | 
                      @@ -7,6 +7,7 @@ from __future__ import annotations  | 
                  
| 7 | 7 | 
                        import contextlib  | 
                    
| 8 | 8 | 
                        import json  | 
                    
| 9 | 9 | 
                        import os  | 
                    
| 10 | 
                        +import shutil  | 
                    |
| 10 | 11 | 
                        import socket  | 
                    
| 11 | 12 | 
                        from typing import TYPE_CHECKING  | 
                    
| 12 | 13 | 
                         | 
                    
| ... | ... | 
                      @@ -686,6 +687,32 @@ class TestCLI:  | 
                  
| 686 | 687 | 
                        b'cannot write config' in result.stderr_bytes  | 
                    
| 687 | 688 | 
                        ), 'program did not print the expected error message'  | 
                    
| 688 | 689 | 
                         | 
                    
| 690 | 
                        + def test_214d_export_settings_settings_directory_not_a_directory(  | 
                    |
| 691 | 
                        + self,  | 
                    |
| 692 | 
                        + monkeypatch: Any,  | 
                    |
| 693 | 
                        + ) -> None:  | 
                    |
| 694 | 
                        + runner = click.testing.CliRunner(mix_stderr=False)  | 
                    |
| 695 | 
                        + with tests.isolated_config(  | 
                    |
| 696 | 
                        +            monkeypatch=monkeypatch, runner=runner, config={'services': {}}
                       | 
                    |
| 697 | 
                        + ):  | 
                    |
| 698 | 
                        + with contextlib.suppress(FileNotFoundError):  | 
                    |
| 699 | 
                        +                shutil.rmtree('.derivepassphrase')
                       | 
                    |
| 700 | 
                        +            with open('.derivepassphrase', 'w', encoding='UTF-8') as outfile:
                       | 
                    |
| 701 | 
                        +                print('Obstruction!!', file=outfile)
                       | 
                    |
| 702 | 
                        + result = runner.invoke(  | 
                    |
| 703 | 
                        + cli.derivepassphrase,  | 
                    |
| 704 | 
                        + ['--export', '-'],  | 
                    |
| 705 | 
                        + input=b'null',  | 
                    |
| 706 | 
                        + catch_exceptions=False,  | 
                    |
| 707 | 
                        + )  | 
                    |
| 708 | 
                        + assert result.exit_code > 0, 'program unexpectedly succeeded'  | 
                    |
| 709 | 
                        + assert (  | 
                    |
| 710 | 
                        + result.stderr_bytes  | 
                    |
| 711 | 
                        + ), 'program did not print any error message'  | 
                    |
| 712 | 
                        + assert (  | 
                    |
| 713 | 
                        + b'cannot load config' in result.stderr_bytes  | 
                    |
| 714 | 
                        + ), 'program did not print the expected error message'  | 
                    |
| 715 | 
                        +  | 
                    |
| 689 | 716 | 
                        def test_220_edit_notes_successfully(self, monkeypatch: Any) -> None:  | 
                    
| 690 | 717 | 
                        edit_result = """  | 
                    
| 691 | 718 | 
                         | 
                    
| ... | ... | 
                      @@ -946,6 +973,72 @@ contents go here  | 
                  
| 946 | 973 | 
                        b'no passphrase or key given' in result.stderr_bytes  | 
                    
| 947 | 974 | 
                        ), 'expected error message missing'  | 
                    
| 948 | 975 | 
                         | 
                    
| 976 | 
                        + def test_230_config_directory_nonexistant(self, monkeypatch: Any) -> None:  | 
                    |
| 977 | 
                        + """the-13th-letter/derivepassphrase#6"""  | 
                    |
| 978 | 
                        + runner = click.testing.CliRunner(mix_stderr=False)  | 
                    |
| 979 | 
                        + with tests.isolated_config(  | 
                    |
| 980 | 
                        + monkeypatch=monkeypatch,  | 
                    |
| 981 | 
                        + runner=runner,  | 
                    |
| 982 | 
                        +            config={'services': {}},
                       | 
                    |
| 983 | 
                        + ):  | 
                    |
| 984 | 
                        +            os.remove('.derivepassphrase/settings.json')
                       | 
                    |
| 985 | 
                        +            os.rmdir('.derivepassphrase')
                       | 
                    |
| 986 | 
                        + os_makedirs_called = False  | 
                    |
| 987 | 
                        + real_os_makedirs = os.makedirs  | 
                    |
| 988 | 
                        +  | 
                    |
| 989 | 
                        + def makedirs(*args: Any, **kwargs: Any) -> Any:  | 
                    |
| 990 | 
                        + nonlocal os_makedirs_called  | 
                    |
| 991 | 
                        + os_makedirs_called = True  | 
                    |
| 992 | 
                        + return real_os_makedirs(*args, **kwargs)  | 
                    |
| 993 | 
                        +  | 
                    |
| 994 | 
                        + monkeypatch.setattr(os, 'makedirs', makedirs)  | 
                    |
| 995 | 
                        + result = runner.invoke(  | 
                    |
| 996 | 
                        + cli.derivepassphrase,  | 
                    |
| 997 | 
                        + ['--config', '-p'],  | 
                    |
| 998 | 
                        + catch_exceptions=False,  | 
                    |
| 999 | 
                        + input='abc\n',  | 
                    |
| 1000 | 
                        + )  | 
                    |
| 1001 | 
                        + assert (  | 
                    |
| 1002 | 
                        + result.stderr_bytes == b'Passphrase:'  | 
                    |
| 1003 | 
                        + ), 'program unexpectedly failed?!'  | 
                    |
| 1004 | 
                        + assert result.exit_code == 0, 'program unexpectedly failed?!'  | 
                    |
| 1005 | 
                        + assert os_makedirs_called, 'os.makedirs has not been called?!'  | 
                    |
| 1006 | 
                        + with open(cli._config_filename(), encoding='UTF-8') as infile:  | 
                    |
| 1007 | 
                        + config_readback = json.load(infile)  | 
                    |
| 1008 | 
                        +            assert config_readback == {
                       | 
                    |
| 1009 | 
                        +                'global': {'phrase': 'abc'},
                       | 
                    |
| 1010 | 
                        +                'services': {},
                       | 
                    |
| 1011 | 
                        + }, 'config mismatch'  | 
                    |
| 1012 | 
                        +  | 
                    |
| 1013 | 
                        + def test_230a_config_directory_not_a_file(self, monkeypatch: Any) -> None:  | 
                    |
| 1014 | 
                        + """the-13th-letter/derivepassphrase#6"""  | 
                    |
| 1015 | 
                        + runner = click.testing.CliRunner(mix_stderr=False)  | 
                    |
| 1016 | 
                        + with tests.isolated_config(  | 
                    |
| 1017 | 
                        + monkeypatch=monkeypatch,  | 
                    |
| 1018 | 
                        + runner=runner,  | 
                    |
| 1019 | 
                        +            config={'services': {}},
                       | 
                    |
| 1020 | 
                        + ):  | 
                    |
| 1021 | 
                        + _save_config = cli._save_config  | 
                    |
| 1022 | 
                        +  | 
                    |
| 1023 | 
                        + def obstruct_config_saving(*args: Any, **kwargs: Any) -> Any:  | 
                    |
| 1024 | 
                        + with contextlib.suppress(FileNotFoundError):  | 
                    |
| 1025 | 
                        +                    shutil.rmtree('.derivepassphrase')
                       | 
                    |
| 1026 | 
                        + with open(  | 
                    |
| 1027 | 
                        + '.derivepassphrase', 'w', encoding='UTF-8'  | 
                    |
| 1028 | 
                        + ) as outfile:  | 
                    |
| 1029 | 
                        +                    print('Obstruction!!', file=outfile)
                       | 
                    |
| 1030 | 
                        + monkeypatch.setattr(cli, '_save_config', _save_config)  | 
                    |
| 1031 | 
                        + return _save_config(*args, **kwargs)  | 
                    |
| 1032 | 
                        +  | 
                    |
| 1033 | 
                        + monkeypatch.setattr(cli, '_save_config', obstruct_config_saving)  | 
                    |
| 1034 | 
                        + with pytest.raises(FileExistsError):  | 
                    |
| 1035 | 
                        + runner.invoke(  | 
                    |
| 1036 | 
                        + cli.derivepassphrase,  | 
                    |
| 1037 | 
                        + ['--config', '-p'],  | 
                    |
| 1038 | 
                        + catch_exceptions=False,  | 
                    |
| 1039 | 
                        + input='abc\n',  | 
                    |
| 1040 | 
                        + )  | 
                    |
| 1041 | 
                        +  | 
                    |
| 949 | 1042 | 
                         | 
                    
| 950 | 1043 | 
                        class TestCLIUtils:  | 
                    
| 951 | 1044 | 
                        def test_100_save_bad_config(self, monkeypatch: Any) -> None:  |