mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-06-11 04:10:56 +00:00
bdb60de7ae
- Update go.mod and Dockerfile to Go 1.26 - Apply `go fix ./...` stdlib modernizations across 170+ files - Add `go fix` to post-implementation checklist in CLAUDE.md - Fix go fix misapplied rewrite in loop_history.go
132 lines
2.7 KiB
Go
132 lines
2.7 KiB
Go
package tools
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestNewToolRateLimiter_Zero(t *testing.T) {
|
|
rl := NewToolRateLimiter(0)
|
|
if rl != nil {
|
|
t.Errorf("expected nil for maxPerHour=0, got %v", rl)
|
|
}
|
|
}
|
|
|
|
func TestNewToolRateLimiter_Negative(t *testing.T) {
|
|
rl := NewToolRateLimiter(-5)
|
|
if rl != nil {
|
|
t.Errorf("expected nil for maxPerHour=-5, got %v", rl)
|
|
}
|
|
}
|
|
|
|
func TestToolRateLimiter_AllowUnderLimit(t *testing.T) {
|
|
rl := NewToolRateLimiter(5)
|
|
for i := range 5 {
|
|
if err := rl.Allow("user1"); err != nil {
|
|
t.Errorf("action %d should be allowed: %v", i, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestToolRateLimiter_BlockOverLimit(t *testing.T) {
|
|
rl := NewToolRateLimiter(3)
|
|
|
|
for i := range 3 {
|
|
if err := rl.Allow("user1"); err != nil {
|
|
t.Fatalf("action %d should be allowed: %v", i, err)
|
|
}
|
|
}
|
|
|
|
err := rl.Allow("user1")
|
|
if err == nil {
|
|
t.Error("4th action should be blocked")
|
|
}
|
|
}
|
|
|
|
func TestToolRateLimiter_SeparateKeys(t *testing.T) {
|
|
rl := NewToolRateLimiter(2)
|
|
|
|
// Fill user1
|
|
rl.Allow("user1")
|
|
rl.Allow("user1")
|
|
|
|
// user1 is blocked
|
|
if err := rl.Allow("user1"); err == nil {
|
|
t.Error("user1 should be blocked")
|
|
}
|
|
|
|
// user2 is independent
|
|
if err := rl.Allow("user2"); err != nil {
|
|
t.Errorf("user2 should be allowed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestToolRateLimiter_WindowExpiry(t *testing.T) {
|
|
rl := &ToolRateLimiter{
|
|
windows: make(map[string][]time.Time),
|
|
maxPerHr: 2,
|
|
window: 100 * time.Millisecond, // short window for testing
|
|
}
|
|
|
|
// Fill the window
|
|
rl.Allow("key1")
|
|
rl.Allow("key1")
|
|
|
|
if err := rl.Allow("key1"); err == nil {
|
|
t.Error("should be blocked at limit")
|
|
}
|
|
|
|
// Wait for window to expire
|
|
time.Sleep(150 * time.Millisecond)
|
|
|
|
if err := rl.Allow("key1"); err != nil {
|
|
t.Errorf("should be allowed after window expiry: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestToolRateLimiter_Cleanup(t *testing.T) {
|
|
rl := &ToolRateLimiter{
|
|
windows: make(map[string][]time.Time),
|
|
maxPerHr: 10,
|
|
window: 50 * time.Millisecond,
|
|
}
|
|
|
|
rl.Allow("key1")
|
|
rl.Allow("key2")
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
rl.Cleanup()
|
|
|
|
rl.mu.Lock()
|
|
count := len(rl.windows)
|
|
rl.mu.Unlock()
|
|
|
|
if count != 0 {
|
|
t.Errorf("cleanup should remove all expired entries, got %d", count)
|
|
}
|
|
}
|
|
|
|
func TestToolRateLimiter_CleanupPartial(t *testing.T) {
|
|
rl := &ToolRateLimiter{
|
|
windows: make(map[string][]time.Time),
|
|
maxPerHr: 10,
|
|
window: 200 * time.Millisecond,
|
|
}
|
|
|
|
rl.Allow("key1") // will expire
|
|
time.Sleep(100 * time.Millisecond)
|
|
rl.Allow("key1") // still fresh
|
|
|
|
// Only the first entry should be pruned, not the whole key
|
|
time.Sleep(150 * time.Millisecond)
|
|
rl.Cleanup()
|
|
|
|
rl.mu.Lock()
|
|
entries := len(rl.windows["key1"])
|
|
rl.mu.Unlock()
|
|
|
|
if entries != 1 {
|
|
t.Errorf("expected 1 remaining entry, got %d", entries)
|
|
}
|
|
}
|