diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 93f10a1950..fcc366a045 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -489,6 +489,18 @@ def version( info_table.add_row("Platform", platform.system()) info_table.add_row("Architecture", platform.machine()) info_table.add_row("OS Version", platform.version()) + # The OpenSSL runtime the interpreter actually loaded. HTTPS failure + # reports (#4433) hinge on which OpenSSL is in play, and on Windows it is + # not obvious from the outside, so surface it here. An interpreter built + # without the ssl extension skips the row rather than failing the command. + try: + import ssl + + openssl_version = getattr(ssl, "OPENSSL_VERSION", "") + except ImportError: + openssl_version = "" + if openssl_version: + info_table.add_row("OpenSSL", openssl_version) panel = Panel( info_table, diff --git a/tests/test_cli_version.py b/tests/test_cli_version.py index 041ff62e55..ae64fa53bc 100644 --- a/tests/test_cli_version.py +++ b/tests/test_cli_version.py @@ -1,6 +1,8 @@ """Tests for CLI version reporting.""" import json +import ssl +import sys from unittest.mock import patch from typer.testing import CliRunner @@ -77,3 +79,43 @@ def test_version_json_requires_features(self): assert result.exit_code != 0 assert "--json requires --features" in result.output + + def test_version_reports_openssl_runtime(self): + """specify version reports the OpenSSL runtime the interpreter loaded. + + Regression test for the triage gap in #4433: HTTPS failures on Windows + are commonly blamed on a PATH-preceded OpenSSL DLL, but ``specify + version`` reported no OpenSSL information at all, so a report had no way + to show which runtime was actually in use. + """ + with patch("specify_cli.get_speckit_version", return_value="1.2.3"): + result = runner.invoke(app, ["version"]) + + assert result.exit_code == 0 + + expected = ssl.OPENSSL_VERSION + assert expected, "test host reports no ssl.OPENSSL_VERSION to assert against" + assert expected in result.output + + def test_version_skips_openssl_row_when_ssl_unavailable(self, monkeypatch): + """An interpreter built without the ssl extension skips the OpenSSL row. + + ``sys.modules["ssl"] = None`` makes ``import ssl`` raise ImportError, + simulating a build without ``_ssl``. The command must still succeed — + only the OpenSSL row is omitted. + """ + monkeypatch.setitem(sys.modules, "ssl", None) + with patch("specify_cli.get_speckit_version", return_value="1.2.3"): + result = runner.invoke(app, ["version"]) + + assert result.exit_code == 0 + assert "OpenSSL" not in result.output + + def test_version_features_never_touches_ssl(self, monkeypatch): + """--features/--json return early and must not require ssl at all.""" + monkeypatch.setitem(sys.modules, "ssl", None) + with patch("specify_cli.get_speckit_version", return_value="1.2.3"): + result = runner.invoke(app, ["version", "--features", "--json"]) + + assert result.exit_code == 0 + assert json.loads(result.output)["version"] == "1.2.3"