Harmonize tests for missing SSH agent communication support
Marco Ricci

Marco Ricci commited on 2026-06-20 13:58:03
Zeige 2 geänderte Dateien mit 125 Einfügungen und 25 Löschungen.


Use a common parameter set and a common monkeypatching setup.  At the
moment, this is done by copy-pasting the parametrization and setup code,
but this could feasibly be abstracted into a common testing function and
common parameter set within the `tests.data` and `tests.data.callables`
modules.
... ...
@@ -13,6 +13,7 @@ subsystems.)
13 13
 from __future__ import annotations
14 14
 
15 15
 import contextlib
16
+import ctypes
16 17
 import json
17 18
 import socket
18 19
 import textwrap
... ...
@@ -22,9 +23,8 @@ from typing import TYPE_CHECKING, ClassVar
22 23
 import pytest
23 24
 
24 25
 from derivepassphrase import _types, cli, ssh_agent
25
-from derivepassphrase._internals import (
26
-    cli_helpers,
27
-)
26
+from derivepassphrase._internals import cli_helpers, cli_messages
27
+from derivepassphrase.ssh_agent import socketprovider
28 28
 from tests import data, machinery
29 29
 from tests.machinery import pytest as pytest_machinery
30 30
 
... ...
@@ -218,6 +218,31 @@ class Parametrize(types.SimpleNamespace):
218 218
             ),
219 219
         ],
220 220
     )
221
+    MISSING_AGENT_SUPPORT = pytest.mark.parametrize(
222
+        ["provider", "exception", "message"],
223
+        [
224
+            pytest.param(
225
+                _types.BuiltinSSHAgentSocketProvider.SSH_AUTH_SOCK_ON_POSIX,
226
+                socketprovider.UnixDomainSocketsNotAvailableError,
227
+                str(
228
+                    cli_messages.TranslatedString(
229
+                        cli_messages.WarnMsgTemplate.NO_UNIX_DOMAIN_SOCKETS
230
+                    )
231
+                ),
232
+                id="unix_domain_sockets",
233
+            ),
234
+            pytest.param(
235
+                _types.BuiltinSSHAgentSocketProvider.WINDOWS_NAMED_PIPE,
236
+                socketprovider.WindowsNamedPipesNotAvailableError,
237
+                str(
238
+                    cli_messages.TranslatedString(
239
+                        cli_messages.WarnMsgTemplate.NO_WINDOWS_NAMED_PIPES
240
+                    )
241
+                ),
242
+                id="windows_named_pipes",
243
+            ),
244
+        ],
245
+    )
221 246
 
222 247
 
223 248
 class TestPassphraseUnicodeNormalization:
... ...
@@ -364,11 +389,37 @@ class TestUserConfigurationFileOther:
364 389
 class TestSSHAgentAvailability:
365 390
     """Tests concerning the availability of the SSH agent."""
366 391
 
367
-    def test_missing_af_unix_support(
392
+    def _unsupport_provider_and_force_use(
393
+        self,
394
+        provider: _types.BuiltinSSHAgentSocketProvider,
395
+        monkeypatch: pytest.MonkeyPatch,
396
+    ) -> None:
397
+        """Remove support for the named socket provider."""
398
+        if (
399
+            provider
400
+            == _types.BuiltinSSHAgentSocketProvider.SSH_AUTH_SOCK_ON_POSIX
401
+        ):
402
+            monkeypatch.delattr(socket, "AF_UNIX", raising=False)
403
+        elif (
404
+            provider == _types.BuiltinSSHAgentSocketProvider.WINDOWS_NAMED_PIPE
405
+        ):
406
+            monkeypatch.delattr(ctypes, "WinDLL", raising=False)
407
+        else:
408
+            pytest.fail(f"Don't know how to remove support for {provider!r}.")
409
+        monkeypatch.setattr(
410
+            ssh_agent.SSHAgentClient, "SOCKET_PROVIDERS", [provider]
411
+        )
412
+
413
+    @Parametrize.MISSING_AGENT_SUPPORT
414
+    def test_missing_agent_support(
368 415
         self,
369 416
         caplog: pytest.LogCaptureFixture,
417
+        provider: _types.BuiltinSSHAgentSocketProvider,
418
+        exception: type[NotImplementedError],
419
+        message: str,
370 420
     ) -> None:
371
-        """Querying the SSH agent without `AF_UNIX` support fails."""
421
+        """Querying the SSH agent without support fails."""
422
+        del exception
372 423
         runner = machinery.CliRunner(mix_stderr=False)
373 424
         # TODO(the-13th-letter): Rewrite using parenthesized
374 425
         # with-statements.
... ...
@@ -379,16 +430,13 @@ class TestSSHAgentAvailability:
379 430
                 pytest_machinery.isolated_vault_config(
380 431
                     monkeypatch=monkeypatch,
381 432
                     runner=runner,
382
-                    vault_config={"global": {"phrase": "abc"}, "services": {}},
433
+                    vault_config={"services": {}},
383 434
                 )
384 435
             )
385 436
             monkeypatch.setenv(
386 437
                 "SSH_AUTH_SOCK", "the value doesn't even matter"
387 438
             )
388
-            monkeypatch.setattr(
389
-                ssh_agent.SSHAgentClient, "SOCKET_PROVIDERS", ["posix"]
390
-            )
391
-            monkeypatch.delattr(socket, "AF_UNIX", raising=False)
439
+            self._unsupport_provider_and_force_use(provider, monkeypatch)
392 440
             result = runner.invoke(
393 441
                 cli.derivepassphrase_vault,
394 442
                 ["--key", "--config"],
... ...
@@ -397,7 +445,6 @@ class TestSSHAgentAvailability:
397 445
         assert result.error_exit(
398 446
             error="does not support communicating with it"
399 447
         ), "expected error exit and known error message"
400
-        assert machinery.warning_emitted(
401
-            "Cannot connect to an SSH agent via UNIX domain sockets",
402
-            caplog.record_tuples,
403
-        ), "expected known warning message in stderr"
448
+        assert machinery.warning_emitted(message, caplog.record_tuples), (
449
+            "expected known warning message in stderr"
450
+        )
... ...
@@ -8,6 +8,7 @@ from __future__ import annotations
8 8
 
9 9
 import base64
10 10
 import contextlib
11
+import ctypes
11 12
 import importlib.metadata
12 13
 import io
13 14
 import re
... ...
@@ -23,7 +24,7 @@ from hypothesis import strategies
23 24
 from typing_extensions import TypeAlias
24 25
 
25 26
 from derivepassphrase import _types, ssh_agent, vault
26
-from derivepassphrase._internals import cli_helpers
27
+from derivepassphrase._internals import cli_helpers, cli_messages
27 28
 from derivepassphrase.ssh_agent import socketprovider
28 29
 from tests import data, machinery
29 30
 from tests.data import callables
... ...
@@ -348,6 +349,31 @@ class Parametrize(types.SimpleNamespace):
348 349
             ),
349 350
         ],
350 351
     )
352
+    MISSING_AGENT_SUPPORT = pytest.mark.parametrize(
353
+        ["provider", "exception", "message"],
354
+        [
355
+            pytest.param(
356
+                _types.BuiltinSSHAgentSocketProvider.SSH_AUTH_SOCK_ON_POSIX,
357
+                socketprovider.UnixDomainSocketsNotAvailableError,
358
+                str(
359
+                    cli_messages.TranslatedString(
360
+                        cli_messages.WarnMsgTemplate.NO_UNIX_DOMAIN_SOCKETS
361
+                    )
362
+                ),
363
+                id="unix_domain_sockets",
364
+            ),
365
+            pytest.param(
366
+                _types.BuiltinSSHAgentSocketProvider.WINDOWS_NAMED_PIPE,
367
+                socketprovider.WindowsNamedPipesNotAvailableError,
368
+                str(
369
+                    cli_messages.TranslatedString(
370
+                        cli_messages.WarnMsgTemplate.NO_WINDOWS_NAMED_PIPES
371
+                    )
372
+                ),
373
+                id="windows_named_pipes",
374
+            ),
375
+        ],
376
+    )
351 377
     PUBLIC_KEY_DATA = pytest.mark.parametrize(
352 378
         "public_key_struct",
353 379
         list(data.SUPPORTED_KEYS.values()),
... ...
@@ -1142,6 +1168,27 @@ class TestSuitableKeys:
1142 1168
 class TestConstructorFailures:
1143 1169
     """Test actually talking to the SSH agent: constructor failures."""
1144 1170
 
1171
+    def _unsupport_provider_and_force_use(
1172
+        self,
1173
+        provider: _types.BuiltinSSHAgentSocketProvider,
1174
+        monkeypatch: pytest.MonkeyPatch,
1175
+    ) -> None:
1176
+        """Remove support for the named socket provider."""
1177
+        if (
1178
+            provider
1179
+            == _types.BuiltinSSHAgentSocketProvider.SSH_AUTH_SOCK_ON_POSIX
1180
+        ):
1181
+            monkeypatch.delattr(socket, "AF_UNIX", raising=False)
1182
+        elif (
1183
+            provider == _types.BuiltinSSHAgentSocketProvider.WINDOWS_NAMED_PIPE
1184
+        ):
1185
+            monkeypatch.delattr(ctypes, "WinDLL", raising=False)
1186
+        else:
1187
+            pytest.fail(f"Don't know how to remove support for {provider!r}.")
1188
+        monkeypatch.setattr(
1189
+            ssh_agent.SSHAgentClient, "SOCKET_PROVIDERS", [provider]
1190
+        )
1191
+
1145 1192
     def test_constructor_bad_running_agent(
1146 1193
         self,
1147 1194
         running_ssh_agent: data.RunningSSHAgentInfo,
... ...
@@ -1157,17 +1204,23 @@ class TestConstructorFailures:
1157 1204
             with pytest.raises(OSError):  # noqa: PT011
1158 1205
                 ssh_agent.SSHAgentClient()
1159 1206
 
1160
-    def test_constructor_no_af_unix_support(self) -> None:
1161
-        """Fail without [`socket.AF_UNIX`][] support."""
1162
-        assert "posix" in socketprovider.SocketProvider.registry
1207
+    @Parametrize.MISSING_AGENT_SUPPORT
1208
+    def test_constructor_no_support(
1209
+        self,
1210
+        provider: _types.BuiltinSSHAgentSocketProvider,
1211
+        exception: type[NotImplementedError],
1212
+        message: str,
1213
+    ) -> None:
1214
+        """Fail without support for the communication technology."""
1215
+        del message
1216
+        assert provider in socketprovider.SocketProvider.registry
1163 1217
         with pytest.MonkeyPatch.context() as monkeypatch:
1164
-            monkeypatch.setenv("SSH_AUTH_SOCK", "the value doesn't matter")
1165
-            monkeypatch.delattr(socket, "AF_UNIX", raising=False)
1166
-            with pytest.raises(
1167
-                NotImplementedError,
1168
-                match="UNIX domain sockets",
1169
-            ):
1170
-                ssh_agent.SSHAgentClient(socket="posix")
1218
+            monkeypatch.setenv(
1219
+                "SSH_AUTH_SOCK", "the value doesn't even matter"
1220
+            )
1221
+            self._unsupport_provider_and_force_use(provider, monkeypatch)
1222
+            with pytest.raises(exception):
1223
+                ssh_agent.SSHAgentClient(socket=provider)
1171 1224
 
1172 1225
     def test_no_ssh_agent_socket_provider_available(
1173 1226
         self,
1174 1227