Files
aitbc/tests/test_caching.py
aitbc f4688aefbd
Some checks failed
Cross-Node Transaction Testing / transaction-test (push) Has been cancelled
Deploy to Testnet / deploy-testnet (push) Has been cancelled
Documentation Validation / validate-docs (push) Has been cancelled
Documentation Validation / validate-policies-strict (push) Has been cancelled
Integration Tests / test-service-integration (push) Has been cancelled
Multi-Node Stress Testing / stress-test (push) Has been cancelled
Python Tests / test-python (push) Has been cancelled
Security Scanning / security-scan (push) Has been cancelled
refactor: improve imports, fix datetime usage, and reorganize cross-chain services
- Added logger initialization to EventRouter in events.py
- Fixed datetime.timedelta references to use timedelta directly in security_hardening.py
- Fixed StateTransition timestamp default_factory to use lambda in state.py
- Fixed StateValidator.validate_transitions to only check source states exist
- Moved cross_chain_bridge_enhanced.py to cross_chain/bridge_enhanced.py
- Updated import paths in global_marketplace
2026-05-12 20:49:01 +02:00

458 lines
14 KiB
Python

"""
Tests for caching utilities
"""
import pytest
import time
from datetime import datetime, timedelta
from unittest.mock import patch
from aitbc.caching import (
CacheEntry,
LRUCache,
TTLCache,
cached,
cached_lru,
_generate_cache_key,
get_global_lru_cache,
get_global_ttl_cache,
clear_global_caches,
)
class TestCacheEntry:
"""Tests for CacheEntry"""
def test_cache_entry_creation(self):
"""Test CacheEntry creation"""
entry = CacheEntry(value="test_value")
assert entry.value == "test_value"
assert entry.expires_at is None
assert entry.hit_count == 0
def test_cache_entry_with_expiration(self):
"""Test CacheEntry with expiration"""
expires = datetime.now() + timedelta(seconds=60)
entry = CacheEntry(value="test_value", expires_at=expires)
assert entry.expires_at == expires
def test_is_expired_no_expiration(self):
"""Test is_expired when no expiration set"""
entry = CacheEntry(value="test_value")
assert entry.is_expired() is False
def test_is_expired_not_expired(self):
"""Test is_expired when not yet expired"""
expires = datetime.now() + timedelta(seconds=60)
entry = CacheEntry(value="test_value", expires_at=expires)
assert entry.is_expired() is False
def test_is_expired_expired(self):
"""Test is_expired when expired"""
expires = datetime.now() - timedelta(seconds=1)
entry = CacheEntry(value="test_value", expires_at=expires)
assert entry.is_expired() is True
class TestLRUCache:
"""Tests for LRUCache"""
def test_initialization(self):
"""Test LRUCache initialization"""
cache = LRUCache(capacity=10)
assert cache.capacity == 10
assert len(cache.cache) == 0
assert cache._hits == 0
assert cache._misses == 0
def test_get_miss(self):
"""Test get when key not in cache"""
cache = LRUCache()
result = cache.get("nonexistent")
assert result is None
assert cache._misses == 1
def test_get_hit(self):
"""Test get when key in cache"""
cache = LRUCache()
cache.set("key1", "value1")
result = cache.get("key1")
assert result == "value1"
assert cache._hits == 1
def test_get_expired(self):
"""Test get when entry expired"""
cache = LRUCache()
cache.set("key1", "value1", ttl=1)
time.sleep(1.1)
result = cache.get("key1")
assert result is None
assert cache._misses == 1
def test_set_basic(self):
"""Test set basic functionality"""
cache = LRUCache()
cache.set("key1", "value1")
assert cache.get("key1") == "value1"
def test_set_with_ttl(self):
"""Test set with TTL"""
cache = LRUCache()
cache.set("key1", "value1", ttl=60)
assert cache.get("key1") == "value1"
def test_set_overwrite(self):
"""Test set overwrites existing key"""
cache = LRUCache()
cache.set("key1", "value1")
cache.set("key1", "value2")
assert cache.get("key1") == "value2"
def test_set_eviction(self):
"""Test LRU eviction when capacity exceeded"""
cache = LRUCache(capacity=3)
cache.set("key1", "value1")
cache.set("key2", "value2")
cache.set("key3", "value3")
cache.set("key4", "value4") # Should evict key1 (least recently used)
assert cache.get("key1") is None
assert cache.get("key2") == "value2"
assert cache.get("key3") == "value3"
assert cache.get("key4") == "value4"
def test_clear(self):
"""Test clear cache"""
cache = LRUCache()
cache.set("key1", "value1")
cache.set("key2", "value2")
cache.clear()
assert len(cache.cache) == 0
assert cache.get("key1") is None
def test_get_stats(self):
"""Test get cache statistics"""
cache = LRUCache(capacity=10)
cache.set("key1", "value1")
cache.get("key1")
cache.get("key2") # miss
stats = cache.get_stats()
assert stats["capacity"] == 10
assert stats["size"] == 1
assert stats["hits"] == 1
assert stats["misses"] == 1
assert stats["hit_rate"] == 0.5
def test_get_stats_empty(self):
"""Test get stats on empty cache"""
cache = LRUCache()
stats = cache.get_stats()
assert stats["hit_rate"] == 0
@patch('aitbc.caching.logger')
def test_print_stats(self, mock_logger):
"""Test print stats logs output"""
cache = LRUCache()
cache.set("key1", "value1")
cache.print_stats()
assert mock_logger.info.called
def test_lru_ordering(self):
"""Test that recently used items are moved to end"""
cache = LRUCache(capacity=3)
cache.set("key1", "value1")
cache.set("key2", "value2")
cache.set("key3", "value3")
# Access key1 to make it recently used
cache.get("key1")
# Add key4, should evict key2 (not key1)
cache.set("key4", "value4")
assert cache.get("key1") == "value1" # Still in cache
assert cache.get("key2") is None # Evicted
class TestTTLCache:
"""Tests for TTLCache"""
def test_initialization(self):
"""Test TTLCache initialization"""
cache = TTLCache(default_ttl=60)
assert cache.default_ttl == 60
assert len(cache.cache) == 0
def test_get_miss(self):
"""Test get when key not in cache"""
cache = TTLCache()
result = cache.get("nonexistent")
assert result is None
assert cache._misses == 1
def test_get_hit(self):
"""Test get when key in cache"""
cache = TTLCache(default_ttl=60)
cache.set("key1", "value1")
result = cache.get("key1")
assert result == "value1"
assert cache._hits == 1
def test_get_expired(self):
"""Test get when entry expired"""
cache = TTLCache(default_ttl=60)
cache.set("key1", "value1")
# Manually set expiration to past
cache.cache["key1"].expires_at = datetime.now() - timedelta(seconds=1)
result = cache.get("key1")
assert result is None
assert cache._misses == 1
def test_set_with_default_ttl(self):
"""Test set uses default TTL"""
cache = TTLCache(default_ttl=60)
cache.set("key1", "value1")
entry = cache.cache["key1"]
assert entry.expires_at is not None
assert entry.expires_at > datetime.now()
def test_set_with_custom_ttl(self):
"""Test set with custom TTL"""
cache = TTLCache(default_ttl=60)
cache.set("key1", "value1", ttl=30)
entry = cache.cache["key1"]
assert entry.expires_at is not None
expected_expires = datetime.now() + timedelta(seconds=30)
assert abs((entry.expires_at - expected_expires).total_seconds()) < 1
def test_set_overwrite(self):
"""Test set overwrites existing key"""
cache = TTLCache()
cache.set("key1", "value1")
cache.set("key1", "value2")
assert cache.get("key1") == "value2"
def test_clear(self):
"""Test clear cache"""
cache = TTLCache()
cache.set("key1", "value1")
cache.clear()
assert len(cache.cache) == 0
def test_cleanup_expired(self):
"""Test cleanup expired entries"""
cache = TTLCache(default_ttl=60)
cache.set("key1", "value1")
cache.set("key2", "value2")
# Expire key1
cache.cache["key1"].expires_at = datetime.now() - timedelta(seconds=1)
removed = cache.cleanup_expired()
assert removed == 1
assert cache.get("key1") is None
assert cache.get("key2") == "value2"
def test_cleanup_expired_none(self):
"""Test cleanup when no expired entries"""
cache = TTLCache()
cache.set("key1", "value1")
removed = cache.cleanup_expired()
assert removed == 0
def test_get_stats(self):
"""Test get cache statistics"""
cache = TTLCache(default_ttl=60)
cache.set("key1", "value1")
cache.get("key1")
cache.get("key2") # miss
stats = cache.get_stats()
assert stats["size"] == 1
assert stats["default_ttl"] == 60
assert stats["hits"] == 1
assert stats["misses"] == 1
assert stats["hit_rate"] == 0.5
class TestCacheDecorators:
"""Tests for cache decorators"""
def test_cached_decorator(self):
"""Test cached decorator"""
call_count = [0]
@cached(ttl=60)
def expensive_function(x):
call_count[0] += 1
return x * 2
# First call executes function
result1 = expensive_function(5)
assert result1 == 10
assert call_count[0] == 1
# Second call uses cache
result2 = expensive_function(5)
assert result2 == 10
assert call_count[0] == 1 # Should not increment
def test_cached_decorator_different_args(self):
"""Test cached decorator with different arguments"""
call_count = [0]
@cached(ttl=60)
def expensive_function(x):
call_count[0] += 1
return x * 2
expensive_function(5)
expensive_function(10)
assert call_count[0] == 2 # Different args, different cache keys
def test_cached_decorator_with_custom_cache(self):
"""Test cached decorator with custom cache instance"""
call_count = [0]
custom_cache = TTLCache(default_ttl=60)
@cached(ttl=60, cache_instance=custom_cache)
def expensive_function(x):
call_count[0] += 1
return x * 2
expensive_function(5)
expensive_function(5)
assert call_count[0] == 1
def test_cached_lru_decorator(self):
"""Test cached_lru decorator"""
call_count = [0]
@cached_lru(capacity=10)
def expensive_function(x):
call_count[0] += 1
return x * 2
expensive_function(5)
expensive_function(5)
assert call_count[0] == 1
def test_cached_lru_decorator_with_ttl(self):
"""Test cached_lru decorator with TTL"""
call_count = [0]
@cached_lru(capacity=10, ttl=1)
def expensive_function(x):
call_count[0] += 1
return x * 2
expensive_function(5)
expensive_function(5)
assert call_count[0] == 1
# Wait for expiration
time.sleep(1.1)
expensive_function(5)
assert call_count[0] == 2 # Should re-execute after expiration
def test_cached_lru_decorator_eviction(self):
"""Test cached_lru decorator eviction"""
call_count = [0]
@cached_lru(capacity=2)
def expensive_function(x):
call_count[0] += 1
return x * 2
expensive_function(1)
expensive_function(2)
expensive_function(3) # Should evict least recently used
expensive_function(1) # Should re-execute
assert call_count[0] == 4 # All calls executed due to eviction
def test_decorator_cache_attachment(self):
"""Test that cache is attached to decorated function"""
@cached(ttl=60)
def func(x):
return x * 2
assert hasattr(func, 'cache')
assert isinstance(func.cache, TTLCache)
class TestCacheKeyGeneration:
"""Tests for cache key generation"""
def test_generate_cache_key_simple_args(self):
"""Test cache key with simple arguments"""
key = _generate_cache_key("func_name", (1, 2, 3), {})
assert "func_name" in key
assert "1" in key
assert "2" in key
assert "3" in key
def test_generate_cache_key_with_kwargs(self):
"""Test cache key with keyword arguments"""
key = _generate_cache_key("func_name", (), {"x": 1, "y": 2})
assert "x=1" in key
assert "y=2" in key
def test_generate_cache_key_complex_args(self):
"""Test cache key with complex arguments"""
key = _generate_cache_key("func_name", ([1, 2], {"a": 3}), {})
# Complex args should be hashed
assert "func_name" in key
assert len(key.split(":")) == 3 # func_name + 2 hashed args
def test_generate_cache_key_consistency(self):
"""Test cache key generation is consistent"""
key1 = _generate_cache_key("func", (1, 2), {"x": 3})
key2 = _generate_cache_key("func", (1, 2), {"x": 3})
assert key1 == key2
def test_generate_cache_key_different_order(self):
"""Test cache key with different kwarg order"""
key1 = _generate_cache_key("func", (), {"x": 1, "y": 2})
key2 = _generate_cache_key("func", (), {"y": 2, "x": 1})
assert key1 == key2 # Should be same due to sorting
class TestGlobalCaches:
"""Tests for global cache instances"""
def test_get_global_lru_cache(self):
"""Test get global LRU cache"""
cache = get_global_lru_cache()
assert isinstance(cache, LRUCache)
assert cache.capacity == 256
def test_get_global_ttl_cache(self):
"""Test get global TTL cache"""
cache = get_global_ttl_cache()
assert isinstance(cache, TTLCache)
assert cache.default_ttl == 300
def test_global_caches_singleton(self):
"""Test global caches are singletons"""
cache1 = get_global_lru_cache()
cache2 = get_global_lru_cache()
assert cache1 is cache2
def test_clear_global_caches(self):
"""Test clear all global caches"""
lru_cache = get_global_lru_cache()
ttl_cache = get_global_ttl_cache()
lru_cache.set("key1", "value1")
ttl_cache.set("key2", "value2")
clear_global_caches()
assert lru_cache.get("key1") is None
assert ttl_cache.get("key2") is None
@patch('aitbc.caching.logger')
def test_clear_global_caches_logging(self, mock_logger):
"""Test clear global caches logs"""
clear_global_caches()
assert mock_logger.info.called