test_client_unit.py•7.38 kB
from importlib.metadata import version
from unittest.mock import MagicMock, patch
import pytest
from starlette.requests import Request
from pagerduty_mcp_server.client import PagerDutyClient, create_client
@pytest.fixture(autouse=True)
def reset_env_client():
    """Reset the singleton env client before and after each test."""
    from pagerduty_mcp_server.client import client as module_client
    # Reset both class attribute and instance attribute
    module_client._env_client = None
    PagerDutyClient._env_client = None
    yield
    # Reset again after test
    module_client._env_client = None
    PagerDutyClient._env_client = None
@pytest.mark.unit
@pytest.mark.client
def test_create_client_with_request_token():
    """Test that create_client() creates new instance with request token."""
    # Create mock client with headers attribute
    mock_client = MagicMock()
    mock_client.headers = {}
    # Create mock request with token
    mock_request = MagicMock(spec=Request)
    mock_request.headers = {"X-Goose-Token": "request-token"}
    with patch(
        "pagerduty.RestApiV2Client", return_value=mock_client
    ) as mock_client_class:
        with patch(
            "pagerduty_mcp_server.client.get_http_request", return_value=mock_request
        ):
            with patch.dict("os.environ", {"PAGERDUTY_API_TOKEN": "env-token"}):
                client1 = create_client()
                client2 = create_client()
                # Verify new instance created each time with request token
                assert mock_client_class.call_count == 2
                mock_client_class.assert_called_with("request-token")
                assert (
                    client1.headers["User-Agent"]
                    == f"pagerduty_mcp_server/{version('pagerduty_mcp_server')}"
                )
                assert (
                    client2.headers["User-Agent"]
                    == f"pagerduty_mcp_server/{version('pagerduty_mcp_server')}"
                )
@pytest.mark.unit
@pytest.mark.client
def test_create_client_with_env_token_singleton():
    """Test that create_client() reuses instance with env token."""
    # Create mock client with headers attribute
    mock_client = MagicMock()
    mock_client.headers = {}
    with patch(
        "pagerduty.RestApiV2Client", return_value=mock_client
    ) as mock_client_class:
        with patch(
            "pagerduty_mcp_server.client.get_http_request", side_effect=RuntimeError
        ):
            with patch.dict("os.environ", {"PAGERDUTY_API_TOKEN": "env-token"}):
                client1 = create_client()
                client2 = create_client()
                # Verify single instance created and reused
                mock_client_class.assert_called_once_with("env-token")
                assert client1 is client2
                assert (
                    client1.headers["User-Agent"]
                    == f"pagerduty_mcp_server/{version('pagerduty_mcp_server')}"
                )
@pytest.mark.unit
@pytest.mark.client
def test_token_precedence():
    """Test that header token takes precedence over env token."""
    # Create mock clients with headers attribute
    mock_header_client = MagicMock()
    mock_header_client.headers = {}
    mock_env_client = MagicMock()
    mock_env_client.headers = {}
    # Create mock request with token
    mock_request = MagicMock(spec=Request)
    mock_request.headers = {"X-Goose-Token": "header-token"}
    with patch(
        "pagerduty.RestApiV2Client", side_effect=[mock_header_client, mock_env_client]
    ) as mock_client_class:
        with patch(
            "pagerduty_mcp_server.client.get_http_request", return_value=mock_request
        ):
            with patch.dict("os.environ", {"PAGERDUTY_API_TOKEN": "env-token"}):
                client = create_client()
                # Verify header token was used
                mock_client_class.assert_called_once_with("header-token")
                assert (
                    client.headers["User-Agent"]
                    == f"pagerduty_mcp_server/{version('pagerduty_mcp_server')}"
                )
@pytest.mark.unit
@pytest.mark.client
def test_create_client_no_token(monkeypatch):
    """Test that create_client() raises exception when no token is available."""
    # Ensure singleton is reset
    PagerDutyClient._env_client = None
    # Remove any environment variables
    monkeypatch.delenv("PAGERDUTY_API_TOKEN", raising=False)
    # Mock request with no token
    mock_request = MagicMock(spec=Request)
    mock_request.headers = {}  # Empty headers, no token
    # Create a fresh instance to avoid any class-level caching
    test_client = PagerDutyClient()
    with patch("pagerduty.RestApiV2Client") as mock_client_class:
        with patch(
            "pagerduty_mcp_server.client.get_http_request", return_value=mock_request
        ):
            # Verify our setup is clean
            assert test_client._env_client is None
            assert PagerDutyClient._env_client is None
            assert test_client._get_header_token() is None
            assert test_client._get_env_token() is None
            # Test the error is raised
            with pytest.raises(Exception) as exc_info:
                test_client.get_client()
            assert (
                str(exc_info.value)
                == "No auth token found in headers or environment variables"
            )
            mock_client_class.assert_not_called()
@pytest.mark.unit
@pytest.mark.client
def test_get_header_token():
    """Test header token retrieval."""
    mock_request = MagicMock(spec=Request)
    mock_request.headers = {"X-Goose-Token": "header-token"}
    with patch(
        "pagerduty_mcp_server.client.get_http_request", return_value=mock_request
    ):
        token = PagerDutyClient._get_header_token()
        assert token == "header-token"
@pytest.mark.unit
@pytest.mark.client
def test_get_header_token_missing():
    """Test header token retrieval when missing."""
    mock_request = MagicMock(spec=Request)
    mock_request.headers = {}
    with patch(
        "pagerduty_mcp_server.client.get_http_request", return_value=mock_request
    ):
        token = PagerDutyClient._get_header_token()
        assert token is None
@pytest.mark.unit
@pytest.mark.client
def test_get_env_token():
    """Test environment token retrieval."""
    with patch.dict("os.environ", {"PAGERDUTY_API_TOKEN": "env-token"}):
        token = PagerDutyClient._get_env_token()
        assert token == "env-token"
@pytest.mark.unit
@pytest.mark.client
def test_get_env_token_missing():
    """Test environment token retrieval when missing."""
    with patch.dict("os.environ", {}, clear=True):
        token = PagerDutyClient._get_env_token()
        assert token is None
@pytest.mark.unit
@pytest.mark.client
def test_create_client_with_token():
    """Test client creation with token."""
    mock_client = MagicMock()
    mock_client.headers = {}
    with patch(
        "pagerduty.RestApiV2Client", return_value=mock_client
    ) as mock_client_class:
        client = PagerDutyClient._create_client_with_token("test-token")
        mock_client_class.assert_called_once_with("test-token")
        assert (
            client.headers["User-Agent"]
            == f"pagerduty_mcp_server/{version('pagerduty_mcp_server')}"
        )