Accountant: Inventar, where, Checkmk, Abrechnung

This commit is contained in:
br committed 2026-10-18 17:08:40 +02:00
1 parent fdcd06c93f
commit 9f16148cde
2 files changed

No files matched your search

-61
View File
@@ -1,61 +0,0 @@
package server
import (
"sync"
"time"
)
// rateLimiter begrenzt Versuche je Schlüssel (Client-IP) auf limit pro
// window (gleitendes Fenster). limit < 0 = unbegrenzt.
type rateLimiter struct {
limit int
window time.Duration
now func() time.Time
mu sync.Mutex
hits map[string][]time.Time
lastPurge time.Time
}
// allow verbucht einen Versuch; bei Ablehnung liefert es die Wartezeit.
func (l *rateLimiter) allow(key string) (bool, time.Duration) {
if l.limit < 0 {
return true, 0
}
now := time.Now()
if l.now != nil {
now = l.now()
}
cutoff := now.Add(-l.window)
l.mu.Lock()
defer l.mu.Unlock()
if l.hits == nil {
l.hits = map[string][]time.Time{}
}
if now.Sub(l.lastPurge) >= l.window { // vergessen, was nicht mehr zählt
for k, ts := range l.hits {
if !ts[len(ts)-1].After(cutoff) {
delete(l.hits, k)
}
}
l.lastPurge = now
}
ts := l.hits[key]
i := 0
for i < len(ts) && !ts[i].After(cutoff) {
i++
}
ts = ts[i:]
if len(ts) >= l.limit {
l.hits[key] = ts
return false, ts[0].Add(l.window).Sub(now)
}
l.hits[key] = append(ts, now)
return true, 0
}
func (l *rateLimiter) size() int {
l.mu.Lock()
defer l.mu.Unlock()
return len(l.hits)
}
-56
View File
@@ -1,56 +0,0 @@
package server
import (
"testing"
"time"
)
func TestRateLimiter(t *testing.T) {
now := time.Date(2026, 10, 2, 12, 0, 0, 0, time.UTC)
l := &rateLimiter{limit: 3, window: time.Minute, now: func() time.Time { return now }}
for i := range 3 {
if ok, _ := l.allow("1.2.3.4"); !ok {
t.Fatalf("Versuch %d abgelehnt", i+1)
}
}
ok, retry := l.allow("1.2.3.4")
if ok || retry != time.Minute {
t.Errorf("4. Versuch: ok=%v retry=%v, erwartet abgelehnt mit 1 min", ok, retry)
}
if ok, _ := l.allow("5.6.7.8"); !ok {
t.Error("andere IP mitbetroffen")
}
now = now.Add(40 * time.Second)
if _, retry := l.allow("1.2.3.4"); retry != 20*time.Second {
t.Errorf("Retry-After nach 40 s: %v, erwartet 20 s", retry)
}
now = now.Add(20 * time.Second)
if ok, _ := l.allow("1.2.3.4"); !ok {
t.Error("nach Ablauf des Fensters weiter gesperrt")
}
}
// Die Zählerliste darf bei vielen IPs nicht unbegrenzt wachsen.
func TestRateLimiter_ForgetsOldEntries(t *testing.T) {
now := time.Date(2026, 10, 2, 12, 0, 0, 0, time.UTC)
l := &rateLimiter{limit: 1, window: time.Minute, now: func() time.Time { return now }}
for i := range 1000 {
l.allow(string(rune('a'+i%26)) + time.Duration(i).String())
}
now = now.Add(2 * time.Minute)
l.allow("neu")
if n := l.size(); n != 1 {
t.Errorf("%d Einträge nach Ablauf, erwartet 1", n)
}
}
func TestRateLimiter_Disabled(t *testing.T) {
l := &rateLimiter{limit: -1, window: time.Minute}
for range 100 {
if ok, _ := l.allow("x"); !ok {
t.Fatal("Limit -1 muss unbegrenzt sein")
}
}
}