feat: add TLS status section to doctor() command output
- Inserts TLS check between Serve config and Auth status sections - Shows enabled status with cert expiry date when valid certs configured - Shows warning with days-expired count when cert is expired - Shows configured-but-not-readable warning when cert file unreadable - Shows disabled warning mentioning clipboard/HTTPS requirement when no TLS - Adds 3 new tests: tls_disabled, tls_enabled, tls_clipboard_warning - All 86 CLI tests pass Co-authored-by: Amplifier <amplifier@amplified.dev>
This commit is contained in:
@@ -342,6 +342,37 @@ def doctor() -> None:
|
|||||||
f" (auth={cfg['auth']}, ttl={cfg['session_ttl']}s)"
|
f" (auth={cfg['auth']}, ttl={cfg['session_ttl']}s)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# TLS status
|
||||||
|
tls_cert = cfg.get("tls_cert", "")
|
||||||
|
tls_key = cfg.get("tls_key", "")
|
||||||
|
if tls_cert and tls_key:
|
||||||
|
from datetime import datetime, timezone # noqa: PLC0415
|
||||||
|
|
||||||
|
from muxplex.tls import get_cert_info # noqa: PLC0415
|
||||||
|
|
||||||
|
cert_info = get_cert_info(tls_cert)
|
||||||
|
if cert_info is not None:
|
||||||
|
expires = cert_info["expires"]
|
||||||
|
# Ensure timezone-aware for comparison
|
||||||
|
if expires.tzinfo is None:
|
||||||
|
expires = expires.replace(tzinfo=timezone.utc)
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
if expires < now:
|
||||||
|
days_ago = (now - expires).days
|
||||||
|
print(
|
||||||
|
f" {warn_mark} TLS: WARNING \u2014 cert expired {days_ago} days ago."
|
||||||
|
" Run muxplex setup-tls to renew"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
expiry_str = expires.strftime("%Y-%m-%d")
|
||||||
|
print(f" {ok_mark} TLS: enabled (cert expires {expiry_str})")
|
||||||
|
else:
|
||||||
|
print(f" {warn_mark} TLS: configured but cert not readable ({tls_cert})")
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
f" {warn_mark} TLS: disabled (clipboard requires HTTPS on non-localhost)"
|
||||||
|
)
|
||||||
|
|
||||||
# Auth status
|
# Auth status
|
||||||
pw_path = get_password_path()
|
pw_path = get_password_path()
|
||||||
if pam_available():
|
if pam_available():
|
||||||
|
|||||||
@@ -1561,3 +1561,76 @@ def test_serve_subcommand_accepts_tls_flags():
|
|||||||
tls_cert="/path/cert.pem",
|
tls_cert="/path/cert.pem",
|
||||||
tls_key="/path/key.pem",
|
tls_key="/path/key.pem",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# task-6-doctor-tls: TLS status section in doctor()
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_doctor_shows_tls_disabled(tmp_path, monkeypatch, capsys):
|
||||||
|
"""doctor() shows TLS disabled when no TLS configured."""
|
||||||
|
import muxplex.settings as settings_mod
|
||||||
|
|
||||||
|
settings_file = tmp_path / "settings.json"
|
||||||
|
# No tls_cert/tls_key — just use empty settings
|
||||||
|
settings_file.write_text("{}")
|
||||||
|
monkeypatch.setattr(settings_mod, "SETTINGS_PATH", settings_file)
|
||||||
|
|
||||||
|
from muxplex.cli import doctor
|
||||||
|
|
||||||
|
doctor()
|
||||||
|
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
out_lower = out.lower()
|
||||||
|
assert "tls" in out_lower, f"Expected 'tls' in doctor output, got: {out!r}"
|
||||||
|
assert "disabled" in out_lower, (
|
||||||
|
f"Expected 'disabled' in doctor output, got: {out!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_doctor_shows_tls_enabled(tmp_path, monkeypatch, capsys):
|
||||||
|
"""doctor() shows TLS enabled when valid certs are configured."""
|
||||||
|
import json
|
||||||
|
|
||||||
|
import muxplex.settings as settings_mod
|
||||||
|
from muxplex.tls import generate_self_signed
|
||||||
|
|
||||||
|
# Generate real self-signed certs in tmp_path
|
||||||
|
cert_path = tmp_path / "muxplex.crt"
|
||||||
|
key_path = tmp_path / "muxplex.key"
|
||||||
|
generate_self_signed(cert_path, key_path)
|
||||||
|
|
||||||
|
settings_file = tmp_path / "settings.json"
|
||||||
|
settings_file.write_text(
|
||||||
|
json.dumps({"tls_cert": str(cert_path), "tls_key": str(key_path)})
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(settings_mod, "SETTINGS_PATH", settings_file)
|
||||||
|
|
||||||
|
from muxplex.cli import doctor
|
||||||
|
|
||||||
|
doctor()
|
||||||
|
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
out_lower = out.lower()
|
||||||
|
assert "tls" in out_lower, f"Expected 'tls' in doctor output, got: {out!r}"
|
||||||
|
assert "enabled" in out_lower, f"Expected 'enabled' in doctor output, got: {out!r}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_doctor_shows_tls_clipboard_warning(tmp_path, monkeypatch, capsys):
|
||||||
|
"""doctor() mentions clipboard or https when TLS is disabled."""
|
||||||
|
import muxplex.settings as settings_mod
|
||||||
|
|
||||||
|
settings_file = tmp_path / "settings.json"
|
||||||
|
settings_file.write_text("{}")
|
||||||
|
monkeypatch.setattr(settings_mod, "SETTINGS_PATH", settings_file)
|
||||||
|
|
||||||
|
from muxplex.cli import doctor
|
||||||
|
|
||||||
|
doctor()
|
||||||
|
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
out_lower = 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}"
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user