Coverage for tests / test_derivepassphrase_cli_export_vault.py: 100.000%
194 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-07-06 21:34 +0200
« prev ^ index » next coverage.py v7.13.1, created at 2026-07-06 21:34 +0200
1# SPDX-FileCopyrightText: 2026 Marco Ricci <software@the13thletter.info>
2#
3# SPDX-License-Identifier: Zlib
5"""Test the `derivepassphrase export vault` CLI."""
7from __future__ import annotations
9import base64
10import contextlib
11import json
12import pathlib
13import types
14from typing import TYPE_CHECKING
16import hypothesis
17import pytest
18from hypothesis import strategies
20from derivepassphrase import _types, cli, exporter
21from derivepassphrase.exporter import storeroom, vault_native
22from tests import data, machinery
23from tests.machinery import pytest as pytest_machinery
25cryptography = pytest.importorskip("cryptography", minversion="38.0")
27from cryptography.hazmat.primitives import ( # noqa: E402
28 ciphers,
29 hashes,
30 hmac,
31 padding,
32)
33from cryptography.hazmat.primitives.ciphers import ( # noqa: E402
34 algorithms,
35 modes,
36)
38if TYPE_CHECKING:
39 from collections.abc import Callable, Generator
40 from typing import Any
42 from typing_extensions import Buffer, Literal
45class Parametrize(types.SimpleNamespace):
46 BAD_CONFIG = pytest.mark.parametrize(
47 "config", ["xxx", "null", '{"version": 255}']
48 )
49 VAULT_NATIVE_CONFIG_DATA = pytest.mark.parametrize(
50 ["config", "format", "config_data"],
51 [
52 pytest.param(
53 data.VAULT_V02_CONFIG,
54 "v0.2",
55 data.VAULT_V02_CONFIG_DATA,
56 id="V02_CONFIG-v0.2",
57 ),
58 pytest.param(
59 data.VAULT_V02_CONFIG,
60 "v0.3",
61 exporter.NotAVaultConfigError,
62 id="V02_CONFIG-v0.3",
63 ),
64 pytest.param(
65 data.VAULT_V03_CONFIG,
66 "v0.2",
67 exporter.NotAVaultConfigError,
68 id="V03_CONFIG-v0.2",
69 ),
70 pytest.param(
71 data.VAULT_V03_CONFIG,
72 "v0.3",
73 data.VAULT_V03_CONFIG_DATA,
74 id="V03_CONFIG-v0.3",
75 ),
76 ],
77 )
78 VAULT_NATIVE_PARSER_CLASS_DATA = pytest.mark.parametrize(
79 ["config", "parser_class", "config_data"],
80 [
81 pytest.param(
82 data.VAULT_V02_CONFIG,
83 vault_native.VaultNativeV02ConfigParser,
84 data.VAULT_V02_CONFIG_DATA,
85 id="0.2",
86 ),
87 pytest.param(
88 data.VAULT_V03_CONFIG,
89 vault_native.VaultNativeV03ConfigParser,
90 data.VAULT_V03_CONFIG_DATA,
91 id="0.3",
92 ),
93 ],
94 )
95 STOREROOM_HANDLER = pytest.mark.parametrize(
96 "handler",
97 [
98 pytest.param(storeroom.export_storeroom_data, id="handler"),
99 pytest.param(exporter.export_vault_config_data, id="dispatcher"),
100 ],
101 )
102 VAULT_NATIVE_HANDLER = pytest.mark.parametrize(
103 "handler",
104 [
105 pytest.param(vault_native.export_vault_native_data, id="handler"),
106 pytest.param(exporter.export_vault_config_data, id="dispatcher"),
107 ],
108 )
109 VAULT_NATIVE_PBKDF2_RESULT = pytest.mark.parametrize(
110 ["iterations", "result"],
111 [
112 pytest.param(100, b"6ede361e81e9c061efcdd68aeb768b80", id="100"),
113 pytest.param(200, b"bcc7d01e075b9ffb69e702bf701187c1", id="200"),
114 ],
115 )
116 KEY_FORMATS = pytest.mark.parametrize(
117 "key",
118 [
119 None,
120 pytest.param(data.VAULT_MASTER_KEY, id="str"),
121 pytest.param(data.VAULT_MASTER_KEY.encode("ascii"), id="bytes"),
122 pytest.param(
123 bytearray(data.VAULT_MASTER_KEY.encode("ascii")),
124 id="bytearray",
125 ),
126 pytest.param(
127 memoryview(data.VAULT_MASTER_KEY.encode("ascii")),
128 id="memoryview",
129 ),
130 ],
131 )
132 BAD_MASTER_KEYS_DATA = pytest.mark.parametrize(
133 ["master_keys_data", "err_msg"],
134 [
135 pytest.param(
136 '{"version": 255}',
137 "bad or unsupported keys version header",
138 id="v255",
139 ),
140 pytest.param(
141 '{"version": 1}\nAAAA\nAAAA',
142 "trailing data; cannot make sense",
143 id="trailing-data",
144 ),
145 pytest.param(
146 '{"version": 1}\nAAAA',
147 "cannot handle version 0 encrypted keys",
148 id="v0-keys",
149 ),
150 ],
151 )
152 PATH = pytest.mark.parametrize("path", [".vault", None])
153 BAD_STOREROOM_CONFIG_DATA = pytest.mark.parametrize(
154 ["zipped_config", "error_text"],
155 [
156 pytest.param(
157 data.VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED,
158 "Object key mismatch",
159 id="VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED",
160 ),
161 pytest.param(
162 data.VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED2,
163 "Directory index is not actually an index",
164 id="VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED2",
165 ),
166 pytest.param(
167 data.VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED3,
168 "Directory index is not actually an index",
169 id="VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED3",
170 ),
171 pytest.param(
172 data.VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED4,
173 "Object key mismatch",
174 id="VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED4",
175 ),
176 ],
177 )
180class TestCLIParameters:
181 """Test the command-line interface for `derivepassphrase export vault`."""
183 def _test(
184 self,
185 command_line: list[str],
186 *,
187 vault_config: str | bytes = data.VAULT_V03_CONFIG,
188 config_data: dict[str, Any] = data.VAULT_V03_CONFIG_DATA,
189 ) -> None:
190 runner = machinery.CliRunner(mix_stderr=False) 1lmn
191 # TODO(the-13th-letter): Rewrite using parenthesized
192 # with-statements.
193 # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9
194 with contextlib.ExitStack() as stack: 1lmn
195 monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) 1lmn
196 stack.enter_context( 1lmn
197 pytest_machinery.isolated_vault_exporter_config(
198 monkeypatch=monkeypatch,
199 runner=runner,
200 vault_config=vault_config,
201 vault_key=data.VAULT_MASTER_KEY,
202 )
203 )
204 result = runner.invoke( 1lmn
205 cli.derivepassphrase_export_vault,
206 command_line,
207 )
208 assert result.clean_exit(empty_stderr=True), "expected clean exit" 1lmn
209 assert json.loads(result.stdout) == config_data 1lmn
211 def test_path(self) -> None:
212 """The path `VAULT_PATH` is supported.
214 Using `VAULT_PATH` as the path looks up the actual path in the
215 `VAULT_PATH` environment variable. See
216 [`exporter.get_vault_path`][] for details.
218 """
219 self._test(["VAULT_PATH"]) 1n
221 def test_key(self) -> None:
222 """The `--key` option is supported."""
223 self._test(["-k", data.VAULT_MASTER_KEY, ".vault"]) 1l
225 @pytest_machinery.Parametrize.VAULT_CONFIG_FORMATS_DATA
226 def test_load_vault_specific_format(
227 self,
228 config: str | bytes,
229 format: str,
230 config_data: dict[str, Any],
231 ) -> None:
232 """Passing a specific format works.
234 Passing a specific format name causes `derivepassphrase export
235 vault` to only attempt decoding in that named format.
237 """
238 self._test( 1m
239 ["-f", format, "-k", data.VAULT_MASTER_KEY, "VAULT_PATH"],
240 vault_config=config,
241 config_data=config_data,
242 )
245class TestCLIFailures:
246 """Test the command-line interface for `derivepassphrase export vault`."""
248 @contextlib.contextmanager
249 def _test(
250 self,
251 caplog: pytest.LogCaptureFixture,
252 command_line: list[str],
253 /,
254 error_message: str = "Cannot parse '.vault' "
255 "as a valid vault-native config",
256 *,
257 vault_config: str | bytes = data.VAULT_V03_CONFIG,
258 vault_key: str = data.VAULT_MASTER_KEY,
259 ) -> Generator[pytest.MonkeyPatch, None, None]:
260 runner = machinery.CliRunner(mix_stderr=False) 1ijdke
261 # TODO(the-13th-letter): Rewrite using parenthesized
262 # with-statements.
263 # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9
264 with contextlib.ExitStack() as stack: 1ijdke
265 monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) 1ijdke
266 stack.enter_context( 1ijdke
267 pytest_machinery.isolated_vault_exporter_config(
268 monkeypatch=monkeypatch,
269 runner=runner,
270 vault_config=vault_config,
271 vault_key=vault_key,
272 )
273 )
274 # If the setup blows up, bubble the exception.
275 yield monkeypatch # noqa: RUF075 1ijdke
276 result = runner.invoke( 1ijdke
277 cli.derivepassphrase_export_vault,
278 command_line,
279 )
280 assert result.error_exit( 1ijdke
281 error=error_message,
282 record_tuples=caplog.record_tuples,
283 ), "expected error exit and known error message"
284 assert data.CANNOT_LOAD_CRYPTOGRAPHY not in result.stderr 1ijdke
286 def test_file_not_found(
287 self,
288 caplog: pytest.LogCaptureFixture,
289 ) -> None:
290 """Fail when trying to decode non-existant files/directories."""
291 with self._test( 1j
292 caplog,
293 ["does-not-exist.txt"],
294 error_message="Cannot parse 'does-not-exist.txt' "
295 "as a valid vault-native config",
296 ):
297 pass 1j
299 def test_invalid_encrypted_contents(
300 self,
301 caplog: pytest.LogCaptureFixture,
302 ) -> None:
303 """Fail to parse invalid vault configurations (files)."""
304 with self._test(caplog, [".vault"], vault_config=""): 1k
305 pass 1k
307 def test_just_a_directory(
308 self,
309 caplog: pytest.LogCaptureFixture,
310 ) -> None:
311 """Fail to parse invalid vault configurations (directories)."""
312 with self._test(caplog, [".vault"], vault_config=""): 1e
313 p = pathlib.Path(".vault") 1e
314 p.unlink() 1e
315 p.mkdir() 1e
317 def test_bad_signature(
318 self,
319 caplog: pytest.LogCaptureFixture,
320 ) -> None:
321 """Fail to parse vault configurations with invalid integrity checks."""
322 with self._test( 1i
323 caplog,
324 ["-f", "v0.3", ".vault"],
325 vault_config=data.VAULT_V02_CONFIG,
326 ):
327 pass 1i
329 def test_invalid_decrypted_contents(
330 self,
331 caplog: pytest.LogCaptureFixture,
332 ) -> None:
333 """Fail to parse encrypted vault configurations with invalid plaintext."""
334 with self._test( 1d
335 caplog, [".vault"], error_message="Invalid vault config: "
336 ) as monkeypatch:
338 def export_vault_config_data(*_args: Any, **_kwargs: Any) -> None: 1d
339 return None 1d
341 monkeypatch.setattr( 1d
342 exporter,
343 "export_vault_config_data",
344 export_vault_config_data,
345 )
348class TestStoreroom:
349 """Test the "storeroom" handler and handler machinery."""
351 @contextlib.contextmanager
352 def _setup_environment(
353 self,
354 *,
355 vault_config: str | bytes = data.VAULT_STOREROOM_CONFIG_ZIPPED,
356 vault_key: str = data.VAULT_MASTER_KEY,
357 ) -> Generator[tuple[pytest.MonkeyPatch, machinery.CliRunner], None, None]:
358 runner = machinery.CliRunner(mix_stderr=False) 1fpog
359 # TODO(the-13th-letter): Rewrite using parenthesized
360 # with-statements.
361 # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9
362 with contextlib.ExitStack() as stack: 1fpog
363 monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) 1fpog
364 stack.enter_context( 1fpog
365 pytest_machinery.isolated_vault_exporter_config(
366 monkeypatch=monkeypatch,
367 runner=runner,
368 vault_config=vault_config,
369 vault_key=vault_key,
370 )
371 )
372 yield monkeypatch, runner 1fpog
374 @Parametrize.PATH
375 @Parametrize.KEY_FORMATS
376 @Parametrize.STOREROOM_HANDLER
377 def test_export_data_path_and_keys_type(
378 self,
379 path: str | None,
380 key: str | Buffer | None,
381 handler: exporter.ExportVaultConfigDataFunction,
382 ) -> None:
383 """Support different argument types.
385 The [`exporter.export_vault_config_data`][] dispatcher supports
386 them as well.
388 """
389 with self._setup_environment(): 1p
390 assert ( 1p
391 handler(path, key, format="storeroom")
392 == data.VAULT_STOREROOM_CONFIG_DATA
393 )
395 # TODO(the-13th-letter): Rewrite as property-based test.
396 @pytest_machinery.correctness_focused
397 def test_decrypt_bucket_item_unknown_version(self) -> None:
398 """Fail on unknown versions of the master keys file."""
399 bucket_item = ( 1s
400 b"\xff" + bytes(storeroom.ENCRYPTED_KEYPAIR_SIZE) + bytes(3)
401 )
402 master_keys = _types.StoreroomMasterKeys( 1s
403 encryption_key=bytes(storeroom.KEY_SIZE),
404 signing_key=bytes(storeroom.KEY_SIZE),
405 hashing_key=bytes(storeroom.KEY_SIZE),
406 )
407 with pytest.raises(ValueError, match="Cannot handle version 255"): 1s
408 storeroom._decrypt_bucket_item(bucket_item, master_keys) 1s
410 # TODO(the-13th-letter): Rewrite as property-based test.
411 @pytest_machinery.correctness_focused
412 @Parametrize.BAD_CONFIG
413 def test_decrypt_bucket_file_bad_json_or_version(
414 self,
415 config: str,
416 ) -> None:
417 """Fail on bad or unsupported bucket file contents.
419 These include unknown versions, invalid JSON, or JSON of the
420 wrong shape.
422 """
423 master_keys = _types.StoreroomMasterKeys( 1f
424 encryption_key=bytes(storeroom.KEY_SIZE),
425 signing_key=bytes(storeroom.KEY_SIZE),
426 hashing_key=bytes(storeroom.KEY_SIZE),
427 )
428 with self._setup_environment(): 1f
429 p = pathlib.Path(".vault", "20") 1f
430 with p.open("w", encoding="UTF-8") as outfile: 1f
431 print(config, file=outfile) 1f
432 with pytest.raises(ValueError, match="Invalid bucket file: "): 1f
433 list(storeroom._decrypt_bucket_file(p, master_keys)) 1f
435 # TODO(the-13th-letter): Rewrite as property-based test.
436 @pytest_machinery.correctness_focused
437 @Parametrize.BAD_MASTER_KEYS_DATA
438 @Parametrize.STOREROOM_HANDLER
439 def test_export_storeroom_data_bad_master_keys_file(
440 self,
441 master_keys_data: str,
442 err_msg: str,
443 handler: exporter.ExportVaultConfigDataFunction,
444 ) -> None:
445 """Fail on bad or unsupported master keys file contents.
447 These include unknown versions, and data of the wrong shape.
449 """
450 with self._setup_environment(): 1g
451 p = pathlib.Path(".vault", ".keys") 1g
452 with p.open("w", encoding="UTF-8") as outfile: 1g
453 print(master_keys_data, file=outfile) 1g
454 with pytest.raises(RuntimeError, match=err_msg): 1g
455 handler(format="storeroom") 1g
457 # TODO(the-13th-letter): Rewrite as property-based test.
458 @pytest_machinery.correctness_focused
459 @Parametrize.BAD_STOREROOM_CONFIG_DATA
460 @Parametrize.STOREROOM_HANDLER
461 def test_export_storeroom_data_bad_directory_listing(
462 self,
463 zipped_config: bytes,
464 error_text: str,
465 handler: exporter.ExportVaultConfigDataFunction,
466 ) -> None:
467 """Fail on bad decoded directory structures.
469 If the decoded configuration contains directories whose
470 structures are inconsistent, it detects this and fails:
472 - The key indicates a directory, but the contents don't.
473 - The directory indicates children with invalid path names.
474 - The directory indicates children that are missing from the
475 configuration entirely.
476 - The configuration contains nested subdirectories, but the
477 higher-level directories don't indicate their
478 subdirectories.
480 """
481 with self._setup_environment(vault_config=zipped_config): # noqa: SIM117 1o
482 # TODO(the-13th-letter): Rewrite using parenthesized
483 # with-statements.
484 # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9
485 with pytest.raises(RuntimeError, match=error_text): 1o
486 handler(format="storeroom") 1o
488 # TODO(the-13th-letter): Rewrite as property-based test.
489 @pytest_machinery.correctness_focused
490 def test_decrypt_keys_wrong_data_length(self) -> None:
491 """Fail on internal structural data of the wrong size.
493 Specifically, fail on internal structural data such as master
494 keys or session keys that is correctly encrypted according to
495 its MAC, but is of the wrong shape. (Since the data usually are
496 keys and thus are opaque, the only detectable shape violation is
497 the wrong size of the data.)
499 """
500 payload = ( 1b
501 b"Any text here, as long as it isn't exactly 64 or 96 bytes long."
502 )
503 assert len(payload) not in frozenset({ 1b
504 2 * storeroom.KEY_SIZE,
505 3 * storeroom.KEY_SIZE,
506 })
507 key = b"DEADBEEFdeadbeefDeAdBeEfdEaDbEeF" 1b
508 padder = padding.PKCS7(storeroom.IV_SIZE * 8).padder() 1b
509 plaintext = bytearray(padder.update(payload)) 1b
510 plaintext.extend(padder.finalize()) 1b
511 iv = b"deadbeefDEADBEEF" 1b
512 assert len(iv) == storeroom.IV_SIZE 1b
513 encryptor = ciphers.Cipher( 1b
514 algorithms.AES256(key), modes.CBC(iv)
515 ).encryptor()
516 ciphertext = bytearray(encryptor.update(bytes(plaintext))) 1b
517 ciphertext.extend(encryptor.finalize()) 1b
518 mac_obj = hmac.HMAC(key, hashes.SHA256()) 1b
519 mac_obj.update(iv) 1b
520 mac_obj.update(bytes(ciphertext)) 1b
521 data = iv + bytes(ciphertext) + mac_obj.finalize() 1b
522 with pytest.raises( 1b
523 ValueError,
524 match=r"Invalid encrypted master keys payload",
525 ):
526 storeroom._decrypt_master_keys_data( 1b
527 data,
528 _types.StoreroomKeyPair(encryption_key=key, signing_key=key),
529 )
530 with pytest.raises( 1b
531 ValueError,
532 match=r"Invalid encrypted session keys payload",
533 ):
534 storeroom._decrypt_session_keys( 1b
535 data,
536 _types.StoreroomMasterKeys(
537 hashing_key=key, encryption_key=key, signing_key=key
538 ),
539 )
541 # TODO(the-13th-letter): Generate key as well.
542 @pytest_machinery.correctness_focused
543 @hypothesis.given( 1ar
544 data=strategies.binary(
545 min_size=storeroom.MAC_SIZE, max_size=storeroom.MAC_SIZE
546 ),
547 )
548 def test_decrypt_keys_invalid_signature(self, data: bytes) -> None:
549 """Fail on bad MAC values."""
550 key = b"DEADBEEFdeadbeefDeAdBeEfdEaDbEeF" 1r
551 # Guessing a correct payload plus MAC would be a pre-image
552 # attack on the underlying hash function (SHA-256), i.e. is
553 # computationally infeasible, and the chance of finding one by
554 # such random sampling is astronomically tiny.
555 with pytest.raises(cryptography.exceptions.InvalidSignature): 1r
556 storeroom._decrypt_master_keys_data( 1r
557 data,
558 _types.StoreroomKeyPair(encryption_key=key, signing_key=key),
559 )
560 with pytest.raises(cryptography.exceptions.InvalidSignature): 1r
561 storeroom._decrypt_session_keys( 1r
562 data,
563 _types.StoreroomMasterKeys(
564 hashing_key=key, encryption_key=key, signing_key=key
565 ),
566 )
569class TestVaultNativeConfig:
570 """Test the vault-native handler and handler machinery."""
572 @contextlib.contextmanager
573 def _setup_environment(
574 self,
575 *,
576 vault_config: str | bytes = data.VAULT_V03_CONFIG,
577 vault_key: str = data.VAULT_MASTER_KEY,
578 ) -> Generator[tuple[pytest.MonkeyPatch, machinery.CliRunner], None, None]:
579 runner = machinery.CliRunner(mix_stderr=False) 1qhc
580 # TODO(the-13th-letter): Rewrite using parenthesized
581 # with-statements.
582 # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9
583 with contextlib.ExitStack() as stack: 1qhc
584 monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) 1qhc
585 stack.enter_context( 1qhc
586 pytest_machinery.isolated_vault_exporter_config(
587 monkeypatch=monkeypatch,
588 runner=runner,
589 vault_config=vault_config,
590 vault_key=vault_key,
591 )
592 )
593 yield monkeypatch, runner 1qhc
595 @Parametrize.VAULT_NATIVE_PBKDF2_RESULT
596 def test_pbkdf2_manually(self, iterations: int, result: bytes) -> None:
597 """The PBKDF2 helper function works."""
598 assert ( 1u
599 vault_native.VaultNativeConfigParser._pbkdf2(
600 data.VAULT_MASTER_KEY.encode("utf-8"), 32, iterations
601 )
602 == result
603 )
605 @Parametrize.VAULT_NATIVE_CONFIG_DATA
606 @Parametrize.VAULT_NATIVE_HANDLER
607 def test_export_vault_native_data_explicit_version(
608 self,
609 config: str,
610 format: Literal["v0.2", "v0.3"],
611 config_data: _types.VaultConfig | type[Exception],
612 handler: exporter.ExportVaultConfigDataFunction,
613 ) -> None:
614 """Accept data only of the correct version.
616 Note: Historic behavior
617 `derivepassphrase` versions prior to 0.5 automatically tried
618 to parse vault-native configurations as v0.3-type, then
619 v0.2-type. Since `derivepassphrase` 0.5, the command-line
620 interface still tries multi-version parsing, but the API
621 no longer does.
623 """
624 with self._setup_environment(vault_config=config): 1h
625 if isinstance(config_data, type): 1h
626 with pytest.raises(config_data): 1h
627 handler(None, format=format) 1h
628 else:
629 parsed_config = handler(None, format=format) 1h
630 assert parsed_config == config_data 1h
632 @Parametrize.PATH
633 @Parametrize.KEY_FORMATS
634 @Parametrize.VAULT_NATIVE_HANDLER
635 def test_export_data_path_and_keys_type(
636 self,
637 path: str | None,
638 key: str | Buffer | None,
639 handler: exporter.ExportVaultConfigDataFunction,
640 ) -> None:
641 """The handler supports different argument types.
643 The [`exporter.export_vault_config_data`][] dispatcher supports
644 them as well.
646 """
647 with self._setup_environment(): 1q
648 assert ( 1q
649 handler(path, key, format="v0.3") == data.VAULT_V03_CONFIG_DATA
650 )
652 @Parametrize.VAULT_NATIVE_PARSER_CLASS_DATA
653 def test_result_caching(
654 self,
655 config: str,
656 parser_class: type[vault_native.VaultNativeConfigParser],
657 config_data: dict[str, Any],
658 ) -> None:
659 """Cache the results of decrypting/decoding a configuration."""
661 def null_func(name: str) -> Callable[..., None]: 1c
662 def func(*_args: Any, **_kwargs: Any) -> None: # pragma: no cover 1c
663 msg = f"disallowed and stubbed out function {name} called"
664 raise AssertionError(msg)
666 return func 1c
668 with self._setup_environment(vault_config=config) as values: 1c
669 monkeypatch, _ = values 1c
670 parser = parser_class( 1c
671 base64.b64decode(config), data.VAULT_MASTER_KEY
672 )
673 assert parser() == config_data 1c
674 # Now stub out all functions used to calculate the above result.
675 monkeypatch.setattr( 1c
676 parser, "_parse_contents", null_func("_parse_contents")
677 )
678 monkeypatch.setattr( 1c
679 parser, "_derive_keys", null_func("_derive_keys")
680 )
681 monkeypatch.setattr( 1c
682 parser, "_check_signature", null_func("_check_signature")
683 )
684 monkeypatch.setattr( 1c
685 parser, "_decrypt_payload", null_func("_decrypt_payload")
686 )
687 assert parser() == config_data 1c
688 super_call = vault_native.VaultNativeConfigParser.__call__ 1c
689 assert super_call(parser) == config_data 1c
691 def test_no_password(self) -> None:
692 """Fail on empty master keys/master passphrases."""
693 with pytest.raises(ValueError, match="Password must not be empty"): 1t
694 vault_native.VaultNativeV03ConfigParser(b"", b"") 1t