8878711cf3
- GATEWAY_MAX_BODY_BYTES (1 MiB): connect WithReadMaxBytes + http.MaxBytesReader
on the public mux; explicit http2.Server MaxConcurrentStreams/IdleTimeout and
an http.Server ReadHeaderTimeout (R2 report follow-up).
- gateway_rate_limited_total{class} counter, Debug per rejection, a rejection
tracker drained every 30 s into a Warn summary per key and a report POST to
/api/v1/internal/ratelimit/report (feeds the admin view + auto-flag).
- The dead AdminPerMinute/AdminBurst policy now guards the /_gm mount (429),
ahead of its Basic-Auth.
- resolve() logs the cause of infra session-resolve failures at Warn (the
transient unauthenticated dips from the R2 run); unknown tokens stay silent.
39 lines
923 B
Go
39 lines
923 B
Go
package ratelimit_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"scrabble/gateway/internal/ratelimit"
|
|
)
|
|
|
|
// TestTrackerDrain verifies rejections aggregate per (class, key) and that a
|
|
// drain resets the tracker.
|
|
func TestTrackerDrain(t *testing.T) {
|
|
tr := ratelimit.NewTracker()
|
|
if got := tr.Drain(); got != nil {
|
|
t.Fatalf("empty tracker drained %v, want nil", got)
|
|
}
|
|
|
|
tr.Add("user", "u-1")
|
|
tr.Add("user", "u-1")
|
|
tr.Add("public", "10.0.0.1")
|
|
|
|
got := map[string]ratelimit.Rejection{}
|
|
for _, r := range tr.Drain() {
|
|
got[r.Class+"/"+r.Key] = r
|
|
}
|
|
if len(got) != 2 {
|
|
t.Fatalf("drained %d entries, want 2", len(got))
|
|
}
|
|
if r := got["user/u-1"]; r.Rejected != 2 {
|
|
t.Errorf("user/u-1 rejected = %d, want 2", r.Rejected)
|
|
}
|
|
if r := got["public/10.0.0.1"]; r.Rejected != 1 {
|
|
t.Errorf("public/10.0.0.1 rejected = %d, want 1", r.Rejected)
|
|
}
|
|
|
|
if got := tr.Drain(); got != nil {
|
|
t.Fatalf("second drain = %v, want nil", got)
|
|
}
|
|
}
|