Skip to main content
Glama
test_token_manager_base.py3.71 kB
"""Unit tests for the base TokenManager in client.token_manager. Covers caching behavior and error handling independent of server subclassing. """ from datetime import UTC, datetime, timedelta from unittest.mock import AsyncMock, patch import pytest from snc_cribl_mcp.client.token_manager import TokenManager from snc_cribl_mcp.config import CriblConfig def _config_with_token() -> CriblConfig: return CriblConfig( server_url="https://cribl.example.com", base_url="https://cribl.example.com/api/v1", bearer_token="tok", ) def _config_with_credentials() -> CriblConfig: return CriblConfig( server_url="https://cribl.example.com", base_url="https://cribl.example.com/api/v1", username="user", password="pass", ) @pytest.mark.asyncio async def test_get_security_returns_existing() -> None: """It should return the existing bearer token from config without network calls.""" manager = TokenManager(_config_with_token()) security = await manager.get_security() assert security.bearer_auth == "tok" @pytest.mark.asyncio async def test_fetch_new_token_success() -> None: """It should fetch a new token when none exists and then cache it.""" manager = TokenManager(_config_with_credentials()) with ( patch.object(TokenManager, "_request_token", new_callable=AsyncMock, return_value="newtok") as mock_request, patch.object(manager, "_get_jwt_exp", return_value=datetime.now(UTC) + timedelta(hours=1)), ): security = await manager.get_security() assert security.bearer_auth == "newtok" security2 = await manager.get_security() assert security2.bearer_auth == "newtok" mock_request.assert_awaited_once() @pytest.mark.asyncio async def test_fetch_new_token_missing_credentials_raises() -> None: """Missing credentials at fetch time should raise a RuntimeError.""" manager = TokenManager(_config_with_credentials()) manager._config.username = None # type: ignore[reportPrivateUsage] manager._cached_token = None # type: ignore[reportPrivateUsage] with pytest.raises(RuntimeError, match="CRIBL_USERNAME and CRIBL_PASSWORD"): await manager.get_security() @pytest.mark.asyncio async def test_fetch_new_token_empty_response_raises() -> None: """An empty token in the auth response should raise a RuntimeError.""" manager = TokenManager(_config_with_credentials()) with ( patch.object(TokenManager, "_request_token", new_callable=AsyncMock, return_value=""), pytest.raises(RuntimeError, match="empty token"), ): await manager.get_security() @pytest.mark.asyncio async def test_fetch_new_token_expired_response_raises() -> None: """Tokens that are already expired should be rejected immediately.""" manager = TokenManager(_config_with_credentials()) with ( patch.object(TokenManager, "_request_token", new_callable=AsyncMock, return_value="newtok"), patch.object(manager, "_get_jwt_exp", return_value=datetime.now(UTC) - timedelta(minutes=1)), pytest.raises(RuntimeError, match="expired token"), ): await manager.get_security() def test_close_is_idempotent() -> None: """TokenManager.close should be safe to call multiple times.""" manager = TokenManager(_config_with_credentials()) manager.close() manager.close() def test_context_manager_calls_close() -> None: """Using TokenManager as a context manager should still call close().""" manager = TokenManager(_config_with_credentials()) with patch.object(manager, "close") as mock_close, manager: pass mock_close.assert_called_once()

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/atree1023/snc-cribl-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server