Marco Ricci commited on 2025-08-14 19:10:17
Zeige 1 geänderte Dateien mit 107 Einfügungen und 38 Löschungen.
As in a2c0a0b3b6f000d824787dabc011041549bda206, introduce further classes/groupings for the `vault` derivation scheme tests, trimming test names appropriately.
... | ... |
@@ -121,6 +121,10 @@ class TestVault: |
121 | 121 |
|
122 | 122 |
phrase = PHRASE |
123 | 123 |
|
124 |
+ |
|
125 |
+class TestPhraseDependence: |
|
126 |
+ """Test the dependence of the internal hash on the master passphrase.""" |
|
127 |
+ |
|
124 | 128 |
@hypothesis.given( |
125 | 129 |
phrases=strategies.lists( |
126 | 130 |
strategies.binary(min_size=1, max_size=BLOCK_SIZE // 2), |
... | ... |
@@ -134,7 +138,7 @@ class TestVault: |
134 | 138 |
max_size=BLOCK_SIZE // 2, |
135 | 139 |
), |
136 | 140 |
) |
137 |
- def test_100a_create_hash_phrase_dependence_small( |
|
141 |
+ def test_small( |
|
138 | 142 |
self, |
139 | 143 |
phrases: list[bytes], |
140 | 144 |
service: str, |
... | ... |
@@ -161,7 +165,7 @@ class TestVault: |
161 | 165 |
max_size=BLOCK_SIZE // 2, |
162 | 166 |
), |
163 | 167 |
) |
164 |
- def test_100b_create_hash_phrase_dependence_medium( |
|
168 |
+ def test_medium( |
|
165 | 169 |
self, |
166 | 170 |
phrases: list[bytes], |
167 | 171 |
service: str, |
... | ... |
@@ -190,7 +194,7 @@ class TestVault: |
190 | 194 |
max_size=BLOCK_SIZE // 2, |
191 | 195 |
), |
192 | 196 |
) |
193 |
- def test_100c_create_hash_phrase_dependence_large( |
|
197 |
+ def test_large( |
|
194 | 198 |
self, |
195 | 199 |
phrases: tuple[bytes, bytes], |
196 | 200 |
service: str, |
... | ... |
@@ -223,7 +227,7 @@ class TestVault: |
223 | 227 |
max_size=BLOCK_SIZE // 2, |
224 | 228 |
), |
225 | 229 |
) |
226 |
- def test_100d_create_hash_phrase_dependence_mixed( |
|
230 |
+ def test_mixed( |
|
227 | 231 |
self, |
228 | 232 |
phrases: list[bytes], |
229 | 233 |
service: str, |
... | ... |
@@ -237,6 +241,10 @@ class TestVault: |
237 | 241 |
phrase=phrases[0], service=service |
238 | 242 |
) != vault.Vault.create_hash(phrase=phrases[1], service=service) |
239 | 243 |
|
244 |
+ |
|
245 |
+class TestServiceNameDependence: |
|
246 |
+ """Test the dependence of the internal hash on the service name.""" |
|
247 |
+ |
|
240 | 248 |
@hypothesis.given( |
241 | 249 |
phrase=strategies.text( |
242 | 250 |
strategies.characters(min_codepoint=32, max_codepoint=126), |
... | ... |
@@ -250,7 +258,7 @@ class TestVault: |
250 | 258 |
unique=True, |
251 | 259 |
), |
252 | 260 |
) |
253 |
- def test_101_create_hash_service_name_dependence( |
|
261 |
+ def test_service_name_dependence( |
|
254 | 262 |
self, |
255 | 263 |
phrase: str, |
256 | 264 |
services: list[bytes], |
... | ... |
@@ -260,6 +268,10 @@ class TestVault: |
260 | 268 |
phrase=phrase, service=services[0] |
261 | 269 |
) != vault.Vault.create_hash(phrase=phrase, service=services[1]) |
262 | 270 |
|
271 |
+ |
|
272 |
+class TestInterchangablePhrases: |
|
273 |
+ """Test the interchangability of certain master passphrases.""" |
|
274 |
+ |
|
263 | 275 |
@hypothesis.given( |
264 | 276 |
phrases=strategies.binary(max_size=BLOCK_SIZE // 2).flatmap( |
265 | 277 |
lambda bs: strategies.tuples( |
... | ... |
@@ -276,7 +288,7 @@ class TestVault: |
276 | 288 |
max_size=32, |
277 | 289 |
), |
278 | 290 |
) |
279 |
- def test_102a_interchangable_phrases_small( |
|
291 |
+ def test_small( |
|
280 | 292 |
self, |
281 | 293 |
phrases: tuple[bytes, bytes], |
282 | 294 |
service: str, |
... | ... |
@@ -307,7 +319,7 @@ class TestVault: |
307 | 319 |
max_size=32, |
308 | 320 |
), |
309 | 321 |
) |
310 |
- def test_102b_interchangable_phrases_large( |
|
322 |
+ def test_large( |
|
311 | 323 |
self, |
312 | 324 |
phrases: tuple[bytes, bytes], |
313 | 325 |
service: str, |
... | ... |
@@ -318,20 +330,27 @@ class TestVault: |
318 | 330 |
phrase=phrases[0], service=service |
319 | 331 |
) == vault.Vault.create_hash(phrase=phrases[1], service=service) |
320 | 332 |
|
333 |
+ |
|
334 |
+class TestBasicFunctionalityFromUpstream(TestVault): |
|
335 |
+ """Test passphrase derivation with the "vault" scheme: upstream tests.""" |
|
336 |
+ |
|
321 | 337 |
@Parametrize.SAMPLE_SERVICES_AND_PHRASES |
322 |
- def test_200_basic_configuration( |
|
338 |
+ def test_basic_configuration( |
|
323 | 339 |
self, service: bytes | str, expected: bytes |
324 | 340 |
) -> None: |
325 | 341 |
"""Deriving a passphrase principally works.""" |
326 | 342 |
assert vault.Vault(phrase=self.phrase).generate(service) == expected |
327 | 343 |
|
328 |
- def test_201_phrase_dependence(self) -> None: |
|
344 |
+ def test_phrase_dependence(self) -> None: |
|
329 | 345 |
"""The derived passphrase is dependent on the master passphrase.""" |
330 | 346 |
assert ( |
331 | 347 |
vault.Vault(phrase=(self.phrase + b"X")).generate("google") |
332 | 348 |
== b"n+oIz6sL>K*lTEWYRO%7" |
333 | 349 |
) |
334 | 350 |
|
351 |
+ # TODO(the-13th-letter): Retire this test in favor of |
|
352 |
+ # TestPhraseDependence. The first example is a "short" example, the |
|
353 |
+ # second is a "mixed" one. |
|
335 | 354 |
@hypothesis.given( |
336 | 355 |
phrases=strategies.lists( |
337 | 356 |
strategies.binary(min_size=1, max_size=32), |
... | ... |
@@ -366,7 +385,7 @@ class TestVault: |
366 | 385 |
), |
367 | 386 |
raises=AssertionError, |
368 | 387 |
) |
369 |
- def test_201a_phrase_dependence( |
|
388 |
+ def test_xxx_phrase_dependence( |
|
370 | 389 |
self, |
371 | 390 |
phrases: list[bytes], |
372 | 391 |
service: str, |
... | ... |
@@ -384,19 +403,28 @@ class TestVault: |
384 | 403 |
phrase=phrases[1] |
385 | 404 |
).generate(service) |
386 | 405 |
|
387 |
- def test_202a_reproducibility_and_bytes_service_name(self) -> None: |
|
406 |
+ |
|
407 |
+class TestStringAndBinaryExchangability(TestVault): |
|
408 |
+ """Test the exchangability of text and byte strings in the "vault" scheme. |
|
409 |
+ |
|
410 |
+ This specifically refers to ASCII-cleanliness, and buffer-type |
|
411 |
+ independence. |
|
412 |
+ |
|
413 |
+ """ |
|
414 |
+ |
|
415 |
+ def test_bytes_service_name(self) -> None: |
|
388 | 416 |
"""Deriving a passphrase works equally for byte strings.""" |
389 | 417 |
assert vault.Vault(phrase=self.phrase).generate( |
390 | 418 |
b"google" |
391 | 419 |
) == vault.Vault(phrase=self.phrase).generate("google") |
392 | 420 |
|
393 |
- def test_202b_reproducibility_and_bytearray_service_name(self) -> None: |
|
421 |
+ def test_bytearray_service_name(self) -> None: |
|
394 | 422 |
"""Deriving a passphrase works equally for byte arrays.""" |
395 | 423 |
assert vault.Vault(phrase=self.phrase).generate( |
396 | 424 |
b"google" |
397 | 425 |
) == vault.Vault(phrase=self.phrase).generate(bytearray(b"google")) |
398 | 426 |
|
399 |
- def test_202c_reproducibility_and_buffer_like_service_name(self) -> None: |
|
427 |
+ def test_buffer_like_service_name(self) -> None: |
|
400 | 428 |
"""Deriving a passphrase works equally for memory views.""" |
401 | 429 |
assert vault.Vault(phrase=self.phrase).generate( |
402 | 430 |
b"google" |
... | ... |
@@ -414,7 +442,7 @@ class TestVault: |
414 | 442 |
max_size=32, |
415 | 443 |
), |
416 | 444 |
) |
417 |
- def test_203a_reproducibility_and_binary_phrases( |
|
445 |
+ def test_binary_phrases( |
|
418 | 446 |
self, |
419 | 447 |
phrase: str, |
420 | 448 |
service: str, |
... | ... |
@@ -450,7 +478,7 @@ class TestVault: |
450 | 478 |
max_size=32, |
451 | 479 |
), |
452 | 480 |
) |
453 |
- def test_203b_reproducibility_and_binary_service_name( |
|
481 |
+ def test_binary_service_name( |
|
454 | 482 |
self, |
455 | 483 |
phrase: str, |
456 | 484 |
service: str, |
... | ... |
@@ -474,6 +502,8 @@ class TestVault: |
474 | 502 |
"service name generate different passphrases" |
475 | 503 |
) |
476 | 504 |
|
505 |
+ # TODO(the-13th-letter): Retire this test in favor of |
|
506 |
+ # TestServiceNameDependence. |
|
477 | 507 |
@hypothesis.given( |
478 | 508 |
phrase=strategies.text( |
479 | 509 |
strategies.characters(min_codepoint=32, max_codepoint=126), |
... | ... |
@@ -487,7 +517,7 @@ class TestVault: |
487 | 517 |
unique=True, |
488 | 518 |
), |
489 | 519 |
) |
490 |
- def test_204a_service_name_dependence( |
|
520 |
+ def test_xxx_service_name_dependence( |
|
491 | 521 |
self, |
492 | 522 |
phrase: str, |
493 | 523 |
services: list[bytes], |
... | ... |
@@ -497,6 +527,9 @@ class TestVault: |
497 | 527 |
phrase=phrase |
498 | 528 |
).generate(services[1]) |
499 | 529 |
|
530 |
+ # TODO(the-13th-letter): Move this test into TestServiceNameDependence |
|
531 |
+ # and write a counterpart for TestPhraseDependence. Or, generalize |
|
532 |
+ # this test into a class TestConfigDependence. |
|
500 | 533 |
@hypothesis.given( |
501 | 534 |
phrase=strategies.text( |
502 | 535 |
strategies.characters(min_codepoint=32, max_codepoint=126), |
... | ... |
@@ -511,7 +544,7 @@ class TestVault: |
511 | 544 |
unique=True, |
512 | 545 |
), |
513 | 546 |
) |
514 |
- def test_204b_service_name_dependence_with_config( |
|
547 |
+ def test_service_name_dependence_with_config( |
|
515 | 548 |
self, |
516 | 549 |
phrase: str, |
517 | 550 |
config: dict[str, int], |
... | ... |
@@ -533,13 +566,25 @@ class TestVault: |
533 | 566 |
# implementation, and should be raised. |
534 | 567 |
raise |
535 | 568 |
|
536 |
- def test_210_nonstandard_length(self) -> None: |
|
569 |
+ |
|
570 |
+# TODO(the-13th-letter): State machine, for master passphrase, service |
|
571 |
+# name and config dependence? |
|
572 |
+ |
|
573 |
+ |
|
574 |
+class TestConstraintSatisfactionFromUpstream001(TestVault): |
|
575 |
+ """Test passphrase derivation with the "vault" scheme: upstream tests.""" |
|
576 |
+ |
|
577 |
+ def test_nonstandard_length(self) -> None: |
|
537 | 578 |
"""Deriving a passphrase adheres to imposed length limits.""" |
538 | 579 |
assert ( |
539 | 580 |
vault.Vault(phrase=self.phrase, length=4).generate("google") |
540 | 581 |
== b"xDFu" |
541 | 582 |
) |
542 | 583 |
|
584 |
+ |
|
585 |
+class TestConstraintSatisfactionThoroughness001(TestVault): |
|
586 |
+ """Test passphrase derivation with the "vault" scheme: constraint satisfaction.""" |
|
587 |
+ |
|
543 | 588 |
@hypothesis.given( |
544 | 589 |
phrase=strategies.one_of( |
545 | 590 |
strategies.binary(min_size=1, max_size=100), |
... | ... |
@@ -552,7 +597,7 @@ class TestVault: |
552 | 597 |
length=strategies.integers(min_value=1, max_value=200), |
553 | 598 |
service=strategies.text(min_size=1, max_size=100), |
554 | 599 |
) |
555 |
- def test_210a_password_with_length( |
|
600 |
+ def test_password_with_length( |
|
556 | 601 |
self, |
557 | 602 |
phrase: str | bytes, |
558 | 603 |
length: int, |
... | ... |
@@ -562,7 +607,11 @@ class TestVault: |
562 | 607 |
password = vault.Vault(phrase=phrase, length=length).generate(service) |
563 | 608 |
assert len(password) == length |
564 | 609 |
|
565 |
- def test_211_repetition_limit(self) -> None: |
|
610 |
+ |
|
611 |
+class TestConstraintSatisfactionFromUpstream002(TestVault): |
|
612 |
+ """Test passphrase derivation with the "vault" scheme: upstream tests.""" |
|
613 |
+ |
|
614 |
+ def test_repetition_limit(self) -> None: |
|
566 | 615 |
"""Deriving a passphrase adheres to imposed repetition limits.""" |
567 | 616 |
assert ( |
568 | 617 |
vault.Vault( |
... | ... |
@@ -571,21 +620,21 @@ class TestVault: |
571 | 620 |
== b"IVTDzACftqopUXqDHPkuCIhV" |
572 | 621 |
) |
573 | 622 |
|
574 |
- def test_212_without_symbols(self) -> None: |
|
623 |
+ def test_without_symbols(self) -> None: |
|
575 | 624 |
"""Deriving a passphrase adheres to imposed limits on symbols.""" |
576 | 625 |
assert ( |
577 | 626 |
vault.Vault(phrase=self.phrase, symbol=0).generate("google") |
578 | 627 |
== b"XZ4wRe0bZCazbljCaMqR" |
579 | 628 |
) |
580 | 629 |
|
581 |
- def test_213_no_numbers(self) -> None: |
|
630 |
+ def test_no_numbers(self) -> None: |
|
582 | 631 |
"""Deriving a passphrase adheres to imposed limits on numbers.""" |
583 | 632 |
assert ( |
584 | 633 |
vault.Vault(phrase=self.phrase, number=0).generate("google") |
585 | 634 |
== b"_*$TVH.%^aZl(LUeOT?>" |
586 | 635 |
) |
587 | 636 |
|
588 |
- def test_214_no_lowercase_letters(self) -> None: |
|
637 |
+ def test_no_lowercase_letters(self) -> None: |
|
589 | 638 |
""" |
590 | 639 |
Deriving a passphrase adheres to imposed limits on lowercase letters. |
591 | 640 |
""" |
... | ... |
@@ -594,7 +643,7 @@ class TestVault: |
594 | 643 |
== b":{?)+7~@OA:L]!0E$)(+" |
595 | 644 |
) |
596 | 645 |
|
597 |
- def test_215_at_least_5_digits(self) -> None: |
|
646 |
+ def test_at_least_5_digits(self) -> None: |
|
598 | 647 |
"""Deriving a passphrase adheres to imposed counts of numbers.""" |
599 | 648 |
assert ( |
600 | 649 |
vault.Vault(phrase=self.phrase, length=8, number=5).generate( |
... | ... |
@@ -603,14 +652,14 @@ class TestVault: |
603 | 652 |
== b"i0908.7[" |
604 | 653 |
) |
605 | 654 |
|
606 |
- def test_216_lots_of_spaces(self) -> None: |
|
655 |
+ def test_lots_of_spaces(self) -> None: |
|
607 | 656 |
"""Deriving a passphrase adheres to imposed counts of spaces.""" |
608 | 657 |
assert ( |
609 | 658 |
vault.Vault(phrase=self.phrase, space=12).generate("songkick") |
610 | 659 |
== b" c 6 Bq % 5fR " |
611 | 660 |
) |
612 | 661 |
|
613 |
- def test_217_all_character_classes(self) -> None: |
|
662 |
+ def test_all_character_classes(self) -> None: |
|
614 | 663 |
"""Deriving a passphrase adheres to imposed counts of all types.""" |
615 | 664 |
assert ( |
616 | 665 |
vault.Vault( |
... | ... |
@@ -625,6 +674,10 @@ class TestVault: |
625 | 674 |
== b": : fv_wqt>a-4w1S R" |
626 | 675 |
) |
627 | 676 |
|
677 |
+ |
|
678 |
+class TestConstraintSatisfactionHeavyDuty001(TestVault): |
|
679 |
+ """Test passphrase derivation with the "vault" scheme: constraint satisfaction.""" |
|
680 |
+ |
|
628 | 681 |
@hypothesis.given( |
629 | 682 |
phrase=strategies.one_of( |
630 | 683 |
strategies.binary(min_size=1), strategies.text(min_size=1) |
... | ... |
@@ -674,7 +727,7 @@ class TestVault: |
674 | 727 |
}, |
675 | 728 |
service="0", |
676 | 729 |
).via('branch coverage (test function): "no repeats" case') |
677 |
- def test_217a_all_length_character_and_occurrence_constraints_satisfied( |
|
730 |
+ def test_all_length_character_and_occurrence_constraints_satisfied( |
|
678 | 731 |
self, |
679 | 732 |
phrase: str | bytes, |
680 | 733 |
config: dict[str, int], |
... | ... |
@@ -726,7 +779,11 @@ class TestVault: |
726 | 779 |
"Password does not satisfy character repeat constraints." |
727 | 780 |
) |
728 | 781 |
|
729 |
- def test_218_only_numbers_and_very_high_repetition_limit(self) -> None: |
|
782 |
+ |
|
783 |
+class TestConstraintSatisfactionFromUpstream003(TestVault): |
|
784 |
+ """Test passphrase derivation with the "vault" scheme: upstream tests.""" |
|
785 |
+ |
|
786 |
+ def test_only_numbers_and_very_high_repetition_limit(self) -> None: |
|
730 | 787 |
"""Deriving a passphrase adheres to imposed repetition limits. |
731 | 788 |
|
732 | 789 |
This example is checked explicitly against forbidden substrings. |
... | ... |
@@ -757,6 +814,10 @@ class TestVault: |
757 | 814 |
for substring in forbidden_substrings: |
758 | 815 |
assert substring not in generated |
759 | 816 |
|
817 |
+ |
|
818 |
+class TestConstraintSatisfactionThoroughness002(TestVault): |
|
819 |
+ """Test passphrase derivation with the "vault" scheme: constraint satisfaction.""" |
|
820 |
+ |
|
760 | 821 |
# This test has time complexity `O(length * repeat)`, both of which |
761 | 822 |
# are chosen by hypothesis and thus outside our control. |
762 | 823 |
@hypothesis.settings(deadline=None) |
... | ... |
@@ -787,19 +848,27 @@ class TestVault: |
787 | 848 |
for i in range((length + 1) - (repeat + 1)): |
788 | 849 |
assert len(set(password[i : i + repeat + 1])) > 1 |
789 | 850 |
|
790 |
- def test_219_very_limited_character_set(self) -> None: |
|
851 |
+ |
|
852 |
+class TestConstraintSatisfactionFromUpstream004(TestVault): |
|
853 |
+ """Test passphrase derivation with the "vault" scheme: upstream tests.""" |
|
854 |
+ |
|
855 |
+ def test_very_limited_character_set(self) -> None: |
|
791 | 856 |
"""Deriving a passphrase works even with limited character sets.""" |
792 | 857 |
generated = vault.Vault( |
793 | 858 |
phrase=b"", length=24, lower=0, upper=0, space=0, symbol=0 |
794 | 859 |
).generate("testing") |
795 | 860 |
assert generated == b"763252593304946694588866" |
796 | 861 |
|
797 |
- def test_220_character_set_subtraction(self) -> None: |
|
862 |
+ |
|
863 |
+class TestUtilities(TestVault): |
|
864 |
+ """Test passphrase derivation with the "vault" scheme: utility tests.""" |
|
865 |
+ |
|
866 |
+ def test_character_set_subtraction(self) -> None: |
|
798 | 867 |
"""Removing allowed characters internally works.""" |
799 | 868 |
assert vault.Vault._subtract(b"be", b"abcdef") == bytearray(b"acdf") |
800 | 869 |
|
801 | 870 |
@Parametrize.ENTROPY_RESULTS |
802 |
- def test_221_entropy( |
|
871 |
+ def test_entropy( |
|
803 | 872 |
self, length: int, settings: dict[str, int], entropy: int |
804 | 873 |
) -> None: |
805 | 874 |
"""Estimating the entropy and sufficient hash length works.""" |
... | ... |
@@ -812,7 +881,7 @@ class TestVault: |
812 | 881 |
) |
813 | 882 |
assert v._estimate_sufficient_hash_length(8.0) >= entropy |
814 | 883 |
|
815 |
- def test_222_hash_length_estimation(self) -> None: |
|
884 |
+ def test_hash_length_estimation(self) -> None: |
|
816 | 885 |
""" |
817 | 886 |
Estimating the entropy and hash length for degenerate cases works. |
818 | 887 |
""" |
... | ... |
@@ -829,7 +898,7 @@ class TestVault: |
829 | 898 |
assert v._estimate_sufficient_hash_length() > 0 |
830 | 899 |
|
831 | 900 |
@Parametrize.SAMPLE_SERVICES_AND_PHRASES |
832 |
- def test_223_hash_length_expansion( |
|
901 |
+ def test_hash_length_expansion( |
|
833 | 902 |
self, |
834 | 903 |
monkeypatch: pytest.MonkeyPatch, |
835 | 904 |
service: str | bytes, |
... | ... |
@@ -848,7 +917,7 @@ class TestVault: |
848 | 917 |
assert v.generate(service) == expected |
849 | 918 |
|
850 | 919 |
@Parametrize.BINARY_STRINGS |
851 |
- def test_224_binary_strings(self, s: str | bytes | bytearray) -> None: |
|
920 |
+ def test_binary_strings(self, s: str | bytes | bytearray) -> None: |
|
852 | 921 |
"""Byte string conversion is idempotent.""" |
853 | 922 |
binstr = vault.Vault._get_binary_string |
854 | 923 |
if isinstance(s, str): |
... | ... |
@@ -858,14 +927,14 @@ class TestVault: |
858 | 927 |
assert binstr(s) == bytes(s) |
859 | 928 |
assert binstr(binstr(s)) == bytes(s) |
860 | 929 |
|
861 |
- def test_310_too_many_symbols(self) -> None: |
|
930 |
+ def test_too_many_symbols(self) -> None: |
|
862 | 931 |
"""Deriving short passphrases with large length constraints fails.""" |
863 | 932 |
with pytest.raises( |
864 | 933 |
ValueError, match="requested passphrase length too short" |
865 | 934 |
): |
866 | 935 |
vault.Vault(phrase=self.phrase, symbol=100) |
867 | 936 |
|
868 |
- def test_311_no_viable_characters(self) -> None: |
|
937 |
+ def test_no_viable_characters(self) -> None: |
|
869 | 938 |
"""Deriving passphrases without allowed characters fails.""" |
870 | 939 |
with pytest.raises(ValueError, match="no allowed characters left"): |
871 | 940 |
vault.Vault( |
... | ... |
@@ -878,14 +947,14 @@ class TestVault: |
878 | 947 |
symbol=0, |
879 | 948 |
) |
880 | 949 |
|
881 |
- def test_320_character_set_subtraction_duplicate(self) -> None: |
|
950 |
+ def test_character_set_subtraction_duplicate(self) -> None: |
|
882 | 951 |
"""Character sets do not contain duplicate characters.""" |
883 | 952 |
with pytest.raises(ValueError, match="duplicate characters"): |
884 | 953 |
vault.Vault._subtract(b"abcdef", b"aabbccddeeff") |
885 | 954 |
with pytest.raises(ValueError, match="duplicate characters"): |
886 | 955 |
vault.Vault._subtract(b"aabbccddeeff", b"abcdef") |
887 | 956 |
|
888 |
- def test_322_hash_length_estimation(self) -> None: |
|
957 |
+ def test_invalid_hash_length_estimation_safety_factor(self) -> None: |
|
889 | 958 |
"""Hash length estimation rejects invalid safety factors.""" |
890 | 959 |
v = vault.Vault(phrase=self.phrase) |
891 | 960 |
with pytest.raises(ValueError, match="invalid safety factor"): |
892 | 961 |