"""Unit tests for Token Store component.
Tests the secure persistence of authentication tokens using msal-extensions.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from pathlib import Path
class TestTokenStore:
"""Tests for TokenStore class."""
def test_init_creates_cache_directory(self, temp_dir: Path) -> None:
"""Test that TokenStore creates the cache directory if it doesn't exist."""
from sso_mcp_server.auth.token_store import TokenStore
cache_path = temp_dir / "subdir" / "token_cache.bin"
assert not cache_path.parent.exists()
store = TokenStore(cache_path)
assert cache_path.parent.exists()
assert store.cache_path == cache_path
def test_get_cache_returns_persisted_token_cache(self, temp_dir: Path) -> None:
"""Test that get_cache returns an MSAL PersistedTokenCache."""
from sso_mcp_server.auth.token_store import TokenStore
cache_path = temp_dir / "token_cache.bin"
store = TokenStore(cache_path)
cache = store.get_cache()
# Cache should be a valid token cache object
assert cache is not None
assert hasattr(cache, "serialize")
assert hasattr(cache, "deserialize")
def test_has_cached_tokens_returns_false_when_empty(self, temp_dir: Path) -> None:
"""Test that has_cached_tokens returns False when no tokens exist."""
from sso_mcp_server.auth.token_store import TokenStore
cache_path = temp_dir / "token_cache.bin"
store = TokenStore(cache_path)
assert store.has_cached_tokens() is False
def test_has_cached_tokens_returns_true_when_tokens_exist(self, temp_dir: Path) -> None:
"""Test that has_cached_tokens returns True when tokens are stored."""
from sso_mcp_server.auth.token_store import TokenStore
cache_path = temp_dir / "token_cache.bin"
store = TokenStore(cache_path)
# Simulate storing tokens by writing to the cache
cache = store.get_cache()
# Write minimal valid MSAL cache data
cache.deserialize('{"AccessToken": {"key": {"secret": "test"}}, "RefreshToken": {}}')
assert store.has_cached_tokens() is True
def test_clear_cache_removes_tokens(self, temp_dir: Path) -> None:
"""Test that clear_cache removes all cached tokens."""
from sso_mcp_server.auth.token_store import TokenStore
cache_path = temp_dir / "token_cache.bin"
store = TokenStore(cache_path)
# Add some data to cache
cache = store.get_cache()
cache.deserialize('{"AccessToken": {"key": {"secret": "test"}}, "RefreshToken": {}}')
assert store.has_cached_tokens() is True
# Clear the cache
store.clear_cache()
assert store.has_cached_tokens() is False
def test_clear_cache_removes_cache_file(self, temp_dir: Path) -> None:
"""Test that clear_cache removes the cache file from disk."""
from sso_mcp_server.auth.token_store import TokenStore
cache_path = temp_dir / "token_cache.bin"
store = TokenStore(cache_path)
# Write something to create the file
cache = store.get_cache()
cache.deserialize('{"AccessToken": {}, "RefreshToken": {}}')
# Trigger a save by modifying and serializing
# Note: msal-extensions may not immediately write to disk
store.clear_cache()
# After clearing, the file should be empty or not contain valid tokens
assert store.has_cached_tokens() is False
def test_cache_path_property_returns_configured_path(self, temp_dir: Path) -> None:
"""Test that cache_path property returns the configured path."""
from sso_mcp_server.auth.token_store import TokenStore
cache_path = temp_dir / "custom_cache.bin"
store = TokenStore(cache_path)
assert store.cache_path == cache_path
def test_multiple_stores_share_same_cache_file(self, temp_dir: Path) -> None:
"""Test that multiple TokenStore instances can share the same cache file."""
from sso_mcp_server.auth.token_store import TokenStore
cache_path = temp_dir / "token_cache.bin"
store1 = TokenStore(cache_path)
store2 = TokenStore(cache_path)
# Write with store1
cache1 = store1.get_cache()
cache1.deserialize('{"AccessToken": {"key": {"secret": "test"}}, "RefreshToken": {}}')
# Both stores should see the tokens (after re-reading)
# Note: In-memory caches might differ, but file should be shared
assert store1.cache_path == store2.cache_path
class TestTokenStoreEdgeCases:
"""Edge case tests for TokenStore."""
def test_handles_corrupted_cache_file(self, temp_dir: Path) -> None:
"""Test that TokenStore handles corrupted cache files gracefully."""
from sso_mcp_server.auth.token_store import TokenStore
cache_path = temp_dir / "token_cache.bin"
# Create a corrupted cache file
cache_path.parent.mkdir(parents=True, exist_ok=True)
cache_path.write_text("not valid json or msal cache data")
store = TokenStore(cache_path)
# Should not raise, should treat as empty
# Exact behavior depends on msal-extensions error handling
cache = store.get_cache()
assert cache is not None
def test_handles_missing_parent_directory(self, temp_dir: Path) -> None:
"""Test that TokenStore creates missing parent directories."""
from sso_mcp_server.auth.token_store import TokenStore
# Deep nested path that doesn't exist
cache_path = temp_dir / "a" / "b" / "c" / "token_cache.bin"
assert not cache_path.parent.exists()
store = TokenStore(cache_path)
assert cache_path.parent.exists()
assert store.cache_path == cache_path