Jeremiah Lowin commited on
Commit
9d287ee
·
unverified ·
2 Parent(s): 23e57c62627940

Merge pull request #388 from didier-durand/add-test-cache

Browse files
Files changed (1) hide show
  1. tests/utilities/test_cache.py +233 -0
tests/utilities/test_cache.py ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Tests for the cache.py module."""
2
+
3
+ import datetime
4
+ import time
5
+ from unittest.mock import patch
6
+
7
+ from fastmcp.utilities.cache import TimedCache
8
+
9
+
10
+ class TestTimedCache:
11
+ """Tests for the TimedCache class."""
12
+
13
+ def test_init(self):
14
+ """Test that a TimedCache can be initialized with an expiration."""
15
+ expiration = datetime.timedelta(seconds=10)
16
+ cache = TimedCache(expiration)
17
+ assert cache.expiration == expiration
18
+ assert isinstance(cache.cache, dict)
19
+ assert len(cache.cache) == 0
20
+
21
+ def test_set(self):
22
+ """Test that values can be set in the cache."""
23
+ cache = TimedCache(datetime.timedelta(seconds=10))
24
+ key, value = "test_key", "test_value"
25
+
26
+ with patch("datetime.datetime") as mock_datetime:
27
+ now = datetime.datetime(2023, 1, 1, tzinfo=datetime.timezone.utc)
28
+ mock_datetime.now.return_value = now
29
+
30
+ cache.set(key, value)
31
+
32
+ # Check that the value is stored with the correct expiration
33
+ assert key in cache.cache
34
+ stored_value, expiration = cache.cache[key]
35
+ assert stored_value == value
36
+ assert expiration == now + datetime.timedelta(seconds=10)
37
+
38
+ def test_get_found(self):
39
+ """Test retrieving a value that exists and has not expired."""
40
+ cache = TimedCache(datetime.timedelta(seconds=10))
41
+ key, value = "test_key", "test_value"
42
+
43
+ # Set a future expiration time
44
+ future = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(
45
+ seconds=30
46
+ )
47
+ cache.cache[key] = (value, future)
48
+
49
+ # The value should be returned
50
+ assert cache.get(key) == value
51
+
52
+ def test_get_expired(self):
53
+ """Test retrieving a value that exists but has expired."""
54
+ cache = TimedCache(datetime.timedelta(seconds=10))
55
+ key, value = "test_key", "test_value"
56
+
57
+ # Set a past expiration time
58
+ past = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(
59
+ seconds=1
60
+ )
61
+ cache.cache[key] = (value, past)
62
+
63
+ # Should return NOT_FOUND
64
+ assert cache.get(key) is TimedCache.NOT_FOUND
65
+
66
+ def test_get_not_found(self):
67
+ """Test retrieving a value that doesn't exist in the cache."""
68
+ cache = TimedCache(datetime.timedelta(seconds=10))
69
+
70
+ # Key doesn't exist
71
+ assert cache.get("nonexistent_key") is TimedCache.NOT_FOUND
72
+
73
+ def test_clear(self):
74
+ """Test that the cache can be cleared."""
75
+ cache = TimedCache(datetime.timedelta(seconds=10))
76
+
77
+ # Add some items
78
+ cache.set("key1", "value1")
79
+ cache.set("key2", "value2")
80
+ assert len(cache.cache) == 2
81
+
82
+ # Clear the cache
83
+ cache.clear()
84
+ assert len(cache.cache) == 0
85
+
86
+ def test_real_expiration(self):
87
+ """Test that values actually expire after the specified time."""
88
+ # Use a very short expiration for the test
89
+ cache = TimedCache(datetime.timedelta(milliseconds=50))
90
+ key, value = "test_key", "test_value"
91
+
92
+ cache.set(key, value)
93
+ # Value should be available immediately
94
+ assert cache.get(key) == value
95
+
96
+ # Wait for expiration
97
+ time.sleep(0.06) # 60 milliseconds, slightly longer than expiration
98
+
99
+ # Value should now be expired
100
+ assert cache.get(key) is TimedCache.NOT_FOUND
101
+
102
+ def test_overwrite_value(self):
103
+ """Test that setting a key that already exists overwrites the old value."""
104
+ cache = TimedCache(datetime.timedelta(seconds=10))
105
+ key = "test_key"
106
+
107
+ # Set initial value
108
+ cache.set(key, "initial_value")
109
+ assert cache.get(key) == "initial_value"
110
+
111
+ # Overwrite with new value
112
+ cache.set(key, "new_value")
113
+ assert cache.get(key) == "new_value"
114
+
115
+ def test_extends_expiration_on_overwrite(self):
116
+ """Test that overwriting a key extends its expiration time."""
117
+ cache = TimedCache(datetime.timedelta(seconds=10))
118
+ key = "test_key"
119
+
120
+ with patch("datetime.datetime") as mock_datetime:
121
+ # Set initial value at t=0
122
+ initial_time = datetime.datetime(2023, 1, 1, tzinfo=datetime.timezone.utc)
123
+ mock_datetime.now.return_value = initial_time
124
+ cache.set(key, "initial_value")
125
+
126
+ initial_expiration = cache.cache[key][1]
127
+ assert initial_expiration == initial_time + datetime.timedelta(seconds=10)
128
+
129
+ # Overwrite at t=5
130
+ later_time = initial_time + datetime.timedelta(seconds=5)
131
+ mock_datetime.now.return_value = later_time
132
+ cache.set(key, "new_value")
133
+
134
+ # Expiration should be extended
135
+ new_expiration = cache.cache[key][1]
136
+ assert new_expiration == later_time + datetime.timedelta(seconds=10)
137
+
138
+ def test_different_key_types(self):
139
+ """Test that different types of keys can be used."""
140
+ cache = TimedCache(datetime.timedelta(seconds=10))
141
+
142
+ # Test various key types
143
+ keys_and_values = [
144
+ (42, "int_value"),
145
+ (3.14, "float_value"),
146
+ ((1, 2), "tuple_value"),
147
+ (frozenset({1, 2, 3}), "frozenset_value"),
148
+ ]
149
+
150
+ for key, value in keys_and_values:
151
+ cache.set(key, value)
152
+ assert cache.get(key) == value
153
+
154
+ def test_none_value(self):
155
+ """Test that None can be stored as a value."""
156
+ cache = TimedCache(datetime.timedelta(seconds=10))
157
+ key = "none_key"
158
+
159
+ cache.set(key, None)
160
+ # The stored value is None, but get() should return None, not NOT_FOUND
161
+ assert cache.get(key) is None
162
+
163
+ def test_edge_case_zero_expiration(self):
164
+ """Test with a zero expiration time."""
165
+ cache = TimedCache(datetime.timedelta(seconds=0))
166
+ key, value = "test_key", "test_value"
167
+
168
+ cache.set(key, value)
169
+ # The value might already be expired by the time we call get()
170
+ # We can't make strong assertions here due to timing variability
171
+ retrieved = cache.get(key)
172
+ assert retrieved in (value, TimedCache.NOT_FOUND)
173
+
174
+ def test_negative_expiration(self):
175
+ """Test with a negative expiration time."""
176
+ cache = TimedCache(datetime.timedelta(seconds=-1))
177
+ key, value = "test_key", "test_value"
178
+
179
+ cache.set(key, value)
180
+ # Value should be immediately expired
181
+ assert cache.get(key) is TimedCache.NOT_FOUND
182
+
183
+ def test_cache_consistency(self):
184
+ """Test cache consistency with multiple operations."""
185
+ cache = TimedCache(datetime.timedelta(seconds=10))
186
+
187
+ # Add multiple items
188
+ cache.set("key1", "value1")
189
+ cache.set("key2", "value2")
190
+ cache.set("key3", "value3")
191
+
192
+ # Check all items
193
+ assert cache.get("key1") == "value1"
194
+ assert cache.get("key2") == "value2"
195
+ assert cache.get("key3") == "value3"
196
+
197
+ # Overwrite one item
198
+ cache.set("key2", "updated_value")
199
+
200
+ # Check again
201
+ assert cache.get("key1") == "value1"
202
+ assert cache.get("key2") == "updated_value"
203
+ assert cache.get("key3") == "value3"
204
+
205
+ # Clear and verify all items are gone
206
+ cache.clear()
207
+ assert cache.get("key1") is TimedCache.NOT_FOUND
208
+ assert cache.get("key2") is TimedCache.NOT_FOUND
209
+ assert cache.get("key3") is TimedCache.NOT_FOUND
210
+
211
+ def test_large_expiration(self):
212
+ """Test with a very large expiration time."""
213
+ # One year expiration
214
+ cache = TimedCache(datetime.timedelta(days=365))
215
+ key, value = "test_key", "test_value"
216
+
217
+ cache.set(key, value)
218
+ assert cache.get(key) == value
219
+
220
+ def test_many_items(self):
221
+ """Test cache with many items."""
222
+ cache = TimedCache(datetime.timedelta(seconds=10))
223
+
224
+ # Add 1000 items
225
+ for i in range(1000):
226
+ cache.set(f"key{i}", f"value{i}")
227
+
228
+ # Check size
229
+ assert len(cache.cache) == 1000
230
+
231
+ # Check some random items
232
+ for i in [0, 123, 456, 789, 999]:
233
+ assert cache.get(f"key{i}") == f"value{i}"