diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 50f62c093..3cc7efc53 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,7 +82,7 @@ jobs: - package: lablink-cli python-version: "3.11" working-directory: packages/cli - test-command: "uv run pytest tests --cov=src/lablink_cli --cov-report=term-missing:skip-covered --cov-report=xml --cov-report=html" + test-command: "uv run pytest tests --cov=src/lablink_cli --cov-report=term-missing:skip-covered --cov-report=xml --cov-report=html --cov-fail-under=90" needs-tofu: false steps: diff --git a/packages/cli/tests/test_status.py b/packages/cli/tests/test_status.py index 3a7f0da21..03ab99c3a 100644 --- a/packages/cli/tests/test_status.py +++ b/packages/cli/tests/test_status.py @@ -3,6 +3,7 @@ from __future__ import annotations import socket +from datetime import datetime, timedelta, timezone from unittest.mock import MagicMock, patch @@ -16,6 +17,7 @@ check_dns, check_health_endpoint, check_http, + check_ssl_cert, estimate_costs, ) from lablink_cli.docker import Docker, Result @@ -134,6 +136,52 @@ def test_connection_refused(self, mock_urlopen): assert result["healthy"] is False +class TestCheckSslCert: + @patch("lablink_cli.commands.status.ssl.create_default_context") + def test_valid_certificate_reports_issuer_and_expiry(self, mock_context): + domain = "lab.example.org" + expiry = datetime.now(timezone.utc) + timedelta(days=30) + cert = { + "notAfter": expiry.strftime("%b %d %H:%M:%S %Y GMT"), + "issuer": ((('organizationName', 'Example CA'),),), + } + sock = mock_context.return_value.wrap_socket.return_value.__enter__.return_value + sock.getpeercert.return_value = cert + + result = check_ssl_cert(domain) + + assert result["status"] == "pass" + assert "Example CA" in result["detail"] + assert str(expiry.date()) in result["detail"] + sock.connect.assert_called_once_with((domain, 443)) + + @patch("lablink_cli.commands.status.ssl.create_default_context") + def test_expiring_certificate_warns(self, mock_context): + expiry = datetime.now(timezone.utc) + timedelta(days=5) + sock = mock_context.return_value.wrap_socket.return_value.__enter__.return_value + sock.getpeercert.return_value = { + "notAfter": expiry.strftime("%b %d %H:%M:%S %Y GMT") + } + + result = check_ssl_cert("lab.example.org") + + assert result["status"] == "warn" + assert "Expires" in result["detail"] + + @patch("lablink_cli.commands.status.ssl.create_default_context") + def test_missing_certificate_fails(self, mock_context): + sock = mock_context.return_value.wrap_socket.return_value.__enter__.return_value + sock.getpeercert.return_value = None + + result = check_ssl_cert("lab.example.org") + + assert result == { + "check": "SSL Certificate", + "status": "fail", + "detail": "No certificate returned", + } + + # ------------------------------------------------------------------ # User-Agent header #