Didier Durand commited on
Commit
3c8e7d8
·
1 Parent(s): 08ed45c

Adding tests for TimedCache

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