package ratelimit import ( "testing" "time" ) func TestLimiter_AllowsBurstThenBlocksThenRecovers(t *testing.T) { cfg := Config{Burst: 3, RefillEvery: 50 * time.Millisecond} lim := NewLimiter(StaticConfig{Default: cfg}) for i := 0; i < 3; i++ { ok, _ := lim.Allow("mandant-a", "1.2.3.4") if !ok { t.Fatalf("versuch %d im burst hätte erlaubt sein müssen", i+1) } } ok, retryAfter := lim.Allow("mandant-a", "1.2.3.4") if ok { t.Fatalf("vierter versuch über dem burst hätte abgelehnt werden müssen") } if retryAfter <= 0 { t.Fatalf("erwartete positive wartezeit als hinweis, habe %v", retryAfter) } time.Sleep(retryAfter + 10*time.Millisecond) ok, _ = lim.Allow("mandant-a", "1.2.3.4") if !ok { t.Fatalf("nach der wartezeit hätte wieder ein token verfügbar sein müssen") } } func TestLimiter_TenantsAreIndependent(t *testing.T) { lim := NewLimiter(StaticConfig{Default: Config{Burst: 1, RefillEvery: time.Hour}}) okA, _ := lim.Allow("mandant-a", "1.2.3.4") if !okA { t.Fatalf("mandant a: erster versuch hätte erlaubt sein müssen") } okA2, _ := lim.Allow("mandant-a", "1.2.3.4") if okA2 { t.Fatalf("mandant a: zweiter versuch hätte abgelehnt werden müssen") } // Mandant B mit DERSELBEN Quelladresse — eigener Bucket. okB, _ := lim.Allow("mandant-b", "1.2.3.4") if !okB { t.Fatalf("mandant b: eigener bucket, erster versuch hätte erlaubt sein müssen") } } func TestLimiter_PerTenantConfigOverridesDefault(t *testing.T) { lim := NewLimiter(StaticConfig{ Default: Config{Burst: 1, RefillEvery: time.Hour}, PerTenant: map[string]Config{ "mandant-grosszuegig": {Burst: 5, RefillEvery: time.Hour}, }, }) for i := 0; i < 5; i++ { ok, _ := lim.Allow("mandant-grosszuegig", "1.2.3.4") if !ok { t.Fatalf("mandant-grosszuegig: versuch %d hätte im eigenen, größeren burst erlaubt sein müssen", i+1) } } ok, _ := lim.Allow("mandant-grosszuegig", "1.2.3.4") if ok { t.Fatalf("mandant-grosszuegig: sechster versuch hätte abgelehnt werden müssen") } }