Marco Ricci commited on 2025-02-05 11:44:22
Zeige 1 geänderte Dateien mit 124 Einfügungen und 18 Löschungen.
Though the on-disk vault configurations are not intended to be managed by the user, it is valuable for debugging and lay examination for them to be readable and editable anyway; doubly so if the configuration is being exported. So ensure that when writing out the configuration, we add indents and linebreaks. This commit only adds the xfailing tests for this functionality, not the implementation itself. Additionally, since there was no simple test akin to config importing but for config exporting, we added the latter on the basis of the former, and renumbered the other export (failure) tests accordingly.
... | ... |
@@ -236,6 +236,46 @@ def is_harmless_config_import_warning(record: tuple[str, int, str]) -> bool: |
236 | 236 |
return any(tests.warning_emitted(w, [record]) for w in possible_warnings) |
237 | 237 |
|
238 | 238 |
|
239 |
+def assert_vault_config_is_indented_and_line_broken( |
|
240 |
+ config_txt: str, |
|
241 |
+ /, |
|
242 |
+) -> None: |
|
243 |
+ """Return true if the vault configuration is indented and line broken. |
|
244 |
+ |
|
245 |
+ Indented and rewrapped vault configurations as produced by |
|
246 |
+ `json.dump` contain the closing '}' of the '$.services' object |
|
247 |
+ on a separate, indented line: |
|
248 |
+ |
|
249 |
+ ~~~~ |
|
250 |
+ { |
|
251 |
+ "services": { |
|
252 |
+ ... |
|
253 |
+ } <-- this brace here |
|
254 |
+ } |
|
255 |
+ ~~~~ |
|
256 |
+ |
|
257 |
+ or, if there are no services, then the indented line |
|
258 |
+ |
|
259 |
+ ~~~~ |
|
260 |
+ "services": {} |
|
261 |
+ ~~~~ |
|
262 |
+ |
|
263 |
+ Both variations may end with a comma if there are more top-level |
|
264 |
+ keys. |
|
265 |
+ |
|
266 |
+ """ |
|
267 |
+ known_indented_lines = { |
|
268 |
+ '}', |
|
269 |
+ '},', |
|
270 |
+ '"services": {}', |
|
271 |
+ '"services": {},', |
|
272 |
+ } |
|
273 |
+ assert any([ |
|
274 |
+ line.strip() in known_indented_lines and line.startswith((' ', '\t')) |
|
275 |
+ for line in config_txt.splitlines() |
|
276 |
+ ]) |
|
277 |
+ |
|
278 |
+ |
|
239 | 279 |
def vault_config_exporter_shell_interpreter( # noqa: C901 |
240 | 280 |
script: str | Iterable[str], |
241 | 281 |
/, |
... | ... |
@@ -2071,6 +2111,10 @@ class TestCLI: |
2071 | 2111 |
'expected error exit and known error message' |
2072 | 2112 |
) |
2073 | 2113 |
|
2114 |
+ @pytest.mark.xfail( |
|
2115 |
+ reason='config are currently stored as single lines', |
|
2116 |
+ raises=AssertionError, |
|
2117 |
+ ) |
|
2074 | 2118 |
@Parametrize.VALID_TEST_CONFIGS |
2075 | 2119 |
def test_213_import_config_success( |
2076 | 2120 |
self, |
... | ... |
@@ -2097,17 +2141,22 @@ class TestCLI: |
2097 | 2141 |
input=json.dumps(config), |
2098 | 2142 |
catch_exceptions=False, |
2099 | 2143 |
) |
2100 |
- with cli_helpers.config_filename(subsystem='vault').open( |
|
2101 |
- encoding='UTF-8' |
|
2102 |
- ) as infile: |
|
2103 |
- config2 = json.load(infile) |
|
2144 |
+ config_txt = cli_helpers.config_filename( |
|
2145 |
+ subsystem='vault' |
|
2146 |
+ ).read_text(encoding='UTF-8') |
|
2147 |
+ config2 = json.loads(config_txt) |
|
2104 | 2148 |
result = tests.ReadableResult.parse(result_) |
2105 | 2149 |
assert result.clean_exit(empty_stderr=False), 'expected clean exit' |
2106 | 2150 |
assert config2 == config, 'config not imported correctly' |
2107 | 2151 |
assert not result.stderr or all( # pragma: no branch |
2108 | 2152 |
map(is_harmless_config_import_warning, caplog.record_tuples) |
2109 | 2153 |
), 'unexpected error output' |
2154 |
+ assert_vault_config_is_indented_and_line_broken(config_txt) |
|
2110 | 2155 |
|
2156 |
+ @pytest.mark.xfail( |
|
2157 |
+ reason='config are currently stored as single lines', |
|
2158 |
+ raises=AssertionError, |
|
2159 |
+ ) |
|
2111 | 2160 |
@hypothesis.settings( |
2112 | 2161 |
suppress_health_check=[ |
2113 | 2162 |
*hypothesis.settings().suppress_health_check, |
... | ... |
@@ -2155,16 +2204,17 @@ class TestCLI: |
2155 | 2204 |
input=json.dumps(config), |
2156 | 2205 |
catch_exceptions=False, |
2157 | 2206 |
) |
2158 |
- with cli_helpers.config_filename(subsystem='vault').open( |
|
2159 |
- encoding='UTF-8' |
|
2160 |
- ) as infile: |
|
2161 |
- config3 = json.load(infile) |
|
2207 |
+ config_txt = cli_helpers.config_filename( |
|
2208 |
+ subsystem='vault' |
|
2209 |
+ ).read_text(encoding='UTF-8') |
|
2210 |
+ config3 = json.loads(config_txt) |
|
2162 | 2211 |
result = tests.ReadableResult.parse(result_) |
2163 | 2212 |
assert result.clean_exit(empty_stderr=False), 'expected clean exit' |
2164 | 2213 |
assert config3 == config2, 'config not imported correctly' |
2165 | 2214 |
assert not result.stderr or all( |
2166 | 2215 |
map(is_harmless_config_import_warning, caplog.record_tuples) |
2167 | 2216 |
), 'unexpected error output' |
2217 |
+ assert_vault_config_is_indented_and_line_broken(config_txt) |
|
2168 | 2218 |
|
2169 | 2219 |
def test_213b_import_bad_config_not_vault_config( |
2170 | 2220 |
self, |
... | ... |
@@ -2256,8 +2306,54 @@ class TestCLI: |
2256 | 2306 |
'expected error exit and known error message' |
2257 | 2307 |
) |
2258 | 2308 |
|
2309 |
+ @pytest.mark.xfail( |
|
2310 |
+ reason='config exports are currently single-line', |
|
2311 |
+ raises=AssertionError, |
|
2312 |
+ ) |
|
2313 |
+ @Parametrize.VALID_TEST_CONFIGS |
|
2314 |
+ def test_214_export_config_success( |
|
2315 |
+ self, |
|
2316 |
+ caplog: pytest.LogCaptureFixture, |
|
2317 |
+ config: Any, |
|
2318 |
+ ) -> None: |
|
2319 |
+ """Exporting a configuration works.""" |
|
2320 |
+ runner = click.testing.CliRunner(mix_stderr=False) |
|
2321 |
+ # TODO(the-13th-letter): Rewrite using parenthesized |
|
2322 |
+ # with-statements. |
|
2323 |
+ # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
|
2324 |
+ with contextlib.ExitStack() as stack: |
|
2325 |
+ monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
|
2326 |
+ stack.enter_context( |
|
2327 |
+ tests.isolated_vault_config( |
|
2328 |
+ monkeypatch=monkeypatch, |
|
2329 |
+ runner=runner, |
|
2330 |
+ vault_config=config, |
|
2331 |
+ ) |
|
2332 |
+ ) |
|
2333 |
+ with cli_helpers.config_filename(subsystem='vault').open( |
|
2334 |
+ 'w', encoding='UTF-8' |
|
2335 |
+ ) as outfile: |
|
2336 |
+ # Ensure the config is written on one line. |
|
2337 |
+ json.dump(config, outfile, indent=None) |
|
2338 |
+ result_ = runner.invoke( |
|
2339 |
+ cli.derivepassphrase_vault, |
|
2340 |
+ ['--export', '-'], |
|
2341 |
+ catch_exceptions=False, |
|
2342 |
+ ) |
|
2343 |
+ with cli_helpers.config_filename(subsystem='vault').open( |
|
2344 |
+ encoding='UTF-8' |
|
2345 |
+ ) as infile: |
|
2346 |
+ config2 = json.load(infile) |
|
2347 |
+ result = tests.ReadableResult.parse(result_) |
|
2348 |
+ assert result.clean_exit(empty_stderr=False), 'expected clean exit' |
|
2349 |
+ assert config2 == config, 'config not imported correctly' |
|
2350 |
+ assert not result.stderr or all( # pragma: no branch |
|
2351 |
+ map(is_harmless_config_import_warning, caplog.record_tuples) |
|
2352 |
+ ), 'unexpected error output' |
|
2353 |
+ assert_vault_config_is_indented_and_line_broken(result.output) |
|
2354 |
+ |
|
2259 | 2355 |
@Parametrize.EXPORT_FORMAT_OPTIONS |
2260 |
- def test_214_export_settings_no_stored_settings( |
|
2356 |
+ def test_214a_export_settings_no_stored_settings( |
|
2261 | 2357 |
self, |
2262 | 2358 |
export_options: list[str], |
2263 | 2359 |
) -> None: |
... | ... |
@@ -2290,7 +2386,7 @@ class TestCLI: |
2290 | 2386 |
assert result.clean_exit(empty_stderr=True), 'expected clean exit' |
2291 | 2387 |
|
2292 | 2388 |
@Parametrize.EXPORT_FORMAT_OPTIONS |
2293 |
- def test_214a_export_settings_bad_stored_config( |
|
2389 |
+ def test_214b_export_settings_bad_stored_config( |
|
2294 | 2390 |
self, |
2295 | 2391 |
export_options: list[str], |
2296 | 2392 |
) -> None: |
... | ... |
@@ -2320,7 +2416,7 @@ class TestCLI: |
2320 | 2416 |
) |
2321 | 2417 |
|
2322 | 2418 |
@Parametrize.EXPORT_FORMAT_OPTIONS |
2323 |
- def test_214b_export_settings_not_a_file( |
|
2419 |
+ def test_214c_export_settings_not_a_file( |
|
2324 | 2420 |
self, |
2325 | 2421 |
export_options: list[str], |
2326 | 2422 |
) -> None: |
... | ... |
@@ -2352,7 +2448,7 @@ class TestCLI: |
2352 | 2448 |
) |
2353 | 2449 |
|
2354 | 2450 |
@Parametrize.EXPORT_FORMAT_OPTIONS |
2355 |
- def test_214c_export_settings_target_not_a_file( |
|
2451 |
+ def test_214d_export_settings_target_not_a_file( |
|
2356 | 2452 |
self, |
2357 | 2453 |
export_options: list[str], |
2358 | 2454 |
) -> None: |
... | ... |
@@ -2382,7 +2478,7 @@ class TestCLI: |
2382 | 2478 |
) |
2383 | 2479 |
|
2384 | 2480 |
@Parametrize.EXPORT_FORMAT_OPTIONS |
2385 |
- def test_214d_export_settings_settings_directory_not_a_directory( |
|
2481 |
+ def test_214e_export_settings_settings_directory_not_a_directory( |
|
2386 | 2482 |
self, |
2387 | 2483 |
export_options: list[str], |
2388 | 2484 |
) -> None: |
... | ... |
@@ -2559,6 +2655,10 @@ contents go here |
2559 | 2655 |
config = json.load(infile) |
2560 | 2656 |
assert config == {'global': {'phrase': 'abc'}, 'services': {}} |
2561 | 2657 |
|
2658 |
+ @pytest.mark.xfail( |
|
2659 |
+ reason='config are currently stored as single lines', |
|
2660 |
+ raises=AssertionError, |
|
2661 |
+ ) |
|
2562 | 2662 |
@Parametrize.CONFIG_EDITING_VIA_CONFIG_FLAG |
2563 | 2663 |
def test_224_store_config_good( |
2564 | 2664 |
self, |
... | ... |
@@ -2566,7 +2666,12 @@ contents go here |
2566 | 2666 |
input: str, |
2567 | 2667 |
result_config: Any, |
2568 | 2668 |
) -> None: |
2569 |
- """Storing valid settings via `--config` works.""" |
|
2669 |
+ """Storing valid settings via `--config` works. |
|
2670 |
+ |
|
2671 |
+ The format also contains embedded newlines and indentation to make |
|
2672 |
+ the config more readable. |
|
2673 |
+ |
|
2674 |
+ """ |
|
2570 | 2675 |
runner = click.testing.CliRunner(mix_stderr=False) |
2571 | 2676 |
# TODO(the-13th-letter): Rewrite using parenthesized |
2572 | 2677 |
# with-statements. |
... | ... |
@@ -2591,13 +2696,14 @@ contents go here |
2591 | 2696 |
) |
2592 | 2697 |
result = tests.ReadableResult.parse(result_) |
2593 | 2698 |
assert result.clean_exit(), 'expected clean exit' |
2594 |
- with cli_helpers.config_filename(subsystem='vault').open( |
|
2595 |
- encoding='UTF-8' |
|
2596 |
- ) as infile: |
|
2597 |
- config = json.load(infile) |
|
2699 |
+ config_txt = cli_helpers.config_filename( |
|
2700 |
+ subsystem='vault' |
|
2701 |
+ ).read_text(encoding='UTF-8') |
|
2702 |
+ config = json.loads(config_txt) |
|
2598 | 2703 |
assert config == result_config, ( |
2599 | 2704 |
'stored config does not match expectation' |
2600 | 2705 |
) |
2706 |
+ assert_vault_config_is_indented_and_line_broken(config_txt) |
|
2601 | 2707 |
|
2602 | 2708 |
@Parametrize.CONFIG_EDITING_VIA_CONFIG_FLAG_FAILURES |
2603 | 2709 |
def test_225_store_config_fail( |
2604 | 2710 |