oficina / meet Public

Clone
git clone https://amadan.net/oficina/meet

Plain git — no account needed to clone.

Download

Download this file

1package calls
2
3import (
4 "testing"
5 "time"
6)
7
8// Task 5 review minor 5: unit coverage for ratelimit.go's own behaviour,
9// independent of the signal endpoint that happens to be its only caller.
10
11func TestRateLimiterAllowsUpToBurstThenRefuses(t *testing.T) {
12 now := time.Date(2026, 9, 10, 9, 0, 0, 0, time.UTC)
13 l := newRateLimiter(30, 3, 0)
14 for i := 0; i < 3; i++ {
15 if ok, _ := l.allow("k", now); !ok {
16 t.Fatalf("call %d within burst was refused", i)
17 }
18 }
19 ok, retry := l.allow("k", now)
20 if ok {
21 t.Fatal("a call past the burst, with no time elapsed, was allowed")
22 }
23 if retry <= 0 {
24 t.Fatalf("retryAfter %v, want > 0", retry)
25 }
26}
27
28func TestRateLimiterRefillsAfterAnExplicitElapsedDuration(t *testing.T) {
29 now := time.Date(2026, 9, 10, 9, 0, 0, 0, time.UTC)
30 l := newRateLimiter(30, 1, 0)
31 if ok, _ := l.allow("k", now); !ok {
32 t.Fatal("the first call, at burst, was refused")
33 }
34 if ok, _ := l.allow("k", now); ok {
35 t.Fatal("a second call at the same instant was allowed with an empty bucket")
36 }
37 // One token at 30/s refills in 1/30 s ≈ 33 ms; 40 ms is comfortably
38 // past that.
39 later := now.Add(40 * time.Millisecond)
40 if ok, _ := l.allow("k", later); !ok {
41 t.Fatal("a call after the refill window was still refused")
42 }
43}
44
45func TestRateLimiterRetryAfterIsBoundedByOneTokensRefillTime(t *testing.T) {
46 now := time.Date(2026, 9, 10, 9, 0, 0, 0, time.UTC)
47 l := newRateLimiter(30, 1, 0)
48 l.allow("k", now)
49 _, retry := l.allow("k", now)
50 oneToken := time.Second / 30
51 if retry <= 0 || retry > oneToken+time.Millisecond {
52 t.Fatalf("retryAfter %v, want in (0, %v]", retry, oneToken)
53 }
54}
55
56func TestRateLimiterEvictsABucketIdleOverAMinute(t *testing.T) {
57 now := time.Date(2026, 9, 10, 9, 0, 0, 0, time.UTC)
58 l := newRateLimiter(30, 60, 0)
59 l.allow("k", now)
60
61 l.mu.Lock()
62 l.sweepLocked(now.Add(59 * time.Second))
63 _, stillThereAt59s := l.buckets["k"]
64 l.sweepLocked(now.Add(61 * time.Second))
65 _, stillThereAt61s := l.buckets["k"]
66 l.mu.Unlock()
67
68 if !stillThereAt59s {
69 t.Fatal("a bucket under a minute idle was swept early")
70 }
71 if stillThereAt61s {
72 t.Fatal("a bucket over a minute idle was not swept")
73 }
74}
75
76func TestRateLimiterCapEvictsTheLeastRecentlyTouchedBucket(t *testing.T) {
77 now := time.Date(2026, 9, 10, 9, 0, 0, 0, time.UTC)
78 l := newRateLimiter(30, 60, 2)
79 l.allow("a", now)
80 l.allow("b", now.Add(time.Second))
81
82 l.mu.Lock()
83 n := len(l.buckets)
84 l.mu.Unlock()
85 if n != 2 {
86 t.Fatalf("buckets after two distinct keys: %d, want 2", n)
87 }
88
89 // A third key must not grow the map past the cap: "a", the oldest,
90 // is evicted to make room.
91 l.allow("c", now.Add(2*time.Second))
92
93 l.mu.Lock()
94 _, aStillThere := l.buckets["a"]
95 _, bStillThere := l.buckets["b"]
96 _, cThere := l.buckets["c"]
97 size := len(l.buckets)
98 l.mu.Unlock()
99
100 if size > 2 {
101 t.Fatalf("bucket count %d exceeds the cap of 2", size)
102 }
103 if aStillThere {
104 t.Fatal("the least recently touched bucket ('a') was not evicted to make room")
105 }
106 if !bStillThere || !cThere {
107 t.Fatal("a bucket newer than the one evicted was lost instead")
108 }
109}
110