test: add edge case tests for TLS certificate generation and SSL serve behavior
This commit is contained in:
@@ -1634,3 +1634,36 @@ def test_doctor_shows_tls_clipboard_warning(tmp_path, monkeypatch, capsys):
|
|||||||
assert "clipboard" in out_lower or "https" in out_lower, (
|
assert "clipboard" in out_lower or "https" in out_lower, (
|
||||||
f"Expected 'clipboard' or 'https' in doctor TLS-disabled output, got: {out!r}"
|
f"Expected 'clipboard' or 'https' in doctor TLS-disabled output, got: {out!r}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# task-7: Edge case tests for serve() TLS behavior
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_serve_warns_when_only_cert_set(tmp_path, monkeypatch, capsys):
|
||||||
|
"""serve() must NOT enable SSL when tls_cert is set but tls_key is empty string."""
|
||||||
|
import muxplex.cli as cli_mod
|
||||||
|
|
||||||
|
# Create a real cert file so tls_cert path check passes the "file exists" guard
|
||||||
|
cert_file = tmp_path / "server.crt"
|
||||||
|
cert_file.write_text("fake cert content")
|
||||||
|
|
||||||
|
settings_file = tmp_path / "settings.json"
|
||||||
|
monkeypatch.setattr("muxplex.settings.SETTINGS_PATH", settings_file)
|
||||||
|
|
||||||
|
uvicorn_calls = []
|
||||||
|
|
||||||
|
def fake_run(*args, **kwargs):
|
||||||
|
uvicorn_calls.append(kwargs)
|
||||||
|
|
||||||
|
with patch("uvicorn.run", fake_run):
|
||||||
|
with patch.dict("sys.modules", {"muxplex.main": MagicMock()}):
|
||||||
|
cli_mod.serve(tls_cert=str(cert_file), tls_key="")
|
||||||
|
|
||||||
|
assert len(uvicorn_calls) == 1
|
||||||
|
kwargs = uvicorn_calls[0]
|
||||||
|
assert "ssl_certfile" not in kwargs, (
|
||||||
|
"serve() must NOT pass ssl_certfile to uvicorn when tls_key is empty string — "
|
||||||
|
"SSL requires both cert and key"
|
||||||
|
)
|
||||||
|
|||||||
@@ -156,3 +156,59 @@ def test_get_cert_info_returns_none_for_missing_file(tmp_path):
|
|||||||
assert result is None, (
|
assert result is None, (
|
||||||
f"get_cert_info() must return None for missing file, got: {result!r}"
|
f"get_cert_info() must return None for missing file, got: {result!r}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 10–12. Edge case tests
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_self_signed_with_custom_hostnames(tmp_path):
|
||||||
|
"""generate_self_signed() with custom hostnames must include all of them in result['hostnames']."""
|
||||||
|
from muxplex.tls import generate_self_signed
|
||||||
|
|
||||||
|
cert_path = tmp_path / "cert.pem"
|
||||||
|
key_path = tmp_path / "key.pem"
|
||||||
|
custom_hostnames = ["mybox.local", "mybox.tailnet.ts.net"]
|
||||||
|
|
||||||
|
result = generate_self_signed(cert_path, key_path, hostnames=custom_hostnames)
|
||||||
|
|
||||||
|
assert isinstance(result, dict), "generate_self_signed() must return a dict"
|
||||||
|
assert isinstance(result.get("hostnames"), list), "result['hostnames'] must be a list"
|
||||||
|
assert "mybox.local" in result["hostnames"], (
|
||||||
|
f"'mybox.local' must be in result['hostnames'], got: {result['hostnames']!r}"
|
||||||
|
)
|
||||||
|
assert "mybox.tailnet.ts.net" in result["hostnames"], (
|
||||||
|
f"'mybox.tailnet.ts.net' must be in result['hostnames'], got: {result['hostnames']!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_cert_info_hostnames_include_ip(tmp_path):
|
||||||
|
"""get_cert_info() must include '127.0.0.1' in hostnames (from IP SANs added by generate_self_signed)."""
|
||||||
|
from muxplex.tls import generate_self_signed, get_cert_info
|
||||||
|
|
||||||
|
cert_path = tmp_path / "cert.pem"
|
||||||
|
key_path = tmp_path / "key.pem"
|
||||||
|
generate_self_signed(cert_path, key_path, hostnames=["localhost"])
|
||||||
|
|
||||||
|
info = get_cert_info(cert_path)
|
||||||
|
|
||||||
|
assert info is not None, "get_cert_info() must not return None for a valid cert"
|
||||||
|
assert "hostnames" in info, "get_cert_info() result must have 'hostnames' key"
|
||||||
|
assert "127.0.0.1" in info["hostnames"], (
|
||||||
|
f"'127.0.0.1' must be in info['hostnames'] (IP SANs), got: {info['hostnames']!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_cert_info_returns_none_for_corrupt_file(tmp_path):
|
||||||
|
"""get_cert_info() must return None for a file containing invalid PEM data."""
|
||||||
|
from muxplex.tls import get_cert_info
|
||||||
|
|
||||||
|
corrupt_pem = tmp_path / "corrupt.pem"
|
||||||
|
corrupt_pem.write_text("THIS IS NOT A CERTIFICATE")
|
||||||
|
|
||||||
|
result = get_cert_info(corrupt_pem)
|
||||||
|
|
||||||
|
assert result is None, (
|
||||||
|
f"get_cert_info() must return None for corrupt PEM file, got: {result!r}"
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user