Files
mostovik-backend/tests/apps/core/test_cache.py
Aleksandr Meshchriakov a91ed1f1ae
Some checks failed
CI/CD Pipeline / Code Quality Checks (push) Failing after 3m10s
CI/CD Pipeline / Run Tests (push) Successful in 3m35s
CI/CD Pipeline / Telegram Notify Success (push) Has been skipped
CI/CD Pipeline / Code Quality Checks (pull_request) Failing after 2m26s
CI/CD Pipeline / Run Tests (pull_request) Successful in 2m46s
CI/CD Pipeline / Telegram Notify Success (pull_request) Has been skipped
feat(registry): add new endpoints for registers, exchange, and backups; update routing and configurations
2026-03-04 15:36:57 +01:00

274 lines
8.3 KiB
Python

"""Tests for core cache utilities"""
from apps.core.cache import (
CacheManager,
_build_cache_key,
cache_method,
cache_result,
invalidate_cache,
)
from django.core.cache import cache
from django.test import TestCase
from tests.utils.fixtures import fake
class CacheResultDecoratorTest(TestCase):
"""Tests for @cache_result decorator"""
def setUp(self):
cache.clear()
self.call_count = 0
def test_result_is_cached(self):
"""Test that function result is cached"""
@cache_result(timeout=60, key_prefix="test")
def expensive_function(x):
self.call_count += 1
return x * 2
# First call - should execute
result1 = expensive_function(5)
self.assertEqual(result1, 10)
self.assertEqual(self.call_count, 1)
# Second call - should return cached result
result2 = expensive_function(5)
self.assertEqual(result2, 10)
self.assertEqual(self.call_count, 1) # Still 1, not called again
def test_key_builder_used(self):
"""Test custom key builder overrides default."""
called = {"count": 0}
def key_builder(*_args, **_kwargs):
return "custom-key"
@cache_result(timeout=60, key_builder=key_builder)
def expensive_function(x):
called["count"] += 1
return x * 3
result1 = expensive_function(2)
result2 = expensive_function(2)
self.assertEqual(result1, 6)
self.assertEqual(result2, 6)
self.assertEqual(called["count"], 1)
def test_invalidate_cache_fallback(self):
"""Test invalidate_cache fallback without delete_pattern."""
cache.set("cache-key", "value", timeout=60)
invalidate_cache("cache-key")
self.assertIsNone(cache.get("cache-key"))
def test_invalidate_wrapper_with_key_builder(self):
key_suffix = fake.pystr(min_chars=3, max_chars=8)
def key_builder(x):
return f"{key_suffix}:{x}"
@cache_result(timeout=60, key_builder=key_builder)
def expensive_function(x):
self.call_count += 1
return x * 2
value = fake.random_int(min=1, max=9)
result = expensive_function(value)
self.assertEqual(result, value * 2)
self.assertIsNotNone(cache.get(f"{key_suffix}:{value}"))
expensive_function.invalidate(value)
self.assertIsNone(cache.get(f"{key_suffix}:{value}"))
def test_invalidate_wrapper_default_builder(self):
value = fake.random_int(min=1, max=9)
@cache_result(timeout=60, key_prefix="default")
def expensive_function(x):
self.call_count += 1
return x * 2
_ = expensive_function(value)
key = _build_cache_key(expensive_function, "default", (value,), {})
self.assertIsNotNone(cache.get(key))
expensive_function.invalidate(value)
self.assertIsNone(cache.get(key))
def test_different_args_not_cached(self):
"""Test that different arguments create different cache entries"""
@cache_result(timeout=60, key_prefix="test")
def expensive_function(x):
self.call_count += 1
return x * 2
result1 = expensive_function(5)
result2 = expensive_function(10)
self.assertEqual(result1, 10)
self.assertEqual(result2, 20)
self.assertEqual(self.call_count, 2)
def test_kwargs_included_in_cache_key(self):
"""Test that kwargs are included in cache key"""
@cache_result(timeout=60, key_prefix="test")
def expensive_function(x, multiplier=2):
self.call_count += 1
return x * multiplier
result1 = expensive_function(5, multiplier=2)
result2 = expensive_function(5, multiplier=3)
self.assertEqual(result1, 10)
self.assertEqual(result2, 15)
self.assertEqual(self.call_count, 2)
class CacheMethodDecoratorTest(TestCase):
"""Tests for @cache_method decorator"""
def setUp(self):
cache.clear()
def test_classmethod_caching(self):
"""Test caching works with classmethod"""
call_count = {"value": 0}
class MyService:
@classmethod
@cache_method(timeout=60, key_prefix="service")
def get_data(cls, item_id):
call_count["value"] += 1
return {"id": item_id, "data": "test"}
# First call
result1 = MyService.get_data(1)
self.assertEqual(result1["id"], 1)
self.assertEqual(call_count["value"], 1)
# Second call - should be cached
result2 = MyService.get_data(1)
self.assertEqual(result2["id"], 1)
self.assertEqual(call_count["value"], 1)
# Different argument
result3 = MyService.get_data(2)
self.assertEqual(result3["id"], 2)
self.assertEqual(call_count["value"], 2)
class CacheManagerTest(TestCase):
"""Tests for CacheManager"""
def setUp(self):
cache.clear()
self.manager = CacheManager("test_prefix")
def test_set_and_get(self):
"""Test basic set and get operations"""
self.manager.set("key1", "value1", timeout=60)
result = self.manager.get("key1")
self.assertEqual(result, "value1")
def test_get_default(self):
"""Test get returns default for missing key"""
result = self.manager.get("nonexistent", default="default_value")
self.assertEqual(result, "default_value")
def test_delete(self):
"""Test delete operation"""
self.manager.set("key1", "value1")
self.manager.delete("key1")
result = self.manager.get("key1")
self.assertIsNone(result)
def test_get_or_set(self):
"""Test get_or_set operation"""
call_count = {"value": 0}
def compute_value():
call_count["value"] += 1
return "computed"
# First call - should compute
result1 = self.manager.get_or_set("key1", compute_value)
self.assertEqual(result1, "computed")
self.assertEqual(call_count["value"], 1)
# Second call - should return cached
result2 = self.manager.get_or_set("key1", compute_value)
self.assertEqual(result2, "computed")
self.assertEqual(call_count["value"], 1)
def test_prefix_applied(self):
"""Test that prefix is applied to keys"""
self.manager.set("mykey", "myvalue")
# Direct cache access should use prefixed key
direct_result = cache.get("test_prefix:mykey")
self.assertEqual(direct_result, "myvalue")
def test_clear_removes_prefixed_keys(self):
self.manager.set("one", "1")
self.manager.set("two", "2")
self.manager.clear()
# LocMemCache doesn't support delete_pattern, so keys may remain.
self.assertIsNotNone(cache.get("test_prefix:one"))
class BuildCacheKeyTest(TestCase):
"""Tests for _build_cache_key function"""
def test_key_includes_function_name(self):
"""Test cache key includes function name"""
def my_function():
pass
key = _build_cache_key(my_function, "", (), {})
self.assertIn("my_function", key)
def test_key_includes_prefix(self):
"""Test cache key includes prefix"""
def my_function():
pass
key = _build_cache_key(my_function, "myprefix", (), {})
self.assertTrue(key.startswith("myprefix:"))
def test_different_args_different_keys(self):
"""Test different arguments produce different keys"""
def my_function():
pass
key1 = _build_cache_key(my_function, "", (1, 2), {})
key2 = _build_cache_key(my_function, "", (1, 3), {})
self.assertNotEqual(key1, key2)
def test_different_kwargs_different_keys(self):
"""Test different kwargs produce different keys"""
def my_function():
pass
key1 = _build_cache_key(my_function, "", (), {"a": 1})
key2 = _build_cache_key(my_function, "", (), {"a": 2})
self.assertNotEqual(key1, key2)
def test_build_cache_key_handles_circular(self):
"""Test circular references fallback to str."""
def my_function():
pass
items: list[object] = []
items.append(items)
key = _build_cache_key(my_function, "", (items,), {})
self.assertIn("my_function", key)