Skip to main content
Glama
test_manager.py6.88 kB
"""Unit tests for cache manager.""" import json from datetime import datetime from unittest.mock import AsyncMock, MagicMock, patch import pytest from src.cache.manager import CacheManager class TestCacheManager: """Test suite for CacheManager class.""" @pytest.fixture def mock_redis(self): """Create a mock Redis client.""" mock = AsyncMock() mock.get = AsyncMock(return_value=None) mock.setex = AsyncMock() mock.delete = AsyncMock(return_value=1) return mock @pytest.fixture def cache_manager(self, mock_redis): """Create CacheManager with mocked Redis client.""" manager = CacheManager() manager.redis = mock_redis return manager @pytest.mark.asyncio async def test_get_cache_hit(self, cache_manager, mock_redis): """Test get() returns cached data on cache hit.""" cached_data = { "data": {"results": [{"id": "123"}]}, "cached_at": datetime.utcnow().isoformat(), "ttl": 300, } mock_redis.get.return_value = json.dumps(cached_data) result = await cache_manager.get("test_key") assert result is not None assert result["data"] == cached_data["data"] assert "age_seconds" in result mock_redis.get.assert_called_once_with("test_key") @pytest.mark.asyncio async def test_get_cache_miss(self, cache_manager, mock_redis): """Test get() returns None on cache miss.""" mock_redis.get.return_value = None result = await cache_manager.get("test_key") assert result is None mock_redis.get.assert_called_once_with("test_key") @pytest.mark.asyncio async def test_get_with_no_redis(self): """Test get() returns None when Redis is not available.""" manager = CacheManager() manager.redis = None result = await manager.get("test_key") assert result is None @pytest.mark.asyncio async def test_get_invalid_json(self, cache_manager, mock_redis): """Test get() handles invalid JSON gracefully.""" mock_redis.get.return_value = "invalid json {" mock_redis.delete = AsyncMock() result = await cache_manager.get("test_key") assert result is None # Should delete invalid cached data mock_redis.delete.assert_called_once_with("test_key") @pytest.mark.asyncio async def test_set_success(self, cache_manager, mock_redis): """Test set() stores data successfully.""" data = {"results": [{"id": "123"}]} result = await cache_manager.set("test_key", data, ttl=300) assert result is True mock_redis.setex.assert_called_once() args = mock_redis.setex.call_args[0] assert args[0] == "test_key" # key assert args[1] == 300 # ttl # Verify JSON can be parsed cached = json.loads(args[2]) assert cached["data"] == data @pytest.mark.asyncio async def test_set_with_no_redis(self): """Test set() returns False when Redis is not available.""" manager = CacheManager() manager.redis = None result = await manager.set("test_key", {"data": "test"}, ttl=300) assert result is False @pytest.mark.asyncio async def test_set_non_serializable_data(self, cache_manager, mock_redis): """Test set() handles non-serializable data gracefully.""" # Create non-serializable data (function object) non_serializable = {"func": lambda x: x} result = await cache_manager.set("test_key", non_serializable, ttl=300) assert result is False @pytest.mark.asyncio async def test_delete_success(self, cache_manager, mock_redis): """Test delete() removes cached data.""" mock_redis.delete.return_value = 1 result = await cache_manager.delete("test_key") assert result is True mock_redis.delete.assert_called_once_with("test_key") @pytest.mark.asyncio async def test_delete_nonexistent_key(self, cache_manager, mock_redis): """Test delete() with nonexistent key.""" mock_redis.delete.return_value = 0 result = await cache_manager.delete("test_key") assert result is False @pytest.mark.asyncio async def test_delete_with_no_redis(self): """Test delete() returns False when Redis is not available.""" manager = CacheManager() manager.redis = None result = await manager.delete("test_key") assert result is False @pytest.mark.asyncio async def test_get_or_fetch_cache_hit(self, cache_manager, mock_redis): """Test get_or_fetch() returns cached data without calling fetch.""" cached_data = { "data": {"results": [{"id": "123"}]}, "cached_at": datetime.utcnow().isoformat(), "ttl": 300, } mock_redis.get.return_value = json.dumps(cached_data) fetch_func = AsyncMock() result = await cache_manager.get_or_fetch("test_key", fetch_func, ttl=300) assert result["metadata"]["cached"] is True assert result["data"] == cached_data["data"] fetch_func.assert_not_called() @pytest.mark.asyncio async def test_get_or_fetch_cache_miss(self, cache_manager, mock_redis): """Test get_or_fetch() calls fetch function on cache miss.""" mock_redis.get.return_value = None fetched_data = {"results": [{"id": "456"}]} fetch_func = AsyncMock(return_value=fetched_data) result = await cache_manager.get_or_fetch("test_key", fetch_func, ttl=300) assert result["metadata"]["cached"] is False assert result["data"] == fetched_data fetch_func.assert_called_once() mock_redis.setex.assert_called_once() @pytest.mark.asyncio async def test_get_or_fetch_fetch_error(self, cache_manager, mock_redis): """Test get_or_fetch() propagates fetch function errors.""" mock_redis.get.return_value = None fetch_func = AsyncMock(side_effect=Exception("Fetch failed")) with pytest.raises(Exception) as exc_info: await cache_manager.get_or_fetch("test_key", fetch_func, ttl=300) assert "Fetch failed" in str(exc_info.value) @pytest.mark.asyncio async def test_get_or_fetch_no_redis_still_fetches(self): """Test get_or_fetch() still fetches data when Redis is unavailable.""" manager = CacheManager() manager.redis = None fetched_data = {"results": [{"id": "789"}]} fetch_func = AsyncMock(return_value=fetched_data) result = await manager.get_or_fetch("test_key", fetch_func, ttl=300) assert result["metadata"]["cached"] is False assert result["data"] == fetched_data fetch_func.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/padak/apify-actor-reddit-mcp'

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