-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathapi_idempotency_cache_test.go
More file actions
93 lines (76 loc) · 2.39 KB
/
api_idempotency_cache_test.go
File metadata and controls
93 lines (76 loc) · 2.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package main
import (
"testing"
"time"
)
func TestIdempotencyCapNeverEvictsInFlight(t *testing.T) {
now := time.Unix(1_700_000_000, 0)
c := newIdempotencyCache(10*time.Minute, 1, "")
var h1, h2 [32]byte
h1[0] = 1
h2[0] = 2
state, _ := c.getOrStart(now, "k1", h1)
if state != "start" {
t.Fatalf("expected start for k1, got %q", state)
}
state, _ = c.getOrStart(now.Add(1*time.Second), "k2", h2)
if state != "start" {
t.Fatalf("expected start for k2, got %q", state)
}
c.mu.Lock()
if len(c.entries) != 2 {
c.mu.Unlock()
t.Fatalf("expected 2 entries (over cap due to in-flight protection), got %d", len(c.entries))
}
if !c.entries["k1"].inFlight || !c.entries["k2"].inFlight {
c.mu.Unlock()
t.Fatalf("expected both entries to be in-flight")
}
c.mu.Unlock()
// Completing one request makes it eligible for eviction; cap should drop back to 1.
c.complete(now.Add(2*time.Second), "k1", h1, 200, []byte(`{"ok":true}`))
c.mu.Lock()
defer c.mu.Unlock()
if len(c.entries) != 1 {
t.Fatalf("expected 1 entry after completion + cap enforcement, got %d", len(c.entries))
}
if _, ok := c.entries["k2"]; !ok || !c.entries["k2"].inFlight {
t.Fatalf("expected remaining entry to be k2 in-flight")
}
}
func TestIdempotencyPruneSkipsInFlight(t *testing.T) {
now := time.Unix(1_700_000_000, 0)
c := newIdempotencyCache(1*time.Second, 100, "")
var h [32]byte
h[0] = 9
_, _ = c.getOrStart(now, "k", h)
// Advance beyond TTL; prune should not remove in-flight entry.
c.mu.Lock()
c.pruneLocked(now.Add(10 * time.Second))
_, ok := c.entries["k"]
c.mu.Unlock()
if !ok {
t.Fatal("expected in-flight entry to survive prune")
}
}
func TestIdempotencyCachePersistsCompletedEntries(t *testing.T) {
now := time.Now()
path := t.TempDir() + "/send-idempotency.json"
c := newIdempotencyCache(24*time.Hour, 100, path)
var h [32]byte
h[0] = 7
state, _ := c.getOrStart(now, "k", h)
if state != "start" {
t.Fatalf("expected start, got %q", state)
}
body := []byte("{\"ok\":true}\n")
c.complete(now.Add(time.Second), "k", h, 200, body)
reloaded := newIdempotencyCache(24*time.Hour, 100, path)
state, res := reloaded.getOrStart(now.Add(2*time.Second), "k", h)
if state != "replay" {
t.Fatalf("expected replay after reload, got %q", state)
}
if res.status != 200 || string(res.body) != string(body) {
t.Fatalf("unexpected replay result: status=%d body=%q", res.status, string(res.body))
}
}