import pytest import hashlib from unittest.mock import MagicMock from ankigen.utils import ( ResponseCache, RateLimiter, strip_html_tags, fetch_webpage_text, setup_logging, get_logger, ) import requests # --- ResponseCache Tests --- def test_cache_set_get(): cache = ResponseCache(maxsize=2) cache.set("prompt1", "model1", "response1") assert cache.get("prompt1", "model1") == "response1" assert cache.hits == 1 assert cache.misses == 0 def test_cache_miss(): cache = ResponseCache(maxsize=2) assert cache.get("nonexistent", "model1") is None assert cache.hits == 0 assert cache.misses == 1 def test_cache_eviction(): cache = ResponseCache(maxsize=2) cache.set("p1", "m1", "r1") cache.set("p2", "m2", "r2") cache.set("p3", "m3", "r3") # Should evict p1 assert cache.get("p1", "m1") is None assert cache.get("p2", "m2") == "r2" assert cache.get("p3", "m3") == "r3" def test_cache_lru_update(): cache = ResponseCache(maxsize=2) cache.set("p1", "m1", "r1") cache.set("p2", "m2", "r2") cache.get("p1", "m1") # p1 is now MRU cache.set("p3", "m3", "r3") # Should evict p2 assert cache.get("p2", "m2") is None assert cache.get("p1", "m1") == "r1" assert cache.get("p3", "m3") == "r3" def test_cache_clear(): cache = ResponseCache(maxsize=2) cache.set("p1", "m1", "r1") cache.hits = 5 cache.misses = 2 cache.clear() assert cache.get("p1", "m1") is None assert cache.hits == 0 assert cache.misses == 1 # Miss from the get() call above def test_cache_key_hashing(): cache = ResponseCache() prompt = "test prompt" model = "gpt-4" expected_key = hashlib.md5(f"{model}:{prompt}".encode("utf-8")).hexdigest() assert cache._create_key(prompt, model) == expected_key # --- RateLimiter Tests --- def test_rate_limiter_init_invalid(): with pytest.raises(ValueError, match="Requests per second must be positive."): RateLimiter(0) with pytest.raises(ValueError, match="Requests per second must be positive."): RateLimiter(-1) def test_rate_limiter_wait(mocker): # Mock time.monotonic and time.sleep mock_monotonic = mocker.patch("time.monotonic") mock_sleep = mocker.patch("time.sleep") # First call: current_time = 10.0, last_request = 0.0 # Interval = 1.0 (for 1 req/sec) # diff = 10.0 >= 1.0, no sleep mock_monotonic.side_effect = [10.0, 10.1, 10.2, 10.3] limiter = RateLimiter(1.0) limiter.wait() assert mock_sleep.call_count == 0 # Second call: current_time = 10.2, last_request = 10.1 (from end of first wait) # diff = 0.1 < 1.0, sleep for 0.9 limiter.wait() mock_sleep.assert_called_once_with(pytest.approx(0.9)) # --- strip_html_tags Tests --- def test_strip_html_tags_normal(): html = "