"""SP-A2 — webai_cache module의 cache hit/miss + key 분기 검증.""" import time import pytest from app.webai_cache import ( PORTFOLIO_CACHE, NEWS_CACHE, SCREENER_CACHE, cache_get_portfolio, cache_set_portfolio, cache_get_news, cache_set_news, cache_get_screener, cache_set_screener, _screener_key, ) def _clear_all(): PORTFOLIO_CACHE.clear() NEWS_CACHE.clear() SCREENER_CACHE.clear() def test_portfolio_cache_miss_then_hit(): _clear_all() assert cache_get_portfolio() is None cache_set_portfolio({"holdings": [], "cash": 0}) assert cache_get_portfolio() == {"holdings": [], "cash": 0} def test_news_cache_key_by_date(): """date가 다르면 별도 캐시 슬롯.""" _clear_all() cache_set_news("2026-05-18", {"count": 5}) cache_set_news("2026-05-17", {"count": 3}) assert cache_get_news("2026-05-18") == {"count": 5} assert cache_get_news("2026-05-17") == {"count": 3} assert cache_get_news("2026-05-16") is None # not cached def test_news_cache_latest_key_normalized(): """date=None은 'latest' 키로 정규화되어 동일 슬롯.""" _clear_all() cache_set_news(None, {"count": 9}) assert cache_get_news(None) == {"count": 9} def test_screener_key_includes_mode_and_top_n(): """screener key는 mode + top_n + weights hash로 분기.""" k_preview = _screener_key("preview", 20, None) k_preview_w = _screener_key("preview", 20, {"news": 0.3}) k_auto = _screener_key("auto", 20, None) assert k_preview != k_preview_w assert k_preview != k_auto def test_screener_cache_roundtrip(): _clear_all() payload = {"asof": "2026-05-18", "survivors_count": 17} cache_set_screener("preview", 20, None, payload) assert cache_get_screener("preview", 20, None) == payload assert cache_get_screener("preview", 20, {"news": 0.3}) is None def test_ttl_expiry_portfolio(): """짧은 ttl로 만료 확인 — 직접 시간 조작 대신 TTLCache 내부 동작 신뢰.""" from cachetools import TTLCache short = TTLCache(maxsize=1, ttl=0.1) # 0.1초 short["result"] = "x" assert short.get("result") == "x" time.sleep(0.2) assert short.get("result") is None