|
6 | 6 | from unittest.mock import MagicMock, patch |
7 | 7 |
|
8 | 8 |
|
9 | | -class TestRedisCache: |
10 | | - """Tests for RedisCache class.""" |
| 9 | +class TestMemoryCache: |
| 10 | + """Tests for MemoryCache class.""" |
11 | 11 |
|
12 | | - @patch("src.utils.cache.redis.from_url") |
13 | | - def test_connect_success(self, mock_from_url): |
14 | | - """Test successful Redis connection.""" |
15 | | - from src.utils.cache import RedisCache |
16 | | - |
17 | | - mock_client = MagicMock() |
18 | | - mock_from_url.return_value = mock_client |
19 | | - |
20 | | - cache = RedisCache("redis://localhost:6379", default_ttl=3600) |
21 | | - client = cache._connect() |
22 | | - |
23 | | - assert client is not None |
24 | | - assert cache._connected is True |
25 | | - |
26 | | - @patch("src.utils.cache.redis.from_url") |
27 | | - def test_connect_failure(self, mock_from_url): |
28 | | - """Test Redis connection failure.""" |
29 | | - import redis |
30 | | - from src.utils.cache import RedisCache |
31 | | - |
32 | | - mock_from_url.side_effect = redis.ConnectionError("Connection refused") |
33 | | - |
34 | | - cache = RedisCache("redis://localhost:6379") |
35 | | - client = cache._connect() |
36 | | - |
37 | | - assert client is None |
38 | | - assert cache._connected is False |
39 | | - |
40 | | - @patch("src.utils.cache.redis.from_url") |
41 | | - def test_connect_cached(self, mock_from_url): |
42 | | - """Test that connection is reused.""" |
43 | | - from src.utils.cache import RedisCache |
44 | | - |
45 | | - mock_client = MagicMock() |
46 | | - mock_from_url.return_value = mock_client |
47 | | - |
48 | | - cache = RedisCache("redis://localhost:6379") |
49 | | - cache._connect() |
50 | | - cache._connect() |
| 12 | + def test_init(self): |
| 13 | + """Test cache initialization.""" |
| 14 | + from src.utils.cache import MemoryCache |
51 | 15 |
|
52 | | - # Should only connect once |
53 | | - assert mock_from_url.call_count == 1 |
| 16 | + cache = MemoryCache(default_ttl=3600) |
| 17 | + assert cache._default_ttl == 3600 |
| 18 | + assert cache._cache == {} |
54 | 19 |
|
55 | 20 | def test_make_key(self): |
56 | 21 | """Test cache key generation.""" |
57 | | - from src.utils.cache import RedisCache |
| 22 | + from src.utils.cache import MemoryCache |
58 | 23 |
|
59 | | - key1 = RedisCache._make_key("prefix", "arg1", kwarg1="value1") |
60 | | - key2 = RedisCache._make_key("prefix", "arg1", kwarg1="value1") |
61 | | - key3 = RedisCache._make_key("prefix", "arg2", kwarg1="value1") |
| 24 | + key1 = MemoryCache._make_key("prefix", "arg1", kwarg1="value1") |
| 25 | + key2 = MemoryCache._make_key("prefix", "arg1", kwarg1="value1") |
| 26 | + key3 = MemoryCache._make_key("prefix", "arg2", kwarg1="value1") |
62 | 27 |
|
63 | 28 | assert key1 == key2 # Same args = same key |
64 | 29 | assert key1 != key3 # Different args = different key |
65 | 30 | assert key1.startswith("prefix:") |
66 | 31 |
|
67 | | - @patch("src.utils.cache.redis.from_url") |
68 | | - def test_get_success(self, mock_from_url): |
69 | | - """Test successful cache get.""" |
70 | | - from src.utils.cache import RedisCache |
71 | | - |
72 | | - mock_client = MagicMock() |
73 | | - mock_from_url.return_value = mock_client |
74 | | - mock_client.get.return_value = '{"key": "value"}' |
| 32 | + def test_set_and_get(self): |
| 33 | + """Test basic set and get operations.""" |
| 34 | + from src.utils.cache import MemoryCache |
75 | 35 |
|
76 | | - cache = RedisCache("redis://localhost:6379") |
77 | | - result = cache.get("test_key") |
78 | | - |
79 | | - assert result == {"key": "value"} |
| 36 | + cache = MemoryCache(default_ttl=3600) |
| 37 | + |
| 38 | + result = cache.set("test_key", {"data": "value"}) |
| 39 | + assert result is True |
| 40 | + |
| 41 | + value = cache.get("test_key") |
| 42 | + assert value == {"data": "value"} |
80 | 43 |
|
81 | | - @patch("src.utils.cache.redis.from_url") |
82 | | - def test_get_miss(self, mock_from_url): |
| 44 | + def test_get_miss(self): |
83 | 45 | """Test cache miss.""" |
84 | | - from src.utils.cache import RedisCache |
85 | | - |
86 | | - mock_client = MagicMock() |
87 | | - mock_from_url.return_value = mock_client |
88 | | - mock_client.get.return_value = None |
89 | | - |
90 | | - cache = RedisCache("redis://localhost:6379") |
91 | | - result = cache.get("test_key") |
| 46 | + from src.utils.cache import MemoryCache |
92 | 47 |
|
| 48 | + cache = MemoryCache() |
| 49 | + result = cache.get("nonexistent_key") |
93 | 50 | assert result is None |
94 | 51 |
|
95 | | - @patch("src.utils.cache.redis.from_url") |
96 | | - def test_get_error(self, mock_from_url): |
97 | | - """Test cache get error handling.""" |
98 | | - import redis |
99 | | - from src.utils.cache import RedisCache |
| 52 | + def test_get_expired(self): |
| 53 | + """Test expired cache entry.""" |
| 54 | + from src.utils.cache import MemoryCache |
100 | 55 |
|
101 | | - mock_client = MagicMock() |
102 | | - mock_from_url.return_value = mock_client |
103 | | - mock_client.get.side_effect = redis.RedisError("Error") |
104 | | - |
105 | | - cache = RedisCache("redis://localhost:6379") |
106 | | - result = cache.get("test_key") |
107 | | - |
108 | | - assert result is None |
109 | | - |
110 | | - @patch("src.utils.cache.redis.from_url") |
111 | | - def test_get_json_error(self, mock_from_url): |
112 | | - """Test cache get with invalid JSON.""" |
113 | | - from src.utils.cache import RedisCache |
114 | | - |
115 | | - mock_client = MagicMock() |
116 | | - mock_from_url.return_value = mock_client |
117 | | - mock_client.get.return_value = "invalid json {" |
118 | | - |
119 | | - cache = RedisCache("redis://localhost:6379") |
120 | | - result = cache.get("test_key") |
121 | | - |
122 | | - assert result is None |
123 | | - |
124 | | - @patch("src.utils.cache.redis.from_url") |
125 | | - def test_get_no_connection(self, mock_from_url): |
126 | | - """Test cache get with no connection.""" |
127 | | - import redis |
128 | | - from src.utils.cache import RedisCache |
129 | | - |
130 | | - mock_from_url.side_effect = redis.ConnectionError() |
131 | | - |
132 | | - cache = RedisCache("redis://localhost:6379") |
| 56 | + cache = MemoryCache(default_ttl=1) |
| 57 | + cache.set("test_key", {"data": "value"}, ttl=0) # Immediate expiry |
| 58 | + |
| 59 | + # Wait a bit for expiry |
| 60 | + time.sleep(0.1) |
| 61 | + |
133 | 62 | result = cache.get("test_key") |
134 | | - |
135 | 63 | assert result is None |
136 | 64 |
|
137 | | - @patch("src.utils.cache.redis.from_url") |
138 | | - def test_set_success(self, mock_from_url): |
139 | | - """Test successful cache set.""" |
140 | | - from src.utils.cache import RedisCache |
141 | | - |
142 | | - mock_client = MagicMock() |
143 | | - mock_from_url.return_value = mock_client |
144 | | - |
145 | | - cache = RedisCache("redis://localhost:6379", default_ttl=3600) |
146 | | - result = cache.set("test_key", {"data": "value"}) |
147 | | - |
148 | | - assert result is True |
149 | | - mock_client.setex.assert_called_once() |
150 | | - |
151 | | - @patch("src.utils.cache.redis.from_url") |
152 | | - def test_set_custom_ttl(self, mock_from_url): |
| 65 | + def test_set_custom_ttl(self): |
153 | 66 | """Test cache set with custom TTL.""" |
154 | | - from src.utils.cache import RedisCache |
155 | | - |
156 | | - mock_client = MagicMock() |
157 | | - mock_from_url.return_value = mock_client |
| 67 | + from src.utils.cache import MemoryCache |
158 | 68 |
|
159 | | - cache = RedisCache("redis://localhost:6379", default_ttl=3600) |
| 69 | + cache = MemoryCache(default_ttl=3600) |
160 | 70 | cache.set("test_key", {"data": "value"}, ttl=7200) |
161 | | - |
162 | | - mock_client.setex.assert_called_with("test_key", 7200, '{"data": "value"}') |
163 | | - |
164 | | - @patch("src.utils.cache.redis.from_url") |
165 | | - def test_set_error(self, mock_from_url): |
166 | | - """Test cache set error handling.""" |
167 | | - import redis |
168 | | - from src.utils.cache import RedisCache |
169 | | - |
170 | | - mock_client = MagicMock() |
171 | | - mock_from_url.return_value = mock_client |
172 | | - mock_client.setex.side_effect = redis.RedisError("Error") |
173 | | - |
174 | | - cache = RedisCache("redis://localhost:6379") |
175 | | - result = cache.set("test_key", {"data": "value"}) |
176 | | - |
177 | | - assert result is False |
178 | | - |
179 | | - @patch("src.utils.cache.redis.from_url") |
180 | | - def test_set_no_connection(self, mock_from_url): |
181 | | - """Test cache set with no connection.""" |
182 | | - import redis |
183 | | - from src.utils.cache import RedisCache |
184 | | - |
185 | | - mock_from_url.side_effect = redis.ConnectionError() |
186 | | - |
187 | | - cache = RedisCache("redis://localhost:6379") |
188 | | - result = cache.set("test_key", {"data": "value"}) |
189 | | - |
190 | | - assert result is False |
191 | | - |
192 | | - @patch("src.utils.cache.redis.from_url") |
193 | | - def test_delete_success(self, mock_from_url): |
194 | | - """Test successful cache delete.""" |
195 | | - from src.utils.cache import RedisCache |
196 | | - |
197 | | - mock_client = MagicMock() |
198 | | - mock_from_url.return_value = mock_client |
199 | | - |
200 | | - cache = RedisCache("redis://localhost:6379") |
| 71 | + |
| 72 | + value, expiry = cache._cache["test_key"] |
| 73 | + assert value == {"data": "value"} |
| 74 | + # Expiry should be ~7200 seconds in the future |
| 75 | + assert expiry > time.time() + 7000 |
| 76 | + |
| 77 | + def test_delete_existing(self): |
| 78 | + """Test deleting existing key.""" |
| 79 | + from src.utils.cache import MemoryCache |
| 80 | + |
| 81 | + cache = MemoryCache() |
| 82 | + cache.set("test_key", "value") |
| 83 | + |
201 | 84 | result = cache.delete("test_key") |
202 | | - |
203 | 85 | assert result is True |
204 | | - mock_client.delete.assert_called_with("test_key") |
205 | | - |
206 | | - @patch("src.utils.cache.redis.from_url") |
207 | | - def test_delete_error(self, mock_from_url): |
208 | | - """Test cache delete error handling.""" |
209 | | - import redis |
210 | | - from src.utils.cache import RedisCache |
211 | | - |
212 | | - mock_client = MagicMock() |
213 | | - mock_from_url.return_value = mock_client |
214 | | - mock_client.delete.side_effect = redis.RedisError("Error") |
215 | | - |
216 | | - cache = RedisCache("redis://localhost:6379") |
217 | | - result = cache.delete("test_key") |
218 | | - |
219 | | - assert result is False |
220 | | - |
221 | | - @patch("src.utils.cache.redis.from_url") |
222 | | - def test_delete_no_connection(self, mock_from_url): |
223 | | - """Test cache delete with no connection.""" |
224 | | - import redis |
225 | | - from src.utils.cache import RedisCache |
| 86 | + assert cache.get("test_key") is None |
226 | 87 |
|
227 | | - mock_from_url.side_effect = redis.ConnectionError() |
228 | | - |
229 | | - cache = RedisCache("redis://localhost:6379") |
230 | | - result = cache.delete("test_key") |
| 88 | + def test_delete_nonexistent(self): |
| 89 | + """Test deleting nonexistent key.""" |
| 90 | + from src.utils.cache import MemoryCache |
231 | 91 |
|
| 92 | + cache = MemoryCache() |
| 93 | + result = cache.delete("nonexistent") |
232 | 94 | assert result is False |
233 | 95 |
|
234 | | - @patch("src.utils.cache.redis.from_url") |
235 | | - def test_is_connected(self, mock_from_url): |
236 | | - """Test is_connected property.""" |
237 | | - from src.utils.cache import RedisCache |
| 96 | + def test_is_connected(self): |
| 97 | + """Test is_connected property (always True for memory cache).""" |
| 98 | + from src.utils.cache import MemoryCache |
238 | 99 |
|
239 | | - mock_client = MagicMock() |
240 | | - mock_from_url.return_value = mock_client |
| 100 | + cache = MemoryCache() |
| 101 | + assert cache.is_connected is True |
241 | 102 |
|
242 | | - cache = RedisCache("redis://localhost:6379") |
243 | | - assert cache.is_connected is False |
| 103 | + def test_cleanup_expired(self): |
| 104 | + """Test cleanup of expired entries.""" |
| 105 | + from src.utils.cache import MemoryCache |
244 | 106 |
|
245 | | - cache._connect() |
246 | | - assert cache.is_connected is True |
| 107 | + cache = MemoryCache() |
| 108 | + |
| 109 | + # Add entries with different expiries |
| 110 | + cache._cache["expired"] = ("value", time.time() - 10) # Already expired |
| 111 | + cache._cache["valid"] = ("value", time.time() + 3600) # Still valid |
| 112 | + |
| 113 | + cache._cleanup_expired() |
| 114 | + |
| 115 | + assert "expired" not in cache._cache |
| 116 | + assert "valid" in cache._cache |
247 | 117 |
|
248 | 118 |
|
249 | 119 | class TestGetCache: |
250 | 120 | """Tests for get_cache function.""" |
251 | 121 |
|
252 | | - @patch("src.utils.cache.RedisCache") |
253 | | - @patch("src.utils.cache.get_settings") |
254 | | - def test_get_cache(self, mock_settings, mock_cache_class): |
| 122 | + def test_get_cache(self): |
255 | 123 | """Test get_cache singleton.""" |
256 | 124 | from src.utils.cache import get_cache |
257 | 125 |
|
258 | 126 | # Clear the lru_cache |
259 | 127 | get_cache.cache_clear() |
260 | 128 |
|
261 | | - mock_settings_obj = MagicMock() |
262 | | - mock_settings_obj.redis_url = "redis://localhost:6379" |
263 | | - mock_settings_obj.cache_ttl_seconds = 3600 |
264 | | - mock_settings.return_value = mock_settings_obj |
265 | | - |
266 | 129 | cache1 = get_cache() |
267 | 130 | cache2 = get_cache() |
268 | 131 |
|
269 | 132 | # Should return same instance (cached) |
270 | 133 | assert cache1 is cache2 |
271 | 134 |
|
272 | 135 |
|
| 136 | +class TestRedisBackwardsCompatibility: |
| 137 | + """Test backwards compatibility alias.""" |
| 138 | + |
| 139 | + def test_redis_cache_alias(self): |
| 140 | + """Test that RedisCache is aliased to MemoryCache.""" |
| 141 | + from src.utils.cache import RedisCache, MemoryCache |
| 142 | + |
| 143 | + assert RedisCache is MemoryCache |
| 144 | + |
| 145 | + |
273 | 146 | class TestRateLimiter: |
274 | 147 | """Tests for RateLimiter class.""" |
275 | 148 |
|
|
0 commit comments