package middleware import ( "context" "net/http/httptest" "testing" "time" "github.com/gin-gonic/gin" "fileshare/internal/cache" ) // failingCache 模拟缓存故障(Get/Incr 均返回非 ErrNotFound 错误)。 type failingCache struct{ cache.Cache } func (f *failingCache) Get(_ context.Context, _ string) (string, error) { return "", context.DeadlineExceeded } func (f *failingCache) Set(_ context.Context, _, _ string, _ time.Duration) error { return context.DeadlineExceeded } func (f *failingCache) Incr(_ context.Context, _ string, _ time.Duration) (int64, error) { return 0, context.DeadlineExceeded } func newLimiterTest(c *gin.Context, cacheImpl cache.Cache, count int) *RateLimiter { return NewRateLimiter(cacheImpl, map[string]LimitRule{ LimitLogin: {Count: count, Window: time.Minute}, }) } func ginTestContext() *gin.Context { gin.SetMode(gin.TestMode) c, _ := gin.CreateTestContext(httptest.NewRecorder()) c.Request = httptest.NewRequest("POST", "/admin/login", nil) return c } // TestRateLimiterFallbackOnCacheFailure L9:缓存故障时限流降级为进程内计数, // 超过上限后 Check 拒绝(此前 fail-open 会一直放行)。 func TestRateLimiterFallbackOnCacheFailure(t *testing.T) { c := ginTestContext() rl := newLimiterTest(c, &failingCache{cache.NewMemory()}, 3) for i := 0; i < 3; i++ { if ok, _ := rl.Check(c, LimitLogin); !ok { t.Fatalf("第 %d 次检查不应拒绝", i+1) } rl.Add(c, LimitLogin) } if ok, _ := rl.Check(c, LimitLogin); ok { t.Fatal("缓存故障降级下,超过上限后 Check 应拒绝(fail-close)") } } // TestRateLimiterNormalCacheCounting 正常缓存路径行为不变。 func TestRateLimiterNormalCacheCounting(t *testing.T) { c := ginTestContext() rl := newLimiterTest(c, cache.NewMemory(), 2) for i := 0; i < 2; i++ { rl.Add(c, LimitLogin) } if ok, _ := rl.Check(c, LimitLogin); ok { t.Fatal("达到上限后 Check 应拒绝") } }