7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
247 lines
7.8 KiB
Python
247 lines
7.8 KiB
Python
"""Tests for metrics pricing_cache module."""
|
|
|
|
import time
|
|
|
|
|
|
from local_deep_research.metrics.pricing.pricing_cache import PricingCache
|
|
|
|
|
|
class TestPricingCacheInit:
|
|
"""Tests for PricingCache initialization."""
|
|
|
|
def test_initializes_with_default_ttl(self):
|
|
"""Should initialize with default TTL of 3600 seconds."""
|
|
cache = PricingCache()
|
|
assert cache.cache_ttl == 3600
|
|
|
|
def test_initializes_with_custom_ttl(self):
|
|
"""Should initialize with custom TTL."""
|
|
cache = PricingCache(cache_ttl=7200)
|
|
assert cache.cache_ttl == 7200
|
|
|
|
def test_initializes_empty_cache(self):
|
|
"""Should initialize with empty in-memory cache."""
|
|
cache = PricingCache()
|
|
assert len(cache._cache) == 0
|
|
|
|
def test_cache_dir_is_deprecated(self):
|
|
"""Cache dir parameter should be ignored (deprecated)."""
|
|
cache = PricingCache(cache_dir="/some/path")
|
|
# Should not raise and should work normally
|
|
assert len(cache._cache) == 0
|
|
|
|
|
|
class TestPricingCacheGetSet:
|
|
"""Tests for PricingCache get/set methods."""
|
|
|
|
def test_get_returns_none_for_missing_key(self):
|
|
"""Should return None for non-existent key."""
|
|
cache = PricingCache()
|
|
result = cache.get("nonexistent")
|
|
assert result is None
|
|
|
|
def test_set_stores_data(self):
|
|
"""Should store data in cache."""
|
|
cache = PricingCache()
|
|
cache.set("test_key", {"value": 123})
|
|
|
|
assert "test_key" in cache._cache
|
|
assert cache._cache["test_key"] == {"value": 123}
|
|
|
|
def test_get_returns_valid_data(self):
|
|
"""Should return data for valid key."""
|
|
cache = PricingCache()
|
|
cache.set("test_key", {"value": 456})
|
|
|
|
result = cache.get("test_key")
|
|
assert result == {"value": 456}
|
|
|
|
def test_get_returns_none_for_expired_key(self):
|
|
"""Should return None and remove expired entries."""
|
|
# audit: PUNCHLIST reviewed 2026-05 — KEEP (TIMING_SLEEP).
|
|
cache = PricingCache(cache_ttl=1) # 1 second TTL
|
|
cache.set("test_key", {"value": 789})
|
|
|
|
# Wait for expiration
|
|
time.sleep(1.1) # allow: unmarked-sleep
|
|
|
|
result = cache.get("test_key")
|
|
assert result is None
|
|
assert "test_key" not in cache._cache
|
|
|
|
|
|
class TestPricingCacheModelPricing:
|
|
"""Tests for model pricing methods."""
|
|
|
|
def test_get_model_pricing_returns_none_for_missing(self):
|
|
"""Should return None for missing model pricing."""
|
|
cache = PricingCache()
|
|
result = cache.get_model_pricing("gpt-4")
|
|
assert result is None
|
|
|
|
def test_set_model_pricing_stores_with_prefix(self):
|
|
"""Should store pricing with 'model:' prefix."""
|
|
cache = PricingCache()
|
|
pricing = {"prompt": 0.03, "completion": 0.06}
|
|
cache.set_model_pricing("gpt-4", pricing)
|
|
|
|
assert "model:gpt-4" in cache._cache
|
|
assert cache._cache["model:gpt-4"] == pricing
|
|
|
|
def test_get_model_pricing_retrieves_stored(self):
|
|
"""Should retrieve stored model pricing."""
|
|
cache = PricingCache()
|
|
pricing = {"prompt": 0.03, "completion": 0.06}
|
|
cache.set_model_pricing("gpt-4", pricing)
|
|
|
|
result = cache.get_model_pricing("gpt-4")
|
|
assert result == pricing
|
|
|
|
|
|
class TestPricingCacheAllPricing:
|
|
"""Tests for all pricing methods."""
|
|
|
|
def test_get_all_pricing_returns_none_when_not_set(self):
|
|
"""Should return None when all pricing not cached."""
|
|
cache = PricingCache()
|
|
result = cache.get_all_pricing()
|
|
assert result is None
|
|
|
|
def test_set_all_pricing_stores_data(self):
|
|
"""Should store all pricing data."""
|
|
cache = PricingCache()
|
|
all_pricing = {
|
|
"gpt-4": {"prompt": 0.03, "completion": 0.06},
|
|
"gpt-3.5-turbo": {"prompt": 0.001, "completion": 0.002},
|
|
}
|
|
cache.set_all_pricing(all_pricing)
|
|
|
|
assert "all_models" in cache._cache
|
|
assert cache._cache["all_models"] == all_pricing
|
|
|
|
def test_get_all_pricing_retrieves_stored(self):
|
|
"""Should retrieve stored all pricing."""
|
|
cache = PricingCache()
|
|
all_pricing = {
|
|
"gpt-4": {"prompt": 0.03, "completion": 0.06},
|
|
}
|
|
cache.set_all_pricing(all_pricing)
|
|
|
|
result = cache.get_all_pricing()
|
|
assert result == all_pricing
|
|
|
|
|
|
class TestPricingCacheClear:
|
|
"""Tests for cache clearing methods."""
|
|
|
|
def test_clear_removes_all_entries(self):
|
|
"""Should remove all cache entries."""
|
|
cache = PricingCache()
|
|
cache.set("key1", "value1")
|
|
cache.set("key2", "value2")
|
|
|
|
cache.clear()
|
|
|
|
assert len(cache._cache) == 0
|
|
|
|
def test_clear_expired_removes_only_expired(self):
|
|
"""Should remove only expired entries."""
|
|
# audit: PUNCHLIST reviewed 2026-05 — KEEP (TIMING_SLEEP).
|
|
cache = PricingCache(cache_ttl=2) # 2 second TTL
|
|
cache.set("old_key", "old_value")
|
|
|
|
# Wait for old entry to expire
|
|
time.sleep(2.1) # allow: unmarked-sleep
|
|
|
|
# Add new entry
|
|
cache.set("new_key", "new_value")
|
|
|
|
cache.clear_expired()
|
|
|
|
assert "old_key" not in cache._cache
|
|
assert "new_key" in cache._cache
|
|
|
|
def test_clear_expired_handles_empty_cache(self):
|
|
"""Should handle empty cache without error."""
|
|
cache = PricingCache()
|
|
cache.clear_expired() # Should not raise
|
|
assert len(cache._cache) == 0
|
|
|
|
|
|
class TestPricingCacheStats:
|
|
"""Tests for cache statistics."""
|
|
|
|
def test_get_cache_stats_returns_correct_structure(self):
|
|
"""Should return stats with correct keys."""
|
|
cache = PricingCache(cache_ttl=3600)
|
|
stats = cache.get_cache_stats()
|
|
|
|
assert "total_entries" in stats
|
|
assert "max_entries" in stats
|
|
assert "cache_type" in stats
|
|
assert "cache_ttl" in stats
|
|
|
|
def test_get_cache_stats_counts_entries(self):
|
|
"""Should count total entries correctly."""
|
|
cache = PricingCache()
|
|
cache.set("key1", "value1")
|
|
cache.set("key2", "value2")
|
|
cache.set("key3", "value3")
|
|
|
|
stats = cache.get_cache_stats()
|
|
|
|
assert stats["total_entries"] == 3
|
|
|
|
def test_get_cache_stats_expired_entries_evicted(self):
|
|
"""TTLCache automatically evicts expired entries on get_cache_stats."""
|
|
# audit: PUNCHLIST reviewed 2026-05 — KEEP (TIMING_SLEEP).
|
|
cache = PricingCache(cache_ttl=1)
|
|
cache.set("key1", "value1")
|
|
|
|
time.sleep(1.1) # allow: unmarked-sleep
|
|
|
|
stats = cache.get_cache_stats()
|
|
|
|
# TTLCache evicts expired entries automatically when expire() is called
|
|
assert stats["total_entries"] == 0
|
|
|
|
def test_get_cache_stats_shows_cache_type(self):
|
|
"""Should show TTLCache cache type."""
|
|
cache = PricingCache()
|
|
stats = cache.get_cache_stats()
|
|
|
|
assert stats["cache_type"] == "TTLCache"
|
|
|
|
def test_get_cache_stats_shows_ttl(self):
|
|
"""Should show configured TTL."""
|
|
cache = PricingCache(cache_ttl=7200)
|
|
stats = cache.get_cache_stats()
|
|
|
|
assert stats["cache_ttl"] == 7200
|
|
|
|
|
|
class TestPricingCacheTTL:
|
|
"""Tests for TTLCache behavior."""
|
|
|
|
def test_cache_respects_ttl(self):
|
|
"""Should respect TTL for entries."""
|
|
# audit: PUNCHLIST reviewed 2026-05 — KEEP (TIMING_SLEEP).
|
|
cache = PricingCache(cache_ttl=1)
|
|
cache.set("key1", "value1")
|
|
|
|
# Entry should be available immediately
|
|
assert cache.get("key1") == "value1"
|
|
|
|
# Wait for expiration
|
|
time.sleep(1.1) # allow: unmarked-sleep
|
|
|
|
# Entry should be expired
|
|
assert cache.get("key1") is None
|
|
|
|
def test_cache_respects_maxsize(self):
|
|
"""Should respect maxsize by evicting old entries."""
|
|
# Create cache with small maxsize
|
|
cache = PricingCache()
|
|
# TTLCache with maxsize=500 is default
|
|
assert cache._cache.maxsize == 500
|