Add hypothesis tests for the `exporter` module
Marco Ricci

Marco Ricci commited on 2025-01-25 23:29:34
Zeige 1 geänderte Dateien mit 173 Einfügungen und 36 Löschungen.


Add hypothesis tests for vault key discovery and for registring vault
configuration data export handlers, each via their own parameter object
and corresponding hypothesis strategy, and convert all existing explicit
parametrized tests to hypothesis examples.
... ...
@@ -4,12 +4,17 @@
4 4
 
5 5
 from __future__ import annotations
6 6
 
7
+import contextlib
8
+import operator
7 9
 import os
8 10
 import pathlib
9
-from typing import TYPE_CHECKING, Any
11
+import string
12
+from typing import TYPE_CHECKING, Any, NamedTuple
10 13
 
11 14
 import click.testing
15
+import hypothesis
12 16
 import pytest
17
+from hypothesis import strategies
13 18
 
14 19
 import tests
15 20
 from derivepassphrase import cli, exporter
... ...
@@ -21,33 +26,143 @@ if TYPE_CHECKING:
21 26
 class Test001ExporterUtils:
22 27
     """Test the utility functions in the `exporter` subpackage."""
23 28
 
24
-    @pytest.mark.parametrize(
25
-        ['expected', 'vault_key', 'logname', 'user', 'username'],
26
-        [
27
-            ('4username', None, None, None, '4username'),
28
-            ('3user', None, None, '3user', None),
29
-            ('3user', None, None, '3user', '4username'),
30
-            ('2logname', None, '2logname', None, None),
31
-            ('2logname', None, '2logname', None, '4username'),
32
-            ('2logname', None, '2logname', '3user', None),
33
-            ('2logname', None, '2logname', '3user', '4username'),
34
-            ('1vault_key', '1vault_key', None, None, None),
35
-            ('1vault_key', '1vault_key', None, None, '4username'),
36
-            ('1vault_key', '1vault_key', None, '3user', None),
37
-            ('1vault_key', '1vault_key', None, '3user', '4username'),
38
-            ('1vault_key', '1vault_key', '2logname', None, None),
39
-            ('1vault_key', '1vault_key', '2logname', None, '4username'),
40
-            ('1vault_key', '1vault_key', '2logname', '3user', None),
41
-            ('1vault_key', '1vault_key', '2logname', '3user', '4username'),
42
-        ],
29
+    class VaultKeyEnvironment(NamedTuple):
30
+        """An environment configuration for vault key determination.
31
+
32
+        Attributes:
33
+            expected:
34
+                The correct vault key value.
35
+            vault_key:
36
+                The value for the `VAULT_KEY` environment variable.
37
+            logname:
38
+                The value for the `LOGNAME` environment variable.
39
+            user:
40
+                The value for the `USER` environment variable.
41
+            username:
42
+                The value for the `USERNAME` environment variable.
43
+
44
+        """
45
+
46
+        expected: str | None
47
+        """"""
48
+        vault_key: str | None
49
+        """"""
50
+        logname: str | None
51
+        """"""
52
+        user: str | None
53
+        """"""
54
+        username: str | None
55
+        """"""
56
+
57
+        @strategies.composite
58
+        @staticmethod
59
+        def strategy(
60
+            draw: strategies.DrawFn,
61
+            allow_missing: bool = False,
62
+        ) -> Test001ExporterUtils.VaultKeyEnvironment:
63
+            """Return a vault key environment configuration."""
64
+            text_strategy = strategies.text(
65
+                strategies.characters(min_codepoint=32, max_codepoint=127),
66
+                min_size=1,
67
+                max_size=24,
68
+            )
69
+            env_var_strategy = strategies.one_of(
70
+                strategies.none(),
71
+                text_strategy,
72
+            )
73
+            num_fields = sum(
74
+                1
75
+                for f in Test001ExporterUtils.VaultKeyEnvironment._fields
76
+                if f != 'expected'
77
+            )
78
+            env_vars: list[str | None] = draw(
79
+                strategies.builds(
80
+                    operator.add,
81
+                    strategies.lists(
82
+                        env_var_strategy,
83
+                        min_size=num_fields - 1,
84
+                        max_size=num_fields - 1,
85
+                    ),
86
+                    strategies.lists(
87
+                        text_strategy
88
+                        if not allow_missing
89
+                        else env_var_strategy,
90
+                        min_size=1,
91
+                        max_size=1,
92
+                    ),
93
+                )
94
+            )
95
+            expected: str | None = None
96
+            for value in reversed(env_vars):
97
+                if value is not None:
98
+                    expected = value
99
+            return Test001ExporterUtils.VaultKeyEnvironment(
100
+                expected, *env_vars
101
+            )
102
+
103
+    @hypothesis.example(
104
+        VaultKeyEnvironment('4username', None, None, None, '4username')
105
+    ).via('manual, pre-hypothesis parametrization value')
106
+    @hypothesis.example(
107
+        VaultKeyEnvironment('3user', None, None, '3user', None)
108
+    ).via('manual, pre-hypothesis parametrization value')
109
+    @hypothesis.example(
110
+        VaultKeyEnvironment('3user', None, None, '3user', '4username')
111
+    ).via('manual, pre-hypothesis parametrization value')
112
+    @hypothesis.example(
113
+        VaultKeyEnvironment('2logname', None, '2logname', None, None)
114
+    ).via('manual, pre-hypothesis parametrization value')
115
+    @hypothesis.example(
116
+        VaultKeyEnvironment('2logname', None, '2logname', None, '4username')
117
+    ).via('manual, pre-hypothesis parametrization value')
118
+    @hypothesis.example(
119
+        VaultKeyEnvironment('2logname', None, '2logname', '3user', None)
120
+    ).via('manual, pre-hypothesis parametrization value')
121
+    @hypothesis.example(
122
+        VaultKeyEnvironment('2logname', None, '2logname', '3user', '4username')
123
+    ).via('manual, pre-hypothesis parametrization value')
124
+    @hypothesis.example(
125
+        VaultKeyEnvironment('1vault_key', '1vault_key', None, None, None)
126
+    ).via('manual, pre-hypothesis parametrization value')
127
+    @hypothesis.example(
128
+        VaultKeyEnvironment(
129
+            '1vault_key', '1vault_key', None, None, '4username'
130
+        )
131
+    ).via('manual, pre-hypothesis parametrization value')
132
+    @hypothesis.example(
133
+        VaultKeyEnvironment('1vault_key', '1vault_key', None, '3user', None)
134
+    ).via('manual, pre-hypothesis parametrization value')
135
+    @hypothesis.example(
136
+        VaultKeyEnvironment(
137
+            '1vault_key', '1vault_key', None, '3user', '4username'
138
+        )
139
+    ).via('manual, pre-hypothesis parametrization value')
140
+    @hypothesis.example(
141
+        VaultKeyEnvironment('1vault_key', '1vault_key', '2logname', None, None)
142
+    ).via('manual, pre-hypothesis parametrization value')
143
+    @hypothesis.example(
144
+        VaultKeyEnvironment(
145
+            '1vault_key', '1vault_key', '2logname', None, '4username'
146
+        )
147
+    ).via('manual, pre-hypothesis parametrization value')
148
+    @hypothesis.example(
149
+        VaultKeyEnvironment(
150
+            '1vault_key', '1vault_key', '2logname', '3user', None
151
+        )
152
+    ).via('manual, pre-hypothesis parametrization value')
153
+    @hypothesis.example(
154
+        VaultKeyEnvironment(
155
+            '1vault_key', '1vault_key', '2logname', '3user', '4username'
156
+        )
157
+    ).via('manual, pre-hypothesis parametrization value')
158
+    @hypothesis.given(
159
+        vault_key_env=VaultKeyEnvironment.strategy().filter(
160
+            lambda env: bool(env.expected)
161
+        ),
43 162
     )
44 163
     def test_200_get_vault_key(
45 164
         self,
46
-        expected: str,
47
-        vault_key: str | None,
48
-        logname: str | None,
49
-        user: str | None,
50
-        username: str | None,
165
+        vault_key_env: VaultKeyEnvironment,
51 166
     ) -> None:
52 167
         """Look up the vault key in `VAULT_KEY`/`LOGNAME`/`USER`/`USERNAME`.
53 168
 
... ...
@@ -55,6 +170,8 @@ class Test001ExporterUtils:
55 170
         their relative priorities.
56 171
 
57 172
         """
173
+        expected, vault_key, logname, user, username = vault_key_env
174
+        assert expected is not None
58 175
         priority_list = [
59 176
             ('VAULT_KEY', vault_key),
60 177
             ('LOGNAME', logname),
... ...
@@ -115,7 +232,32 @@ class Test001ExporterUtils:
115 232
                 == expected.expanduser().resolve()
116 233
             )
117 234
 
118
-    def test_220_register_export_vault_config_data_handler(self) -> None:
235
+    @hypothesis.given(
236
+        name_data=strategies.lists(
237
+            strategies.integers(min_value=1, max_value=3),
238
+            min_size=2,
239
+            max_size=2,
240
+        ).flatmap(
241
+            lambda nm: strategies.lists(
242
+                strategies.builds(
243
+                    operator.add,
244
+                    strategies.sampled_from(string.ascii_letters),
245
+                    strategies.text(
246
+                        string.ascii_letters + string.digits + '_-',
247
+                        max_size=23,
248
+                    ),
249
+                ),
250
+                min_size=sum(nm),
251
+                max_size=sum(nm),
252
+                unique=True,
253
+            ).flatmap(
254
+                lambda list_: strategies.just((list_[nm[0] :], list_[: nm[0]]))
255
+            )
256
+        ),
257
+    )
258
+    def test_220_register_export_vault_config_data_handler(
259
+        self, name_data: tuple[list[str], list[str]]
260
+    ) -> None:
119 261
         """Register vault config data export handlers."""
120 262
 
121 263
         def handler(  # pragma: no cover
... ...
@@ -127,21 +269,16 @@ class Test001ExporterUtils:
127 269
             del path, key
128 270
             raise ValueError(format)
129 271
 
272
+        names1, names2 = name_data
273
+
130 274
         with pytest.MonkeyPatch.context() as monkeypatch:
131
-            registry = {'dummy': handler}
275
+            registry = dict.fromkeys(names1, handler)
132 276
             monkeypatch.setattr(
133 277
                 exporter, '_export_vault_config_data_registry', registry
134 278
             )
135
-            dec = exporter.register_export_vault_config_data_handler(
136
-                'name1',
137
-                'name2',
138
-            )
279
+            dec = exporter.register_export_vault_config_data_handler(*names2)
139 280
             assert dec(handler) == handler
140
-            assert registry == {
141
-                'dummy': handler,
142
-                'name1': handler,
143
-                'name2': handler,
144
-            }
281
+            assert registry == dict.fromkeys(names1 + names2, handler)
145 282
 
146 283
     def test_300_get_vault_key_without_envs(self) -> None:
147 284
         """Fail to look up the vault key in the empty environment."""
148 285