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") } }