Files
cneicy ceddfd3d6b feat: sync deployed v1.0.0-rc.38-custom.42
Sync production source including routing, billing, Astra detection and public uptime. Update custom build instructions and license packaging.
2026-09-26 04:49:11 +08:00

151 lines
3.8 KiB
Go

package common
import (
"sync"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestInMemoryRateLimiterAllocatesTimestampsOnDemand(t *testing.T) {
var limiter InMemoryRateLimiter
limiter.Init(0)
require.True(t, limiter.Request("client", 1_000_000, 60))
entry := limiter.store["client"]
require.NotNil(t, entry)
assert.Equal(t, 1, entry.requests.length)
assert.Same(t, entry.requests.head, entry.requests.tail)
assert.Nil(t, entry.requests.head.next)
limiter.deleteExpiredEntries(time.Now().Add(time.Hour))
assert.Contains(t, limiter.store, "client")
}
func TestInMemoryRateLimiterEnforcesWindowBoundary(t *testing.T) {
var queue rateLimitQueue
queue.append(100)
queue.append(100)
queue.removeExpired(159, 60)
assert.Equal(t, 2, queue.length)
queue.removeExpired(160, 60)
assert.Zero(t, queue.length)
assert.Nil(t, queue.head)
assert.Nil(t, queue.tail)
}
func TestInMemoryRateLimiterRemovesAllExpiredRequestsPerCall(t *testing.T) {
var queue rateLimitQueue
for range 5 {
queue.append(100)
}
queue.append(110)
queue.removeExpired(110, 10)
assert.Equal(t, 1, queue.length)
require.NotNil(t, queue.head)
assert.EqualValues(t, 110, queue.head.timestamp)
assert.Same(t, queue.head, queue.tail)
}
func TestInMemoryRateLimiterKeepsNonExpiredRequests(t *testing.T) {
var queue rateLimitQueue
for range 3 {
queue.append(100)
}
queue.removeExpired(109, 10)
assert.Equal(t, 3, queue.length)
assert.NotNil(t, queue.head)
assert.NotNil(t, queue.tail)
}
func TestInMemoryRateLimiterEvictsOnlyExpiredLRUTail(t *testing.T) {
var limiter InMemoryRateLimiter
limiter.Init(0)
limiter.expirationDuration = 10 * time.Second
require.True(t, limiter.Request("active", 1, 100))
require.True(t, limiter.Request("idle", 1, 100))
assert.False(t, limiter.Request("active", 1, 100))
assert.Equal(t, "active", limiter.lru.Front().Value.(*rateLimitEntry).key)
assert.Equal(t, "idle", limiter.lru.Back().Value.(*rateLimitEntry).key)
idleEntry := limiter.store["idle"]
now := time.Now()
idleEntry.lastActive = now.Add(-10 * time.Second)
limiter.store["active"].lastActive = now
limiter.deleteExpiredEntries(now)
assert.NotContains(t, limiter.store, "idle")
assert.Contains(t, limiter.store, "active")
assert.Equal(t, 1, limiter.lru.Len())
assert.Nil(t, idleEntry.element)
assert.Nil(t, idleEntry.requests.head)
assert.Nil(t, idleEntry.requests.tail)
assert.Zero(t, idleEntry.requests.length)
}
func TestInMemoryRateLimiterCleanupTicksEvictExpiredKeys(t *testing.T) {
var limiter InMemoryRateLimiter
limiter.Init(0)
limiter.expirationDuration = 10 * time.Second
require.True(t, limiter.Request("idle", 1, 100))
now := time.Now()
limiter.store["idle"].lastActive = now.Add(-10 * time.Second)
ticks := make(chan time.Time, 1)
done := make(chan struct{})
go func() {
limiter.clearExpiredItems(ticks)
close(done)
}()
ticks <- now
close(ticks)
<-done
assert.Empty(t, limiter.store)
assert.Zero(t, limiter.lru.Len())
}
func TestInMemoryRateLimiterInitKeepsFirstConfiguration(t *testing.T) {
var limiter InMemoryRateLimiter
limiter.Init(time.Hour)
limiter.Init(0)
assert.Equal(t, time.Hour, limiter.expirationDuration)
require.True(t, limiter.Request("client", 1, 60))
limiter.deleteExpiredEntries(time.Now().Add(time.Hour))
assert.NotContains(t, limiter.store, "client")
}
func TestInMemoryRateLimiterConcurrentInitializationAndRequests(t *testing.T) {
var limiter InMemoryRateLimiter
var allowed atomic.Int64
var waitGroup sync.WaitGroup
for range 100 {
waitGroup.Add(1)
go func() {
defer waitGroup.Done()
limiter.Init(0)
if limiter.Request("client", 10, 60) {
allowed.Add(1)
}
}()
}
waitGroup.Wait()
assert.EqualValues(t, 10, allowed.Load())
assert.Len(t, limiter.store, 1)
assert.Equal(t, 10, limiter.store["client"].requests.length)
}