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: |