Skip to content

Commit a94c0a8

Browse files
committed
test: add unit tests for all packages
1 parent c3fa55b commit a94c0a8

File tree

4 files changed

+838
-0
lines changed

4 files changed

+838
-0
lines changed

internal/cache/cache_test.go

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
package cache
2+
3+
import (
4+
"context"
5+
"sync"
6+
"sync/atomic"
7+
"testing"
8+
"time"
9+
)
10+
11+
func TestCache_GetSet(t *testing.T) {
12+
c := New(time.Second)
13+
defer c.Close()
14+
15+
// Test miss
16+
if _, ok := c.Get("missing"); ok {
17+
t.Error("expected cache miss for missing key")
18+
}
19+
20+
// Test set and get
21+
c.Set("key1", []byte("value1"))
22+
data, ok := c.Get("key1")
23+
if !ok {
24+
t.Error("expected cache hit")
25+
}
26+
if string(data) != "value1" {
27+
t.Errorf("expected value1, got %s", string(data))
28+
}
29+
}
30+
31+
func TestCache_TTL(t *testing.T) {
32+
c := New(50 * time.Millisecond)
33+
defer c.Close()
34+
35+
c.Set("key1", []byte("value1"))
36+
37+
// Should hit immediately
38+
if _, ok := c.Get("key1"); !ok {
39+
t.Error("expected cache hit")
40+
}
41+
42+
// Wait for expiration
43+
time.Sleep(100 * time.Millisecond)
44+
45+
// Should miss after TTL
46+
if _, ok := c.Get("key1"); ok {
47+
t.Error("expected cache miss after TTL")
48+
}
49+
}
50+
51+
func TestCache_GetStale(t *testing.T) {
52+
c := New(50 * time.Millisecond)
53+
defer c.Close()
54+
55+
c.Set("key1", []byte("value1"))
56+
57+
// Wait for expiration
58+
time.Sleep(100 * time.Millisecond)
59+
60+
// GetStale should return expired data
61+
data, ok := c.GetStale("key1")
62+
if !ok {
63+
t.Error("expected stale data to be returned")
64+
}
65+
if string(data) != "value1" {
66+
t.Errorf("expected value1, got %s", string(data))
67+
}
68+
}
69+
70+
func TestCache_Delete(t *testing.T) {
71+
c := New(time.Second)
72+
defer c.Close()
73+
74+
c.Set("key1", []byte("value1"))
75+
c.Delete("key1")
76+
77+
if _, ok := c.Get("key1"); ok {
78+
t.Error("expected cache miss after delete")
79+
}
80+
}
81+
82+
func TestCache_Key(t *testing.T) {
83+
key := Key(1, 0x03, 100, 10)
84+
expected := "1:3:100:10"
85+
if key != expected {
86+
t.Errorf("expected %s, got %s", expected, key)
87+
}
88+
}
89+
90+
func TestCache_GetOrFetch(t *testing.T) {
91+
c := New(time.Second)
92+
defer c.Close()
93+
94+
ctx := context.Background()
95+
fetchCount := 0
96+
fetch := func(ctx context.Context) ([]byte, error) {
97+
fetchCount++
98+
return []byte("fetched"), nil
99+
}
100+
101+
// First call should fetch (cache miss)
102+
data, hit, err := c.GetOrFetch(ctx, "key1", fetch)
103+
if err != nil {
104+
t.Errorf("unexpected error: %v", err)
105+
}
106+
if hit {
107+
t.Error("expected cache miss on first call")
108+
}
109+
if string(data) != "fetched" {
110+
t.Errorf("expected fetched, got %s", string(data))
111+
}
112+
if fetchCount != 1 {
113+
t.Errorf("expected 1 fetch, got %d", fetchCount)
114+
}
115+
116+
// Second call should hit cache
117+
data, hit, err = c.GetOrFetch(ctx, "key1", fetch)
118+
if err != nil {
119+
t.Errorf("unexpected error: %v", err)
120+
}
121+
if !hit {
122+
t.Error("expected cache hit on second call")
123+
}
124+
if string(data) != "fetched" {
125+
t.Errorf("expected fetched, got %s", string(data))
126+
}
127+
if fetchCount != 1 {
128+
t.Errorf("expected 1 fetch (cache hit), got %d", fetchCount)
129+
}
130+
}
131+
132+
func TestCache_RequestCoalescing(t *testing.T) {
133+
c := New(time.Second)
134+
defer c.Close()
135+
136+
ctx := context.Background()
137+
var fetchCount int32
138+
fetchStarted := make(chan struct{})
139+
fetchContinue := make(chan struct{})
140+
141+
fetch := func(ctx context.Context) ([]byte, error) {
142+
atomic.AddInt32(&fetchCount, 1)
143+
close(fetchStarted)
144+
<-fetchContinue
145+
return []byte("fetched"), nil
146+
}
147+
148+
var wg sync.WaitGroup
149+
results := make([][]byte, 3)
150+
errors := make([]error, 3)
151+
152+
// Start first request
153+
wg.Add(1)
154+
go func() {
155+
defer wg.Done()
156+
results[0], _, errors[0] = c.GetOrFetch(ctx, "key1", fetch)
157+
}()
158+
159+
// Wait for fetch to start
160+
<-fetchStarted
161+
162+
// Start two more requests while first is in-flight
163+
for i := 1; i < 3; i++ {
164+
i := i
165+
wg.Add(1)
166+
go func() {
167+
defer wg.Done()
168+
results[i], _, errors[i] = c.GetOrFetch(ctx, "key1", func(ctx context.Context) ([]byte, error) {
169+
atomic.AddInt32(&fetchCount, 1)
170+
return []byte("should not be called"), nil
171+
})
172+
}()
173+
}
174+
175+
// Give time for requests to queue up
176+
time.Sleep(50 * time.Millisecond)
177+
178+
// Allow fetch to complete
179+
close(fetchContinue)
180+
wg.Wait()
181+
182+
// All requests should succeed with same data
183+
for i := 0; i < 3; i++ {
184+
if errors[i] != nil {
185+
t.Errorf("request %d: unexpected error: %v", i, errors[i])
186+
}
187+
if string(results[i]) != "fetched" {
188+
t.Errorf("request %d: expected fetched, got %s", i, string(results[i]))
189+
}
190+
}
191+
192+
// Only one fetch should have happened
193+
if fetchCount != 1 {
194+
t.Errorf("expected 1 fetch (coalesced), got %d", fetchCount)
195+
}
196+
}
197+
198+
func TestCache_ContextCancellation(t *testing.T) {
199+
c := New(time.Second)
200+
defer c.Close()
201+
202+
ctx, cancel := context.WithCancel(context.Background())
203+
fetchStarted := make(chan struct{})
204+
205+
// Start a slow fetch
206+
go func() {
207+
c.GetOrFetch(ctx, "key1", func(ctx context.Context) ([]byte, error) {
208+
close(fetchStarted)
209+
time.Sleep(time.Second)
210+
return []byte("fetched"), nil
211+
})
212+
}()
213+
214+
<-fetchStarted
215+
216+
// Start another request and cancel it
217+
ctx2, cancel2 := context.WithCancel(context.Background())
218+
cancel2() // Cancel immediately
219+
220+
_, _, err := c.GetOrFetch(ctx2, "key1", func(ctx context.Context) ([]byte, error) {
221+
return []byte("should not be called"), nil
222+
})
223+
224+
if err != context.Canceled {
225+
t.Errorf("expected context.Canceled, got %v", err)
226+
}
227+
228+
cancel()
229+
}
230+
231+
func TestCache_DataIsolation(t *testing.T) {
232+
c := New(time.Second)
233+
defer c.Close()
234+
235+
original := []byte("original")
236+
c.Set("key1", original)
237+
238+
// Modify original after setting
239+
original[0] = 'X'
240+
241+
// Retrieved data should be unchanged
242+
data, _ := c.Get("key1")
243+
if string(data) != "original" {
244+
t.Error("cache data was mutated")
245+
}
246+
247+
// Modify retrieved data
248+
data[0] = 'Y'
249+
250+
// Cache should be unchanged
251+
data2, _ := c.Get("key1")
252+
if string(data2) != "original" {
253+
t.Error("cache data was mutated via returned slice")
254+
}
255+
}

0 commit comments

Comments
 (0)