(new): PollTimeout config, RateLimiter.Cleanup
Golang lint / lint (pull_request) Successful in 3m4s
Golang lint / lint (push) Successful in 3m5s

(fix): compact payload escape, Draft.push validation, plugin logger ownership, worker StopAndWait, getChatLimiter deadlock, runner ctx-after-tick
(refactor): remove NewPayload, buildSceneKey from sceneRuntime, unify ToJSON fallback
(tests): compact round-trip, Draft.push state, RateLimiter.Cleanup eviction
(doc): changelog v1.0.0 rewrite, NewCommand dual-use godoc
This commit is contained in:
2026-05-18 17:27:14 +03:00
parent 09fb9261df
commit affb802a7b
38 changed files with 697 additions and 345 deletions
+49
View File
@@ -39,3 +39,52 @@ func TestRateLimiterGlobalWaitRespectsContextCancellation(t *testing.T) {
t.Fatalf("expected DeadlineExceeded, got %v", err)
}
}
// TestRateLimiterCleanupEvictsIdleChats guards the memory-leak fix: per-chat
// limiter and lastSeen state must be reclaimed by Cleanup once the entry has
// been idle for longer than the threshold, while still-active chats and
// unexpired cooldowns must survive.
func TestRateLimiterCleanupEvictsIdleChats(t *testing.T) {
rl := NewRateLimiter()
// Touch chat 1 to make it tracked, then backdate its last-seen marker
// so it looks idle from Cleanup's perspective.
if !rl.Allow(1) {
t.Fatal("expected initial Allow for chat 1 to succeed")
}
rl.chatMu.Lock()
rl.chatLastSeen[1] = time.Now().Add(-time.Hour)
rl.chatMu.Unlock()
// Touch chat 2 so it stays "active".
if !rl.Allow(2) {
t.Fatal("expected initial Allow for chat 2 to succeed")
}
// Expired cooldown should be evicted; future cooldown should survive.
rl.SetChatLock(10, 1)
rl.chatMu.Lock()
rl.chatLocks[10] = time.Now().Add(-time.Second)
rl.chatLocks[11] = time.Now().Add(time.Hour)
rl.chatMu.Unlock()
rl.Cleanup(time.Minute)
rl.chatMu.RLock()
defer rl.chatMu.RUnlock()
if _, ok := rl.chatLimiters[1]; ok {
t.Fatal("expected idle chat 1 limiter to be evicted")
}
if _, ok := rl.chatLastSeen[1]; ok {
t.Fatal("expected idle chat 1 lastSeen to be evicted")
}
if _, ok := rl.chatLimiters[2]; !ok {
t.Fatal("expected active chat 2 limiter to remain")
}
if _, ok := rl.chatLocks[10]; ok {
t.Fatal("expected expired chat 10 lock to be evicted")
}
if _, ok := rl.chatLocks[11]; !ok {
t.Fatal("expected future chat 11 lock to remain")
}
}