mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-01-08 20:51:50 -06:00
178 lines
5.5 KiB
Python
178 lines
5.5 KiB
Python
"""
|
|
Tests for caching utilities (Redis and in-memory fallback).
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import Mock, patch, MagicMock
|
|
from app.utils.cache import get_cache, InMemoryCache, RedisCache
|
|
|
|
|
|
class TestInMemoryCache:
|
|
"""Tests for in-memory cache implementation"""
|
|
|
|
def test_get_set_delete(self):
|
|
"""Test basic cache operations"""
|
|
cache = InMemoryCache()
|
|
|
|
# Test set and get
|
|
cache.set("test_key", "test_value", ttl=3600)
|
|
assert cache.get("test_key") == "test_value"
|
|
|
|
# Test delete
|
|
cache.delete("test_key")
|
|
assert cache.get("test_key") is None
|
|
|
|
def test_expiration(self):
|
|
"""Test that expired entries are not returned"""
|
|
cache = InMemoryCache(default_ttl=1)
|
|
|
|
cache.set("expired_key", "value", ttl=0.1) # Very short TTL
|
|
assert cache.get("expired_key") == "value"
|
|
|
|
import time
|
|
|
|
time.sleep(0.2)
|
|
assert cache.get("expired_key") is None
|
|
|
|
def test_exists(self):
|
|
"""Test exists method"""
|
|
cache = InMemoryCache()
|
|
|
|
assert cache.exists("nonexistent") is False
|
|
cache.set("existing", "value")
|
|
assert cache.exists("existing") is True
|
|
|
|
def test_clear(self):
|
|
"""Test clearing all cache"""
|
|
cache = InMemoryCache()
|
|
|
|
cache.set("key1", "value1")
|
|
cache.set("key2", "value2")
|
|
cache.clear()
|
|
|
|
assert cache.get("key1") is None
|
|
assert cache.get("key2") is None
|
|
|
|
|
|
class TestRedisCache:
|
|
"""Tests for Redis cache implementation"""
|
|
|
|
@patch("app.utils.cache.redis")
|
|
def test_redis_connection_success(self, mock_redis):
|
|
"""Test successful Redis connection"""
|
|
mock_client = MagicMock()
|
|
mock_client.ping.return_value = True
|
|
mock_redis.Redis.return_value = mock_client
|
|
|
|
cache = RedisCache("redis://localhost:6379/0")
|
|
|
|
assert cache._connected is True
|
|
mock_client.ping.assert_called_once()
|
|
|
|
@patch("app.utils.cache.redis")
|
|
def test_redis_connection_failure(self, mock_redis):
|
|
"""Test Redis connection failure falls back to in-memory"""
|
|
mock_redis.Redis.side_effect = Exception("Connection failed")
|
|
|
|
cache = RedisCache("redis://localhost:6379/0")
|
|
|
|
assert cache._connected is False
|
|
assert hasattr(cache, "_fallback")
|
|
|
|
@patch("app.utils.cache.redis")
|
|
def test_redis_get_set(self, mock_redis):
|
|
"""Test Redis get and set operations"""
|
|
import pickle
|
|
|
|
mock_client = MagicMock()
|
|
mock_client.ping.return_value = True
|
|
mock_redis.Redis.return_value = mock_client
|
|
|
|
cache = RedisCache("redis://localhost:6379/0")
|
|
|
|
# Test set
|
|
cache.set("test_key", "test_value", ttl=3600)
|
|
mock_client.setex.assert_called_once()
|
|
args, kwargs = mock_client.setex.call_args
|
|
assert args[0] == "test_key"
|
|
assert args[1] == 3600
|
|
assert pickle.loads(args[2]) == "test_value"
|
|
|
|
# Test get
|
|
mock_client.get.return_value = pickle.dumps("test_value")
|
|
result = cache.get("test_key")
|
|
assert result == "test_value"
|
|
mock_client.get.assert_called_with("test_key")
|
|
|
|
@patch("app.utils.cache.redis")
|
|
def test_redis_fallback_on_error(self, mock_redis):
|
|
"""Test that Redis errors fall back to in-memory cache"""
|
|
mock_client = MagicMock()
|
|
mock_client.ping.return_value = True
|
|
mock_client.get.side_effect = Exception("Redis error")
|
|
mock_redis.Redis.return_value = mock_client
|
|
|
|
cache = RedisCache("redis://localhost:6379/0")
|
|
|
|
# Should not raise, but return None
|
|
result = cache.get("test_key")
|
|
assert result is None
|
|
|
|
|
|
class TestCacheIntegration:
|
|
"""Integration tests for cache utilities"""
|
|
|
|
@patch("app.utils.cache.current_app")
|
|
def test_get_cache_with_redis_enabled(self, mock_app):
|
|
"""Test get_cache when Redis is enabled"""
|
|
mock_config = {"REDIS_ENABLED": True, "REDIS_URL": "redis://localhost:6379/0", "REDIS_DEFAULT_TTL": 3600}
|
|
mock_app.config = mock_config
|
|
|
|
with patch("app.utils.cache.RedisCache") as mock_redis_cache:
|
|
mock_instance = MagicMock()
|
|
mock_instance._connected = True
|
|
mock_redis_cache.return_value = mock_instance
|
|
|
|
cache = get_cache()
|
|
assert cache is not None
|
|
|
|
@patch("app.utils.cache.current_app")
|
|
def test_get_cache_fallback_to_memory(self, mock_app):
|
|
"""Test get_cache falls back to in-memory when Redis unavailable"""
|
|
mock_config = {"REDIS_ENABLED": False, "REDIS_DEFAULT_TTL": 3600}
|
|
mock_app.config = mock_config
|
|
|
|
# Reset global cache
|
|
import app.utils.cache
|
|
|
|
app.utils.cache._cache = None
|
|
|
|
cache = get_cache()
|
|
assert isinstance(cache, InMemoryCache)
|
|
|
|
def test_cache_decorator(self):
|
|
"""Test the @cached decorator"""
|
|
from app.utils.cache import cached
|
|
|
|
call_count = [0]
|
|
|
|
@cached(ttl=60, key_prefix="test")
|
|
def expensive_function(x, y):
|
|
call_count[0] += 1
|
|
return x + y
|
|
|
|
# First call should execute function
|
|
result1 = expensive_function(1, 2)
|
|
assert result1 == 3
|
|
assert call_count[0] == 1
|
|
|
|
# Second call should use cache
|
|
result2 = expensive_function(1, 2)
|
|
assert result2 == 3
|
|
assert call_count[0] == 1 # Should not increment
|
|
|
|
# Different args should execute again
|
|
result3 = expensive_function(2, 3)
|
|
assert result3 == 5
|
|
assert call_count[0] == 2
|