Skip to content

Commit 33fc124

Browse files
0x46616c6bclaude
andcommitted
✅ Add NewRateLimiter tests, exclude main.go from coverage
Cover the constructor's three paths: successful connection (smoke check against miniredis), malformed URL (parse error), and unreachable Redis (startup PING failure must still return a usable, fail-open limiter). Also exclude main.go from Sonarcloud coverage — it only wires components together and is exercised end-to-end rather than by unit tests. Co-Authored-By: Claude <claude@anthropic.com>
1 parent 47ce7ef commit 33fc124

2 files changed

Lines changed: 58 additions & 0 deletions

File tree

ratelimit_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,62 @@ import (
1010
"go.uber.org/zap"
1111
)
1212

13+
func TestNewRateLimiter_Success(t *testing.T) {
14+
mr := miniredis.RunT(t)
15+
url := "redis://" + mr.Addr()
16+
17+
rl, err := NewRateLimiter(context.Background(), url, zap.NewNop())
18+
if err != nil {
19+
t.Fatalf("NewRateLimiter returned error: %v", err)
20+
}
21+
if rl == nil {
22+
t.Fatal("NewRateLimiter returned nil limiter")
23+
}
24+
t.Cleanup(func() { _ = rl.Close() })
25+
26+
allowed, _, _ := rl.CheckAndIncrement(context.Background(), "smoke@example.org", &Quota{PerHour: 1, PerDay: 1})
27+
if !allowed {
28+
t.Error("Expected first message to be allowed against a fresh Redis")
29+
}
30+
}
31+
32+
func TestNewRateLimiter_InvalidURL(t *testing.T) {
33+
rl, err := NewRateLimiter(context.Background(), "://not-a-url", zap.NewNop())
34+
if err == nil {
35+
t.Fatal("Expected error for malformed URL")
36+
}
37+
if rl != nil {
38+
t.Errorf("Expected nil limiter on parse error, got %v", rl)
39+
}
40+
}
41+
42+
func TestNewRateLimiter_PingFailureFailsOpen(t *testing.T) {
43+
// Point at an address that nothing is listening on so the PING fails fast.
44+
// Constructor must still return a usable limiter (fail-open at startup).
45+
// Tight dial timeout keeps the test fast despite go-redis pool retries.
46+
url := "redis://127.0.0.1:1?dial_timeout=100ms"
47+
48+
rl, err := NewRateLimiter(context.Background(), url, zap.NewNop())
49+
if err != nil {
50+
t.Fatalf("Expected no error on PING failure, got %v", err)
51+
}
52+
if rl == nil {
53+
t.Fatal("Expected non-nil limiter even when PING fails")
54+
}
55+
t.Cleanup(func() { _ = rl.Close() })
56+
57+
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
58+
defer cancel()
59+
60+
allowed, hourCount, dayCount := rl.CheckAndIncrement(ctx, "ping@example.org", &Quota{PerHour: 1, PerDay: 1})
61+
if !allowed {
62+
t.Error("Expected fail-open when Redis is unreachable")
63+
}
64+
if hourCount != 0 || dayCount != 0 {
65+
t.Errorf("Expected zero counts on fail-open, got hour=%d, day=%d", hourCount, dayCount)
66+
}
67+
}
68+
1369
func newTestRateLimiter(t *testing.T) (*RateLimiter, *miniredis.Miniredis) {
1470
t.Helper()
1571
mr := miniredis.RunT(t)

sonar-project.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ sonar.exclusions=**/mock_*.go
99
sonar.tests=.
1010
sonar.test.inclusions=**/*_test.go
1111

12+
sonar.coverage.exclusions=main.go
13+
1214
sonar.go.coverage.reportPaths=coverage.txt

0 commit comments

Comments
 (0)