diff --git a/tests/local_testing/test_dual_cache.py b/tests/local_testing/test_dual_cache.py index 30f45033c5..5a1cdf8648 100644 --- a/tests/local_testing/test_dual_cache.py +++ b/tests/local_testing/test_dual_cache.py @@ -245,72 +245,3 @@ async def test_dual_cache_delete(is_async): result = dual_cache.get_cache(test_key) assert result is None - - -def test_dual_cache_late_attach_redis_wires_writes_and_ttl_sync(): - """ - Typical lazy startup (sync): DualCache runs with in-memory only, then Redis - becomes available and is attached. New writes must reach Redis; keys written - before attach are not backfilled. Optional default_redis_ttl is applied on attach. - """ - in_memory = InMemoryCache() - dual_cache = DualCache(in_memory_cache=in_memory, redis_cache=None) - - mock_redis = MagicMock() - mock_redis.set_cache = MagicMock() - mock_redis.async_set_cache = AsyncMock() - - key_before = f"before_attach_{uuid.uuid4()}" - val_before = {"phase": "memory_only"} - dual_cache.set_cache(key_before, val_before) - - assert in_memory.get_cache(key_before) == val_before - - dual_cache.attach_redis_cache(mock_redis, default_redis_ttl=99.0) - assert dual_cache.redis_cache is mock_redis - assert dual_cache.default_redis_ttl == 99.0 - - mock_redis.set_cache.assert_not_called() - - key_after = f"after_attach_{uuid.uuid4()}" - val_after = {"phase": "memory_and_redis"} - dual_cache.set_cache(key_after, val_after) - mock_redis.set_cache.assert_called_once() - assert mock_redis.set_cache.call_args[0][:2] == (key_after, val_after) - - assert in_memory.get_cache(key_after) == val_after - - -@pytest.mark.asyncio -async def test_dual_cache_late_attach_redis_wires_writes_and_ttl_async(): - """ - Typical lazy startup (async): DualCache runs with in-memory only, then Redis - becomes available and is attached. New writes must reach Redis; keys written - before attach are not backfilled. Optional default_redis_ttl is applied on attach. - """ - in_memory = InMemoryCache() - dual_cache = DualCache(in_memory_cache=in_memory, redis_cache=None) - - mock_redis = MagicMock() - mock_redis.set_cache = MagicMock() - mock_redis.async_set_cache = AsyncMock() - - key_before = f"before_attach_{uuid.uuid4()}" - val_before = {"phase": "memory_only"} - await dual_cache.async_set_cache(key_before, val_before) - - assert in_memory.get_cache(key_before) == val_before - - dual_cache.attach_redis_cache(mock_redis, default_redis_ttl=99.0) - assert dual_cache.redis_cache is mock_redis - assert dual_cache.default_redis_ttl == 99.0 - - mock_redis.async_set_cache.assert_not_called() - - key_after = f"after_attach_{uuid.uuid4()}" - val_after = {"phase": "memory_and_redis"} - await dual_cache.async_set_cache(key_after, val_after) - mock_redis.async_set_cache.assert_called_once() - assert mock_redis.async_set_cache.call_args[0][:2] == (key_after, val_after) - - assert in_memory.get_cache(key_after) == val_after diff --git a/tests/test_litellm/caching/test_dual_cache.py b/tests/test_litellm/caching/test_dual_cache.py index 8e50217576..6477472620 100644 --- a/tests/test_litellm/caching/test_dual_cache.py +++ b/tests/test_litellm/caching/test_dual_cache.py @@ -1,5 +1,6 @@ import asyncio import time +import uuid from unittest.mock import AsyncMock, MagicMock, patch import pytest @@ -260,3 +261,72 @@ async def test_async_increment_cache_returns_none_when_no_in_memory_cache_and_re f"Expected None when in_memory_cache is absent and Redis fails, got {result!r}. " "Returning the delta (1.0) would silently miscalculate rate-limit counters." ) + + +def test_dual_cache_late_attach_redis_wires_writes_and_ttl_sync(): + """ + Typical lazy startup (sync): DualCache runs with in-memory only, then Redis + becomes available and is attached. New writes must reach Redis; keys written + before attach are not backfilled. Optional default_redis_ttl is applied on attach. + """ + in_memory = InMemoryCache() + dual_cache = DualCache(in_memory_cache=in_memory, redis_cache=None) + + mock_redis = MagicMock() + mock_redis.set_cache = MagicMock() + mock_redis.async_set_cache = AsyncMock() + + key_before = f"before_attach_{uuid.uuid4()}" + val_before = {"phase": "memory_only"} + dual_cache.set_cache(key_before, val_before) + + assert in_memory.get_cache(key_before) == val_before + + dual_cache.attach_redis_cache(mock_redis, default_redis_ttl=99.0) + assert dual_cache.redis_cache is mock_redis + assert dual_cache.default_redis_ttl == 99.0 + + mock_redis.set_cache.assert_not_called() + + key_after = f"after_attach_{uuid.uuid4()}" + val_after = {"phase": "memory_and_redis"} + dual_cache.set_cache(key_after, val_after) + mock_redis.set_cache.assert_called_once() + assert mock_redis.set_cache.call_args[0][:2] == (key_after, val_after) + + assert in_memory.get_cache(key_after) == val_after + + +@pytest.mark.asyncio +async def test_dual_cache_late_attach_redis_wires_writes_and_ttl_async(): + """ + Typical lazy startup (async): DualCache runs with in-memory only, then Redis + becomes available and is attached. New writes must reach Redis; keys written + before attach are not backfilled. Optional default_redis_ttl is applied on attach. + """ + in_memory = InMemoryCache() + dual_cache = DualCache(in_memory_cache=in_memory, redis_cache=None) + + mock_redis = MagicMock() + mock_redis.set_cache = MagicMock() + mock_redis.async_set_cache = AsyncMock() + + key_before = f"before_attach_{uuid.uuid4()}" + val_before = {"phase": "memory_only"} + await dual_cache.async_set_cache(key_before, val_before) + + assert in_memory.get_cache(key_before) == val_before + + dual_cache.attach_redis_cache(mock_redis, default_redis_ttl=99.0) + assert dual_cache.redis_cache is mock_redis + assert dual_cache.default_redis_ttl == 99.0 + + mock_redis.async_set_cache.assert_not_called() + + key_after = f"after_attach_{uuid.uuid4()}" + val_after = {"phase": "memory_and_redis"} + await dual_cache.async_set_cache(key_after, val_after) + mock_redis.async_set_cache.assert_called_once() + assert mock_redis.async_set_cache.call_args[0][:2] == (key_after, val_after) + + assert in_memory.get_cache(key_after) == val_after