Reformat "vault" tests
Marco Ricci

Marco Ricci commited on 2025-01-27 19:46:57
Zeige 1 geänderte Dateien mit 79 Einfügungen und 88 Löschungen.


Reformat the "vault" tests.  This includes retiring the `Vault` type
alias and using the proper Google-style `vault.Vault` instead, because
the API documentation does not correctly resolve this type alias
otherwise.
... ...
@@ -13,16 +13,14 @@ from typing import TYPE_CHECKING
13 13
 import hypothesis
14 14
 import pytest
15 15
 from hypothesis import strategies
16
-from typing_extensions import TypeAlias, TypeVar
16
+from typing_extensions import TypeVar
17 17
 
18
-import derivepassphrase
19 18
 import tests
19
+from derivepassphrase import vault
20 20
 
21 21
 if TYPE_CHECKING:
22 22
     from collections.abc import Iterator
23 23
 
24
-Vault: TypeAlias = derivepassphrase.vault.Vault
25
-
26 24
 BLOCK_SIZE = hashlib.sha1().block_size
27 25
 DIGEST_SIZE = hashlib.sha1().digest_size
28 26
 
... ...
@@ -32,7 +30,7 @@ def phrases_are_interchangable(
32 30
     phrase2: bytes | bytearray | str,
33 31
     /,
34 32
 ) -> bool:
35
-    """Work-alike of [`Vault.phrases_are_interchangable`][].
33
+    """Work-alike of [`vault.Vault.phrases_are_interchangable`][].
36 34
 
37 35
     This version is not resistant to timing attacks, but faster, and
38 36
     supports strings directly.
... ...
@@ -44,7 +42,7 @@ def phrases_are_interchangable(
44 42
             A passphrase to compare.
45 43
 
46 44
     Returns:
47
-        True if the phrases behave identically under [`Vault`][],
45
+        True if the phrases behave identically under [`vault.Vault`][],
48 46
         false otherwise.
49 47
 
50 48
     """
... ...
@@ -56,8 +54,8 @@ def phrases_are_interchangable(
56 54
             else bs.rstrip(b'\x00')
57 55
         )
58 56
 
59
-    phrase1 = canon(Vault._get_binary_string(phrase1))
60
-    phrase2 = canon(Vault._get_binary_string(phrase2))
57
+    phrase1 = canon(vault.Vault._get_binary_string(phrase1))
58
+    phrase2 = canon(vault.Vault._get_binary_string(phrase2))
61 59
     return phrase1 == phrase2
62 60
 
63 61
 
... ...
@@ -83,9 +81,7 @@ class TestVault:
83 81
             min_size=2,
84 82
             max_size=2,
85 83
             unique=True,
86
-        ).filter(
87
-            lambda tup: not phrases_are_interchangable(*tup)
88
-        ),
84
+        ).filter(lambda tup: not phrases_are_interchangable(*tup)),
89 85
         service=strategies.text(
90 86
             strategies.characters(min_codepoint=32, max_codepoint=126),
91 87
             min_size=1,
... ...
@@ -102,9 +98,9 @@ class TestVault:
102 98
         We filter out interchangable passphrases during generation.
103 99
 
104 100
         """
105
-        assert Vault.create_hash(
101
+        assert vault.Vault.create_hash(
106 102
             phrase=phrases[0], service=service
107
-        ) != Vault.create_hash(phrase=phrases[1], service=service)
103
+        ) != vault.Vault.create_hash(phrase=phrases[1], service=service)
108 104
 
109 105
     @hypothesis.given(
110 106
         phrases=strategies.lists(
... ...
@@ -112,9 +108,7 @@ class TestVault:
112 108
             min_size=2,
113 109
             max_size=2,
114 110
             unique=True,
115
-        ).filter(
116
-            lambda tup: not phrases_are_interchangable(*tup)
117
-        ),
111
+        ).filter(lambda tup: not phrases_are_interchangable(*tup)),
118 112
         service=strategies.text(
119 113
             strategies.characters(min_codepoint=32, max_codepoint=126),
120 114
             min_size=1,
... ...
@@ -131,9 +125,9 @@ class TestVault:
131 125
         We filter out interchangable passphrases during generation.
132 126
 
133 127
         """
134
-        assert Vault.create_hash(
128
+        assert vault.Vault.create_hash(
135 129
             phrase=phrases[0], service=service
136
-        ) != Vault.create_hash(phrase=phrases[1], service=service)
130
+        ) != vault.Vault.create_hash(phrase=phrases[1], service=service)
137 131
 
138 132
     @hypothesis.given(
139 133
         phrases=strategies.lists(
... ...
@@ -143,9 +137,7 @@ class TestVault:
143 137
             min_size=2,
144 138
             max_size=2,
145 139
             unique=True,
146
-        ).filter(
147
-            lambda tup: not phrases_are_interchangable(*tup)
148
-        ),
140
+        ).filter(lambda tup: not phrases_are_interchangable(*tup)),
149 141
         service=strategies.text(
150 142
             strategies.characters(min_codepoint=32, max_codepoint=126),
151 143
             min_size=1,
... ...
@@ -162,9 +154,9 @@ class TestVault:
162 154
         We filter out interchangable passphrases during generation.
163 155
 
164 156
         """
165
-        assert Vault.create_hash(
157
+        assert vault.Vault.create_hash(
166 158
             phrase=phrases[0], service=service
167
-        ) != Vault.create_hash(phrase=phrases[1], service=service)
159
+        ) != vault.Vault.create_hash(phrase=phrases[1], service=service)
168 160
 
169 161
     @hypothesis.given(
170 162
         phrases=strategies.lists(
... ...
@@ -178,9 +170,7 @@ class TestVault:
178 170
             min_size=2,
179 171
             max_size=2,
180 172
             unique=True,
181
-        ).filter(
182
-            lambda tup: not phrases_are_interchangable(*tup)
183
-        ),
173
+        ).filter(lambda tup: not phrases_are_interchangable(*tup)),
184 174
         service=strategies.text(
185 175
             strategies.characters(min_codepoint=32, max_codepoint=126),
186 176
             min_size=1,
... ...
@@ -197,9 +187,9 @@ class TestVault:
197 187
         We filter out interchangable passphrases during generation.
198 188
 
199 189
         """
200
-        assert Vault.create_hash(
190
+        assert vault.Vault.create_hash(
201 191
             phrase=phrases[0], service=service
202
-        ) != Vault.create_hash(phrase=phrases[1], service=service)
192
+        ) != vault.Vault.create_hash(phrase=phrases[1], service=service)
203 193
 
204 194
     @hypothesis.given(
205 195
         phrase=strategies.text(
... ...
@@ -220,9 +210,9 @@ class TestVault:
220 210
         services: list[bytes],
221 211
     ) -> None:
222 212
         """The internal hash is dependent on the service name."""
223
-        assert Vault.create_hash(
213
+        assert vault.Vault.create_hash(
224 214
             phrase=phrase, service=services[0]
225
-        ) != Vault.create_hash(phrase=phrase, service=services[1])
215
+        ) != vault.Vault.create_hash(phrase=phrase, service=services[1])
226 216
 
227 217
     @tests.hypothesis_settings_coverage_compatible
228 218
     @hypothesis.given(
... ...
@@ -232,7 +222,7 @@ class TestVault:
232 222
                 strategies.integers(
233 223
                     min_value=1,
234 224
                     max_value=BLOCK_SIZE - len(bs),
235
-                ).map(lambda num: bs + b'\x00' * num)
225
+                ).map(lambda num: bs + b'\x00' * num),
236 226
             )
237 227
         ),
238 228
         service=strategies.text(
... ...
@@ -247,10 +237,10 @@ class TestVault:
247 237
         service: str,
248 238
     ) -> None:
249 239
         """Claimed interchangable passphrases are actually interchangable."""
250
-        assert Vault.phrases_are_interchangable(*phrases)
251
-        assert Vault.create_hash(
240
+        assert vault.Vault.phrases_are_interchangable(*phrases)
241
+        assert vault.Vault.create_hash(
252 242
             phrase=phrases[0], service=service
253
-        ) == Vault.create_hash(phrase=phrases[1], service=service)
243
+        ) == vault.Vault.create_hash(phrase=phrases[1], service=service)
254 244
 
255 245
     @tests.hypothesis_settings_coverage_compatible
256 246
     @hypothesis.given(
... ...
@@ -279,10 +269,10 @@ class TestVault:
279 269
         service: str,
280 270
     ) -> None:
281 271
         """Claimed interchangable passphrases are actually interchangable."""
282
-        assert Vault.phrases_are_interchangable(*phrases)
283
-        assert Vault.create_hash(
272
+        assert vault.Vault.phrases_are_interchangable(*phrases)
273
+        assert vault.Vault.create_hash(
284 274
             phrase=phrases[0], service=service
285
-        ) == Vault.create_hash(phrase=phrases[1], service=service)
275
+        ) == vault.Vault.create_hash(phrase=phrases[1], service=service)
286 276
 
287 277
     @pytest.mark.parametrize(
288 278
         ['service', 'expected'],
... ...
@@ -295,12 +285,12 @@ class TestVault:
295 285
         self, service: bytes | str, expected: bytes
296 286
     ) -> None:
297 287
         """Deriving a passphrase principally works."""
298
-        assert Vault(phrase=self.phrase).generate(service) == expected
288
+        assert vault.Vault(phrase=self.phrase).generate(service) == expected
299 289
 
300 290
     def test_201_phrase_dependence(self) -> None:
301 291
         """The derived passphrase is dependent on the master passphrase."""
302 292
         assert (
303
-            Vault(phrase=(self.phrase + b'X')).generate('google')
293
+            vault.Vault(phrase=(self.phrase + b'X')).generate('google')
304 294
             == b'n+oIz6sL>K*lTEWYRO%7'
305 295
         )
306 296
 
... ...
@@ -310,9 +300,7 @@ class TestVault:
310 300
             min_size=2,
311 301
             max_size=2,
312 302
             unique=True,
313
-        ).filter(
314
-            lambda tup: not phrases_are_interchangable(*tup)
315
-        ),
303
+        ).filter(lambda tup: not phrases_are_interchangable(*tup)),
316 304
         service=strategies.text(
317 305
             strategies.characters(min_codepoint=32, max_codepoint=126),
318 306
             min_size=1,
... ...
@@ -348,27 +336,27 @@ class TestVault:
348 336
         """The derived passphrase is dependent on the master passphrase.
349 337
 
350 338
         Certain pairs of master passphrases are known to be
351
-        interchangable; see [`Vault.phrases_are_interchangable`][].
339
+        interchangable; see [`vault.Vault.phrases_are_interchangable`][].
352 340
         These are excluded from consideration by the hypothesis
353 341
         strategy.
354 342
 
355 343
         """
356 344
         # See test_100_create_hash_phrase_dependence for context.
357
-        assert Vault(phrase=phrases[0]).generate(
358
-            service
359
-        ) != Vault(phrase=phrases[1]).generate(service)
345
+        assert vault.Vault(phrase=phrases[0]).generate(service) != vault.Vault(
346
+            phrase=phrases[1]
347
+        ).generate(service)
360 348
 
361 349
     def test_202a_reproducibility_and_bytes_service_name(self) -> None:
362 350
         """Deriving a passphrase works equally for byte strings."""
363
-        assert Vault(phrase=self.phrase).generate(b'google') == Vault(
364
-            phrase=self.phrase
365
-        ).generate('google')
351
+        assert vault.Vault(phrase=self.phrase).generate(
352
+            b'google'
353
+        ) == vault.Vault(phrase=self.phrase).generate('google')
366 354
 
367 355
     def test_202b_reproducibility_and_bytearray_service_name(self) -> None:
368 356
         """Deriving a passphrase works equally for byte arrays."""
369
-        assert Vault(phrase=self.phrase).generate(b'google') == Vault(
370
-            phrase=self.phrase
371
-        ).generate(bytearray(b'google'))
357
+        assert vault.Vault(phrase=self.phrase).generate(
358
+            b'google'
359
+        ) == vault.Vault(phrase=self.phrase).generate(bytearray(b'google'))
372 360
 
373 361
     @hypothesis.given(
374 362
         phrase=strategies.text(
... ...
@@ -388,10 +376,10 @@ class TestVault:
388 376
         service: str,
389 377
     ) -> None:
390 378
         """Deriving a passphrase works equally for byte arrays/strings."""
391
-        assert Vault(phrase=phrase).generate(service) == Vault(
379
+        assert vault.Vault(phrase=phrase).generate(service) == vault.Vault(
392 380
             phrase=phrase
393 381
         ).generate(service.encode('utf-8'))
394
-        assert Vault(phrase=phrase).generate(service) == Vault(
382
+        assert vault.Vault(phrase=phrase).generate(service) == vault.Vault(
395 383
             phrase=phrase
396 384
         ).generate(bytearray(service.encode('utf-8')))
397 385
 
... ...
@@ -414,9 +402,9 @@ class TestVault:
414 402
         services: list[bytes],
415 403
     ) -> None:
416 404
         """The derived passphrase is dependent on the service name."""
417
-        assert Vault(phrase=phrase).generate(
418
-            services[0]
419
-        ) != Vault(phrase=phrase).generate(services[1])
405
+        assert vault.Vault(phrase=phrase).generate(services[0]) != vault.Vault(
406
+            phrase=phrase
407
+        ).generate(services[1])
420 408
 
421 409
     @tests.hypothesis_settings_coverage_compatible
422 410
     @hypothesis.given(
... ...
@@ -441,9 +429,9 @@ class TestVault:
441 429
     ) -> None:
442 430
         """The derived passphrase is dependent on the service name."""
443 431
         try:
444
-            assert Vault(phrase=phrase, **config).generate(
432
+            assert vault.Vault(phrase=phrase, **config).generate(
445 433
                 services[0]
446
-            ) != Vault(phrase=phrase, **config).generate(services[1])
434
+            ) != vault.Vault(phrase=phrase, **config).generate(services[1])
447 435
         except ValueError as exc:
448 436
             # The service configuration strategy attempts to only
449 437
             # generate satisfiable configurations.  It is possible,
... ...
@@ -458,7 +446,8 @@ class TestVault:
458 446
     def test_210_nonstandard_length(self) -> None:
459 447
         """Deriving a passphrase adheres to imposed length limits."""
460 448
         assert (
461
-            Vault(phrase=self.phrase, length=4).generate('google') == b'xDFu'
449
+            vault.Vault(phrase=self.phrase, length=4).generate('google')
450
+            == b'xDFu'
462 451
         )
463 452
 
464 453
     @tests.hypothesis_settings_coverage_compatible
... ...
@@ -481,13 +470,13 @@ class TestVault:
481 470
         service: str,
482 471
     ) -> None:
483 472
         """Derived passphrases have the requested length."""
484
-        password = Vault(phrase=phrase, length=length).generate(service)
473
+        password = vault.Vault(phrase=phrase, length=length).generate(service)
485 474
         assert len(password) == length
486 475
 
487 476
     def test_211_repetition_limit(self) -> None:
488 477
         """Deriving a passphrase adheres to imposed repetition limits."""
489 478
         assert (
490
-            Vault(
479
+            vault.Vault(
491 480
                 phrase=b'', length=24, symbol=0, number=0, repeat=1
492 481
             ).generate('asd')
493 482
             == b'IVTDzACftqopUXqDHPkuCIhV'
... ...
@@ -496,14 +485,14 @@ class TestVault:
496 485
     def test_212_without_symbols(self) -> None:
497 486
         """Deriving a passphrase adheres to imposed limits on symbols."""
498 487
         assert (
499
-            Vault(phrase=self.phrase, symbol=0).generate('google')
488
+            vault.Vault(phrase=self.phrase, symbol=0).generate('google')
500 489
             == b'XZ4wRe0bZCazbljCaMqR'
501 490
         )
502 491
 
503 492
     def test_213_no_numbers(self) -> None:
504 493
         """Deriving a passphrase adheres to imposed limits on numbers."""
505 494
         assert (
506
-            Vault(phrase=self.phrase, number=0).generate('google')
495
+            vault.Vault(phrase=self.phrase, number=0).generate('google')
507 496
             == b'_*$TVH.%^aZl(LUeOT?>'
508 497
         )
509 498
 
... ...
@@ -512,28 +501,30 @@ class TestVault:
512 501
         Deriving a passphrase adheres to imposed limits on lowercase letters.
513 502
         """
514 503
         assert (
515
-            Vault(phrase=self.phrase, lower=0).generate('google')
504
+            vault.Vault(phrase=self.phrase, lower=0).generate('google')
516 505
             == b':{?)+7~@OA:L]!0E$)(+'
517 506
         )
518 507
 
519 508
     def test_215_at_least_5_digits(self) -> None:
520 509
         """Deriving a passphrase adheres to imposed counts of numbers."""
521 510
         assert (
522
-            Vault(phrase=self.phrase, length=8, number=5).generate('songkick')
511
+            vault.Vault(phrase=self.phrase, length=8, number=5).generate(
512
+                'songkick'
513
+            )
523 514
             == b'i0908.7['
524 515
         )
525 516
 
526 517
     def test_216_lots_of_spaces(self) -> None:
527 518
         """Deriving a passphrase adheres to imposed counts of spaces."""
528 519
         assert (
529
-            Vault(phrase=self.phrase, space=12).generate('songkick')
520
+            vault.Vault(phrase=self.phrase, space=12).generate('songkick')
530 521
             == b' c   6 Bq  % 5fR    '
531 522
         )
532 523
 
533 524
     def test_217_all_character_classes(self) -> None:
534 525
         """Deriving a passphrase adheres to imposed counts of all types."""
535 526
         assert (
536
-            Vault(
527
+            vault.Vault(
537 528
                 phrase=self.phrase,
538 529
                 lower=2,
539 530
                 upper=2,
... ...
@@ -603,7 +594,7 @@ class TestVault:
603 594
     ) -> None:
604 595
         """Derived passphrases obey character and occurrence restraints."""
605 596
         try:
606
-            password = Vault(phrase=phrase, **config).generate(service)
597
+            password = vault.Vault(phrase=phrase, **config).generate(service)
607 598
         except ValueError as exc:
608 599
             # The service configuration strategy attempts to only
609 600
             # generate satisfiable configurations.  It is possible,
... ...
@@ -619,7 +610,7 @@ class TestVault:
619 610
         for key in ('lower', 'upper', 'number', 'space', 'dash', 'symbol'):
620 611
             if config[key] > 0:
621 612
                 assert (
622
-                    sum(c in Vault._CHARSETS[key] for c in password)
613
+                    sum(c in vault.Vault._CHARSETS[key] for c in password)
623 614
                     >= config[key]
624 615
                 ), (
625 616
                     'Password does not satisfy '
... ...
@@ -630,9 +621,9 @@ class TestVault:
630 621
                 # appear via the other character class.
631 622
                 assert True
632 623
             else:
633
-                assert sum(c in Vault._CHARSETS[key] for c in password) == 0, (
634
-                    'Password does not satisfy character ban constraints.'
635
-                )
624
+                assert (
625
+                    sum(c in vault.Vault._CHARSETS[key] for c in password) == 0
626
+                ), 'Password does not satisfy character ban constraints.'
636 627
 
637 628
         T = TypeVar('T', str, bytes)
638 629
 
... ...
@@ -653,7 +644,7 @@ class TestVault:
653 644
         This example is checked explicitly against forbidden substrings.
654 645
 
655 646
         """
656
-        generated = Vault(
647
+        generated = vault.Vault(
657 648
             phrase=b'',
658 649
             length=40,
659 650
             lower=0,
... ...
@@ -702,22 +693,22 @@ class TestVault:
702 693
         service: str,
703 694
     ) -> None:
704 695
         """Derived passphrases obey the given occurrence constraint."""
705
-        password = Vault(phrase=phrase, length=length, repeat=repeat).generate(
706
-            service
707
-        )
696
+        password = vault.Vault(
697
+            phrase=phrase, length=length, repeat=repeat
698
+        ).generate(service)
708 699
         for i in range((length + 1) - (repeat + 1)):
709 700
             assert len(set(password[i : i + repeat + 1])) > 1
710 701
 
711 702
     def test_219_very_limited_character_set(self) -> None:
712 703
         """Deriving a passphrase works even with limited character sets."""
713
-        generated = Vault(
704
+        generated = vault.Vault(
714 705
             phrase=b'', length=24, lower=0, upper=0, space=0, symbol=0
715 706
         ).generate('testing')
716 707
         assert generated == b'763252593304946694588866'
717 708
 
718 709
     def test_220_character_set_subtraction(self) -> None:
719 710
         """Removing allowed characters internally works."""
720
-        assert Vault._subtract(b'be', b'abcdef') == bytearray(b'acdf')
711
+        assert vault.Vault._subtract(b'be', b'abcdef') == bytearray(b'acdf')
721 712
 
722 713
     @pytest.mark.parametrize(
723 714
         ['length', 'settings', 'entropy'],
... ...
@@ -742,7 +733,7 @@ class TestVault:
742 733
         self, length: int, settings: dict[str, int], entropy: int
743 734
     ) -> None:
744 735
         """Estimating the entropy and sufficient hash length works."""
745
-        v = Vault(length=length, **settings)  # type: ignore[arg-type]
736
+        v = vault.Vault(length=length, **settings)  # type: ignore[arg-type]
746 737
         assert math.isclose(v._entropy(), entropy)
747 738
         assert v._estimate_sufficient_hash_length() > 0
748 739
         if math.isfinite(entropy) and entropy:
... ...
@@ -755,7 +746,7 @@ class TestVault:
755 746
         """
756 747
         Estimating the entropy and hash length for degenerate cases works.
757 748
         """
758
-        v = Vault(
749
+        v = vault.Vault(
759 750
             phrase=self.phrase,
760 751
             lower=0,
761 752
             upper=0,
... ...
@@ -783,7 +774,7 @@ class TestVault:
783 774
         """
784 775
         Estimating the entropy and hash length for the degenerate case works.
785 776
         """
786
-        v = Vault(phrase=self.phrase)
777
+        v = vault.Vault(phrase=self.phrase)
787 778
         monkeypatch.setattr(
788 779
             v,
789 780
             '_estimate_sufficient_hash_length',
... ...
@@ -805,7 +796,7 @@ class TestVault:
805 796
     )
806 797
     def test_224_binary_strings(self, s: str | bytes | bytearray) -> None:
807 798
         """Byte string conversion is idempotent."""
808
-        binstr = Vault._get_binary_string
799
+        binstr = vault.Vault._get_binary_string
809 800
         if isinstance(s, str):
810 801
             assert binstr(s) == s.encode('UTF-8')
811 802
             assert binstr(binstr(s)) == s.encode('UTF-8')
... ...
@@ -818,12 +809,12 @@ class TestVault:
818 809
         with pytest.raises(
819 810
             ValueError, match='requested passphrase length too short'
820 811
         ):
821
-            Vault(phrase=self.phrase, symbol=100)
812
+            vault.Vault(phrase=self.phrase, symbol=100)
822 813
 
823 814
     def test_311_no_viable_characters(self) -> None:
824 815
         """Deriving passphrases without allowed characters fails."""
825 816
         with pytest.raises(ValueError, match='no allowed characters left'):
826
-            Vault(
817
+            vault.Vault(
827 818
                 phrase=self.phrase,
828 819
                 lower=0,
829 820
                 upper=0,
... ...
@@ -836,13 +827,13 @@ class TestVault:
836 827
     def test_320_character_set_subtraction_duplicate(self) -> None:
837 828
         """Character sets do not contain duplicate characters."""
838 829
         with pytest.raises(ValueError, match='duplicate characters'):
839
-            Vault._subtract(b'abcdef', b'aabbccddeeff')
830
+            vault.Vault._subtract(b'abcdef', b'aabbccddeeff')
840 831
         with pytest.raises(ValueError, match='duplicate characters'):
841
-            Vault._subtract(b'aabbccddeeff', b'abcdef')
832
+            vault.Vault._subtract(b'aabbccddeeff', b'abcdef')
842 833
 
843 834
     def test_322_hash_length_estimation(self) -> None:
844 835
         """Hash length estimation rejects invalid safety factors."""
845
-        v = Vault(phrase=self.phrase)
836
+        v = vault.Vault(phrase=self.phrase)
846 837
         with pytest.raises(ValueError, match='invalid safety factor'):
847 838
             assert v._estimate_sufficient_hash_length(-1.0)
848 839
         with pytest.raises(
849 840