import http.client
import types
from unittest.mock import MagicMock
import pytest
from ttk.remote.tls import build_tls_connection, tls_from_config
def test_build_https_when_cert_and_key(monkeypatch):
fake = MagicMock()
monkeypatch.setattr("ssl.SSLContext", lambda *a, **k: fake)
conn = build_tls_connection("x", 9090, 5, {"ca_cert": "ca", "cert": "c", "key": "k"})
assert isinstance(conn, http.client.HTTPSConnection)
fake.load_verify_locations.assert_called_once_with("ca")
fake.load_cert_chain.assert_called_once_with("c", "k")
def test_build_https_when_ca_only(monkeypatch):
fake = MagicMock()
monkeypatch.setattr("ssl.SSLContext", lambda *a, **k: fake)
conn = build_tls_connection("x", 9090, 5, {"ca_cert": "ca"})
assert isinstance(conn, http.client.HTTPSConnection)
fake.load_verify_locations.assert_called_once_with("ca")
fake.load_cert_chain.assert_not_called()
def test_build_http_when_cert_without_key():
assert isinstance(
build_tls_connection("x", 9090, 5, {"cert": "c"}),
http.client.HTTPConnection,
)
def test_build_http_when_no_tls():
assert isinstance(build_tls_connection("x", 9090, 5, None), http.client.HTTPConnection)
assert isinstance(build_tls_connection("x", 9090, 5, {}), http.client.HTTPConnection)
def _cfg(**kw):
return types.SimpleNamespace(
tls_ca=kw.get("ca", ""),
tls_cert=kw.get("cert", ""),
tls_key=kw.get("key", ""),
tls_skip_verify=kw.get("skip_verify", False),
)
def test_tls_from_config_raises_on_cert_without_key():
with pytest.raises(RuntimeError, match="必须成对"):
tls_from_config(_cfg(cert="c"))
def test_tls_from_config_raises_on_key_without_cert():
with pytest.raises(RuntimeError, match="必须成对"):
tls_from_config(_cfg(key="k"))
def test_tls_from_config_empty_when_no_tls():
assert tls_from_config(_cfg()) == {}
def test_tls_from_config_full():
assert tls_from_config(_cfg(ca="ca", cert="c", key="k")) == \
{"ca_cert": "ca", "cert": "c", "key": "k", "skip_verify": False}
def test_tls_from_config_skip_verify_passthrough():
"""tls_skip_verify=True 透传到 tls dict(client 关 server hostname 校验)。"""
assert tls_from_config(_cfg(ca="ca", skip_verify=True))["skip_verify"] is True
def test_build_tls_skip_verify_disables_hostname(monkeypatch):
"""skip_verify=True → ctx.check_hostname=False(关 server hostname 校验, CA 验证仍开)。"""
import ssl
monkeypatch.setattr("ssl.SSLContext.load_verify_locations", lambda self, *a: None)
fake = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
monkeypatch.setattr("ssl.SSLContext", lambda *a, **k: fake)
build_tls_connection("x", 9090, 5, {"ca_cert": "ca", "skip_verify": True})
assert fake.check_hostname is False
def test_build_tls_default_keeps_hostname(monkeypatch):
"""默认(无 skip_verify)保持 check_hostname=True(严格)。"""
import ssl
monkeypatch.setattr("ssl.SSLContext.load_verify_locations", lambda self, *a: None)
fake = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
monkeypatch.setattr("ssl.SSLContext", lambda *a, **k: fake)
build_tls_connection("x", 9090, 5, {"ca_cert": "ca"})
assert fake.check_hostname is True