"""Unit tests for cache module."""
from __future__ import annotations
from unittest.mock import MagicMock, patch
import pytest
from mcp_odoo.cache import RedisCache, _make_cache_key, cached
class TestMakeCacheKey:
"""Tests for _make_cache_key function."""
def test_basic_key(self):
"""Test basic key generation."""
key = _make_cache_key("prefix", "func_name", (1, 2), {"a": "b"})
assert key.startswith("prefix:func_name:")
assert len(key) > len("prefix:func_name:")
def test_no_prefix(self):
"""Test key generation without prefix."""
key = _make_cache_key("", "func_name", (), {})
assert key.startswith("func_name:")
def test_deterministic(self):
"""Test same inputs produce same key."""
key1 = _make_cache_key("p", "f", (1,), {"x": 1})
key2 = _make_cache_key("p", "f", (1,), {"x": 1})
assert key1 == key2
def test_different_args_different_keys(self):
"""Test different arguments produce different keys."""
key1 = _make_cache_key("p", "f", (1,), {})
key2 = _make_cache_key("p", "f", (2,), {})
assert key1 != key2
class TestRedisCache:
"""Tests for RedisCache class."""
def test_disabled_by_default_in_tests(self):
"""Test cache is disabled in test environment."""
cache = RedisCache()
# With REDIS_ENABLED=false in test env
assert not cache.enabled
@patch("mcp_odoo.cache.settings")
def test_get_returns_none_when_disabled(self, mock_settings):
"""Test get returns None when cache disabled."""
mock_settings.redis.enabled = False
cache = RedisCache()
cache._enabled = False
result = cache.get("any_key")
assert result is None
@patch("mcp_odoo.cache.settings")
def test_set_returns_false_when_disabled(self, mock_settings):
"""Test set returns False when cache disabled."""
mock_settings.redis.enabled = False
cache = RedisCache()
cache._enabled = False
result = cache.set("key", "value")
assert result is False
@patch("mcp_odoo.cache.settings")
def test_delete_returns_false_when_disabled(self, mock_settings):
"""Test delete returns False when cache disabled."""
mock_settings.redis.enabled = False
cache = RedisCache()
cache._enabled = False
result = cache.delete("key")
assert result is False
@patch("mcp_odoo.cache.settings")
def test_invalidate_pattern_returns_zero_when_disabled(self, mock_settings):
"""Test invalidate_pattern returns 0 when cache disabled."""
mock_settings.redis.enabled = False
cache = RedisCache()
cache._enabled = False
result = cache.invalidate_pattern("pattern:*")
assert result == 0
class TestCachedDecorator:
"""Tests for cached decorator."""
def test_calls_function_when_cache_disabled(self):
"""Test decorator calls function when cache disabled."""
call_count = 0
@cached(ttl=300)
def my_func():
nonlocal call_count
call_count += 1
return "result"
# Call twice
result1 = my_func()
result2 = my_func()
# Both should call the function (no caching)
assert result1 == "result"
assert result2 == "result"
assert call_count == 2
@patch("mcp_odoo.cache.cache")
def test_returns_cached_value(self, mock_cache):
"""Test decorator returns cached value when available."""
mock_cache.enabled = True
mock_cache.get.return_value = "cached_result"
@cached(ttl=300, prefix="test")
def my_func():
return "new_result"
result = my_func()
assert result == "cached_result"
mock_cache.get.assert_called_once()
@patch("mcp_odoo.cache.cache")
def test_caches_new_value(self, mock_cache):
"""Test decorator caches function result."""
mock_cache.enabled = True
mock_cache.get.return_value = None # Cache miss
@cached(ttl=300, prefix="test")
def my_func():
return "new_result"
result = my_func()
assert result == "new_result"
mock_cache.set.assert_called_once()
def test_invalidate_method(self):
"""Test decorated function has invalidate method."""
@cached(prefix="test")
def my_func():
return "result"
assert hasattr(my_func, "invalidate")
assert callable(my_func.invalidate)
def test_cache_prefix_attribute(self):
"""Test decorated function has cache_prefix attribute."""
@cached(prefix="my_prefix")
def my_func():
return "result"
assert my_func.cache_prefix == "my_prefix"