"""Unit tests for app.cache LRU cache wrapper.""" from datetime import date from app.cache import LRUCache def _key(ticker: str, indicator: str = "RSI") -> tuple: return (ticker, date(2024, 1, 1), date(2024, 6, 1), indicator) class TestLRUCacheBasics: def test_get_miss_returns_none(self): cache = LRUCache() assert cache.get(_key("AAPL")) is None def test_set_and_get_round_trip(self): cache = LRUCache() cache.set(_key("AAPL"), {"score": 72}) assert cache.get(_key("AAPL")) == {"score": 72} def test_set_overwrites_existing(self): cache = LRUCache() cache.set(_key("AAPL"), 1) cache.set(_key("AAPL"), 2) assert cache.get(_key("AAPL")) == 2 assert cache.size == 1 def test_size_and_clear(self): cache = LRUCache(max_size=5) for i in range(3): cache.set((f"T{i}", None, None, "RSI"), i) assert cache.size == 3 cache.clear() assert cache.size == 0 class TestLRUEviction: def test_evicts_lru_when_full(self): cache = LRUCache(max_size=3) cache.set(_key("A"), 1) cache.set(_key("B"), 2) cache.set(_key("C"), 3) # A is LRU — inserting D should evict A cache.set(_key("D"), 4) assert cache.get(_key("A")) is None assert cache.size == 3 def test_access_promotes_entry(self): cache = LRUCache(max_size=3) cache.set(_key("A"), 1) cache.set(_key("B"), 2) cache.set(_key("C"), 3) # Access A so B becomes LRU cache.get(_key("A")) cache.set(_key("D"), 4) assert cache.get(_key("B")) is None assert cache.get(_key("A")) == 1 def test_update_promotes_entry(self): cache = LRUCache(max_size=3) cache.set(_key("A"), 1) cache.set(_key("B"), 2) cache.set(_key("C"), 3) # Update A so B becomes LRU cache.set(_key("A"), 10) cache.set(_key("D"), 4) assert cache.get(_key("B")) is None assert cache.get(_key("A")) == 10 class TestTickerInvalidation: def test_invalidate_removes_all_entries_for_ticker(self): cache = LRUCache() cache.set(("AAPL", date(2024, 1, 1), date(2024, 6, 1), "RSI"), 1) cache.set(("AAPL", date(2024, 1, 1), date(2024, 6, 1), "ADX"), 2) cache.set(("MSFT", date(2024, 1, 1), date(2024, 6, 1), "RSI"), 3) removed = cache.invalidate_ticker("AAPL") assert removed == 2 assert cache.get(("AAPL", date(2024, 1, 1), date(2024, 6, 1), "RSI")) is None assert cache.get(("AAPL", date(2024, 1, 1), date(2024, 6, 1), "ADX")) is None assert cache.get(("MSFT", date(2024, 1, 1), date(2024, 6, 1), "RSI")) == 3 def test_invalidate_nonexistent_ticker_returns_zero(self): cache = LRUCache() cache.set(_key("AAPL"), 1) assert cache.invalidate_ticker("GOOG") == 0 assert cache.size == 1 def test_invalidate_on_empty_cache(self): cache = LRUCache() assert cache.invalidate_ticker("AAPL") == 0 class TestMaxSizeProperty: def test_default_max_size(self): cache = LRUCache() assert cache.max_size == 1000 def test_custom_max_size(self): cache = LRUCache(max_size=50) assert cache.max_size == 50