package ratelimit import ( "context" "fmt" "os" "testing" "time" ) // TestDisabledFailsOpen verifies that a Limiter without a Redis backend allows // all requests and reports usage as zero rather than erroring. func TestDisabledFailsOpen(t *testing.T) { l := New("not-a-valid-url") // parse error => disabled if l.Enabled() { t.Fatal("expected limiter to be disabled for invalid url") } res := l.Allow(context.Background(), "x", 1, time.Minute) if !res.Allowed || res.Remaining != 1 { t.Fatalf("disabled limiter must fail open: %+v", res) } // Must not panic and must return zero usage. l.RecordUsage(context.Background(), "k1") if u := l.Usage(context.Background(), "k1"); u.Total != 0 { t.Fatalf("disabled usage should be zero, got %+v", u) } } // TestNilReceiverSafe ensures a nil *Limiter is safe to use (handler default). func TestNilReceiverSafe(t *testing.T) { var l *Limiter if l.Enabled() { t.Fatal("nil limiter must report disabled") } res := l.Allow(context.Background(), "x", 5, time.Minute) if !res.Allowed { t.Fatal("nil limiter must fail open") } l.RecordUsage(context.Background(), "k") _ = l.Usage(context.Background(), "k") } func testLimiter(t *testing.T) *Limiter { t.Helper() url := os.Getenv("OPENGOODS_REDIS_URL") if url == "" { url = "redis://localhost:6379/0" } l := New(url) if !l.Enabled() { t.Skip("redis not configured") } ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() if err := l.rdb.Ping(ctx).Err(); err != nil { t.Skipf("redis not reachable: %v", err) } return l } func TestAllowFixedWindow(t *testing.T) { l := testLimiter(t) ctx := context.Background() id := fmt.Sprintf("test:%d", time.Now().UnixNano()) for i := 1; i <= 2; i++ { if res := l.Allow(ctx, id, 2, time.Minute); !res.Allowed { t.Fatalf("request %d should be allowed: %+v", i, res) } } res := l.Allow(ctx, id, 2, time.Minute) if res.Allowed { t.Fatalf("3rd request over limit 2 should be denied: %+v", res) } if res.Remaining != 0 { t.Fatalf("remaining should be 0 when over limit, got %d", res.Remaining) } } func TestRecordAndReadUsage(t *testing.T) { l := testLimiter(t) ctx := context.Background() key := fmt.Sprintf("usagekey:%d", time.Now().UnixNano()) l.RecordUsage(ctx, key) l.RecordUsage(ctx, key) u := l.Usage(ctx, key) if u.Total != 2 || u.Today != 2 { t.Fatalf("expected total=2 today=2, got %+v", u) } if u.LastUsedAt == nil { t.Fatal("expected last-used timestamp to be set") } }