Skip to content

Commit f801184

Browse files
committed
Add unittests for internal/omnifocus/cache.go
1 parent ad6a184 commit f801184

File tree

1 file changed

+265
-0
lines changed

1 file changed

+265
-0
lines changed

internal/omnifocus/cache_test.go

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,268 @@ func TestCacheCleanup(t *testing.T) {
133133
t.Error("Expected non-expired entry to remain after cleanup")
134134
}
135135
}
136+
137+
func TestCacheGetNonExistent(t *testing.T) {
138+
cache := NewCache(1 * time.Second)
139+
140+
_, found := cache.Get("non-existent-key")
141+
if found {
142+
t.Error("Expected not to find non-existent key")
143+
}
144+
}
145+
146+
func TestCacheSetOverwrite(t *testing.T) {
147+
cache := NewCache(1 * time.Second)
148+
149+
cache.Set("test-key", "initial-value")
150+
cache.Set("test-key", "updated-value")
151+
152+
value, found := cache.Get("test-key")
153+
if !found {
154+
t.Error("Expected to find cached value")
155+
}
156+
if value.(string) != "updated-value" {
157+
t.Errorf("Expected 'updated-value', got '%s'", value)
158+
}
159+
}
160+
161+
func TestCacheMultipleTypes(t *testing.T) {
162+
cache := NewCache(1 * time.Second)
163+
164+
// Test different value types
165+
cache.Set("string", "test")
166+
cache.Set("int", 42)
167+
cache.Set("bool", true)
168+
cache.Set("slice", []string{"a", "b", "c"})
169+
cache.Set("map", map[string]int{"x": 1, "y": 2})
170+
171+
// Verify all types can be retrieved
172+
str, found := cache.Get("string")
173+
if !found || str.(string) != "test" {
174+
t.Error("Failed to retrieve string value")
175+
}
176+
177+
num, found := cache.Get("int")
178+
if !found || num.(int) != 42 {
179+
t.Error("Failed to retrieve int value")
180+
}
181+
182+
b, found := cache.Get("bool")
183+
if !found || b.(bool) != true {
184+
t.Error("Failed to retrieve bool value")
185+
}
186+
187+
slice, found := cache.Get("slice")
188+
if !found || len(slice.([]string)) != 3 {
189+
t.Error("Failed to retrieve slice value")
190+
}
191+
192+
m, found := cache.Get("map")
193+
if !found || m.(map[string]int)["x"] != 1 {
194+
t.Error("Failed to retrieve map value")
195+
}
196+
}
197+
198+
func TestCacheInvalidateNonExistent(t *testing.T) {
199+
cache := NewCache(1 * time.Second)
200+
201+
// Invalidating non-existent key should not panic
202+
cache.Invalidate("non-existent")
203+
}
204+
205+
func TestCacheInvalidatePatternNoMatch(t *testing.T) {
206+
cache := NewCache(1 * time.Second)
207+
208+
cache.Set("tasks:all", "all tasks")
209+
cache.Set("projects:all", "all projects")
210+
211+
// Invalidate pattern that doesn't match anything
212+
cache.InvalidatePattern("users:")
213+
214+
// All original entries should remain
215+
_, found1 := cache.Get("tasks:all")
216+
_, found2 := cache.Get("projects:all")
217+
218+
if !found1 || !found2 {
219+
t.Error("Expected all entries to remain when pattern doesn't match")
220+
}
221+
}
222+
223+
func TestCacheInvalidatePatternExactMatch(t *testing.T) {
224+
cache := NewCache(1 * time.Second)
225+
226+
cache.Set("tasks", "tasks")
227+
cache.Set("tasks:all", "all tasks")
228+
229+
// Invalidate with exact prefix
230+
cache.InvalidatePattern("tasks")
231+
232+
// Both should be invalidated
233+
_, found1 := cache.Get("tasks")
234+
_, found2 := cache.Get("tasks:all")
235+
236+
if found1 || found2 {
237+
t.Error("Expected both entries to be invalidated")
238+
}
239+
}
240+
241+
func TestCacheCleanupDisabled(t *testing.T) {
242+
cache := NewCache(0)
243+
244+
// Should not panic when cache is disabled
245+
cache.Cleanup()
246+
}
247+
248+
func TestCacheInvalidateAllDisabled(t *testing.T) {
249+
cache := NewCache(0)
250+
251+
// Should not panic when cache is disabled
252+
cache.InvalidateAll()
253+
}
254+
255+
func TestCacheInvalidateDisabled(t *testing.T) {
256+
cache := NewCache(0)
257+
258+
// Should not panic when cache is disabled
259+
cache.Invalidate("test-key")
260+
}
261+
262+
func TestCacheInvalidatePatternDisabled(t *testing.T) {
263+
cache := NewCache(0)
264+
265+
// Should not panic when cache is disabled
266+
cache.InvalidatePattern("test:")
267+
}
268+
269+
func TestCacheNegativeTTL(t *testing.T) {
270+
cache := NewCache(-1 * time.Second)
271+
272+
cache.Set("test-key", "test-value")
273+
274+
_, found := cache.Get("test-key")
275+
if found {
276+
t.Error("Expected cache to be disabled with negative TTL")
277+
}
278+
}
279+
280+
func TestCacheCleanupEmptyCache(t *testing.T) {
281+
cache := NewCache(1 * time.Second)
282+
283+
// Should not panic on empty cache
284+
cache.Cleanup()
285+
}
286+
287+
func TestCacheInvalidateAllEmptyCache(t *testing.T) {
288+
cache := NewCache(1 * time.Second)
289+
290+
// Should not panic on empty cache
291+
cache.InvalidateAll()
292+
}
293+
294+
func TestCacheStartCleanupTimer(t *testing.T) {
295+
cache := NewCache(50 * time.Millisecond)
296+
297+
cache.Set("key1", "value1")
298+
cache.Set("key2", "value2")
299+
300+
// Start cleanup timer with short interval
301+
cache.StartCleanupTimer(60 * time.Millisecond)
302+
303+
// Wait for cleanup to potentially run
304+
time.Sleep(150 * time.Millisecond)
305+
306+
// Add a new entry
307+
cache.Set("key3", "value3")
308+
309+
// Old entries should be cleaned up automatically
310+
_, found1 := cache.Get("key1")
311+
_, found2 := cache.Get("key2")
312+
_, found3 := cache.Get("key3")
313+
314+
if found1 || found2 {
315+
t.Error("Expected expired entries to be cleaned up by timer")
316+
}
317+
if !found3 {
318+
t.Error("Expected new entry to remain")
319+
}
320+
}
321+
322+
func TestCacheStartCleanupTimerDisabled(t *testing.T) {
323+
cache := NewCache(0)
324+
325+
// Should not panic when cache is disabled
326+
cache.StartCleanupTimer(100 * time.Millisecond)
327+
}
328+
329+
func TestCacheConcurrentAccess(t *testing.T) {
330+
cache := NewCache(1 * time.Second)
331+
done := make(chan bool)
332+
333+
// Concurrent writes
334+
for i := 0; i < 10; i++ {
335+
go func(n int) {
336+
for j := 0; j < 100; j++ {
337+
cache.Set("key", n*100+j)
338+
}
339+
done <- true
340+
}(i)
341+
}
342+
343+
// Concurrent reads
344+
for i := 0; i < 10; i++ {
345+
go func() {
346+
for j := 0; j < 100; j++ {
347+
cache.Get("key")
348+
}
349+
done <- true
350+
}()
351+
}
352+
353+
// Wait for all goroutines
354+
for i := 0; i < 20; i++ {
355+
<-done
356+
}
357+
358+
// Should not panic and should have a value
359+
_, found := cache.Get("key")
360+
if !found {
361+
t.Error("Expected to find value after concurrent access")
362+
}
363+
}
364+
365+
func TestCacheConcurrentInvalidation(t *testing.T) {
366+
cache := NewCache(1 * time.Second)
367+
done := make(chan bool)
368+
369+
// Set initial values
370+
for i := 0; i < 100; i++ {
371+
cache.Set("key", i)
372+
}
373+
374+
// Concurrent invalidations
375+
for i := 0; i < 5; i++ {
376+
go func() {
377+
for j := 0; j < 20; j++ {
378+
cache.Invalidate("key")
379+
}
380+
done <- true
381+
}()
382+
}
383+
384+
// Concurrent sets
385+
for i := 0; i < 5; i++ {
386+
go func() {
387+
for j := 0; j < 20; j++ {
388+
cache.Set("key", j)
389+
}
390+
done <- true
391+
}()
392+
}
393+
394+
// Wait for all goroutines
395+
for i := 0; i < 10; i++ {
396+
<-done
397+
}
398+
399+
// Should not panic
400+
}

0 commit comments

Comments
 (0)