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 |