3c687e5e0b
新包 internal/ratelimit:内存/redis(GCRA+Lua) 双实现 + 出错逐调用降级内存 (fail-open 到内存不 fail-closed)。REDIS_ADDR 空=内存模式,行为与既往一致; 配置后跨重启保状态、支持多实例。miniredis 全覆盖测试,零真实外部依赖。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
96 lines
2.3 KiB
Go
96 lines
2.3 KiB
Go
package ratelimit
|
||
|
||
import (
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/alicebob/miniredis/v2"
|
||
"github.com/redis/go-redis/v9"
|
||
"golang.org/x/time/rate"
|
||
)
|
||
|
||
// resetDefault 清空全局 Store(Init 测试用,避免污染其他测试)。
|
||
func resetDefault() {
|
||
defMu.Lock()
|
||
def = nil
|
||
defMu.Unlock()
|
||
}
|
||
|
||
func TestFallbackLimiterDegradesToMemory(t *testing.T) {
|
||
mr := miniredis.RunT(t)
|
||
rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()})
|
||
s := newFallbackStore(rdb)
|
||
l := s.NewLimiter(rate.Limit(1.0/60.0), 2)
|
||
|
||
if !l.Allow("k").Allowed {
|
||
t.Fatal("redis 正常时应放行")
|
||
}
|
||
mr.Close() // 模拟 redis 宕机
|
||
|
||
// 降级内存后仍在限流:burst 2 内放行、超出拒绝(内存桶从零起算)
|
||
if !l.Allow("k").Allowed || !l.Allow("k").Allowed {
|
||
t.Fatal("降级内存后 burst 内应放行(不 5xx、不 fail-closed)")
|
||
}
|
||
if l.Allow("k").Allowed {
|
||
t.Fatal("降级内存后超出 burst 仍应限流")
|
||
}
|
||
}
|
||
|
||
func TestFallbackFailLockerDegrades(t *testing.T) {
|
||
mr := miniredis.RunT(t)
|
||
rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()})
|
||
s := newFallbackStore(rdb)
|
||
fl := s.FailLocker()
|
||
|
||
mr.Close()
|
||
for i := 0; i < 3; i++ {
|
||
fl.RecordFailure("k", 3, time.Minute)
|
||
}
|
||
if !fl.Locked("k") {
|
||
t.Fatal("降级内存后失败锁仍应生效")
|
||
}
|
||
fl.Reset("k")
|
||
if fl.Locked("k") {
|
||
t.Fatal("Reset 应解锁")
|
||
}
|
||
}
|
||
|
||
func TestFallbackCounterDegrades(t *testing.T) {
|
||
mr := miniredis.RunT(t)
|
||
rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()})
|
||
s := newFallbackStore(rdb)
|
||
c := s.Counter()
|
||
|
||
mr.Close()
|
||
n, err := c.Incr("k", time.Hour)
|
||
if err != nil || n != 1 {
|
||
t.Fatalf("降级内存后计数应可用,得到 %d err=%v", n, err)
|
||
}
|
||
}
|
||
|
||
func TestInitBadAddrFallsToMemory(t *testing.T) {
|
||
defer resetDefault()
|
||
Init("127.0.0.1:1") // 无服务端口,Ping 必败:不 panic、落内存
|
||
l := Default().NewLimiter(rate.Limit(1), 1)
|
||
if !l.Allow("k").Allowed {
|
||
t.Fatal("坏地址应降级内存并正常工作")
|
||
}
|
||
}
|
||
|
||
func TestInitEmptyAddrMemory(t *testing.T) {
|
||
defer resetDefault()
|
||
Init("")
|
||
if _, ok := Default().(*memoryStore); !ok {
|
||
t.Fatal("空地址应为内存 Store")
|
||
}
|
||
}
|
||
|
||
func TestInitGoodAddrRedis(t *testing.T) {
|
||
defer resetDefault()
|
||
mr := miniredis.RunT(t)
|
||
Init(mr.Addr())
|
||
if _, ok := Default().(*fallbackStore); !ok {
|
||
t.Fatal("可用地址应为 redis(fallback) Store")
|
||
}
|
||
}
|