package server import ( "net/http" "testing" "time" "git.codelab.vc/pkg/httpx/internal/clock" ) func newTestRequest(remoteAddr, xff string) *http.Request { r := &http.Request{RemoteAddr: remoteAddr, Header: http.Header{}} if xff != "" { r.Header.Set("X-Forwarded-For", xff) } return r } // TestLimiterSweepEvictsIdleBuckets verifies that sweep removes fully-refilled // (idle) buckets while preserving buckets that still hold an active limit, so // memory is bounded without resetting live clients' allowances. func TestLimiterSweepEvictsIdleBuckets(t *testing.T) { clk := clock.Mock(time.Now()) o := &rateLimitOptions{rate: 1, burst: 5, clock: clk, maxKeys: 1 << 30} lim := &limiter{opts: o} // "idle" makes a single request, then time passes so it refills to full. lim.allow("idle") clk.Advance(10 * time.Second) // "active" drains its whole burst at the (advanced) current time. for i := 0; i < 6; i++ { lim.allow("active") } lim.sweep() if _, ok := lim.buckets.Load("idle"); ok { t.Error("fully-refilled idle bucket was not evicted") } if _, ok := lim.buckets.Load("active"); !ok { t.Error("active bucket with a partial limit was wrongly evicted") } } // TestClientKeyTrustedProxy exercises the X-Forwarded-For walk used behind a // trusted proxy, independent of the HTTP layer. func TestClientKeyTrustedProxy(t *testing.T) { o := &rateLimitOptions{} WithTrustedProxies("192.168.0.0/16")(o) r := newTestRequest("192.168.1.10:443", "203.0.113.7, 192.168.1.10") if got := o.clientKey(r); got != "203.0.113.7" { t.Fatalf("clientKey = %q, want real client 203.0.113.7", got) } // Untrusted peer: X-Forwarded-For must be ignored entirely. r = newTestRequest("203.0.113.7:443", "10.0.0.1") if got := o.clientKey(r); got != "203.0.113.7" { t.Fatalf("clientKey = %q, want peer 203.0.113.7 (XFF ignored)", got) } }