Publish Docker image (Multi-arch) / Build & push (amd64) (push) Failing after 1m40s
Release (Linux, macOS, Windows) / Linux Release (push) Failing after 42m16s
Publish Docker image (Multi-arch) / Build & push (arm64) (push) Canceled after 0s
Release (Linux, macOS, Windows) / macOS Release (push) Canceled after 0s
Release (Linux, macOS, Windows) / Windows Release (push) Canceled after 0s
Publish Docker image (Multi-arch) / Create multi-arch manifests (push) Canceled after 0s
Sync production source including routing, billing, Astra detection and public uptime. Update custom build instructions and license packaging.
151 lines
3.8 KiB
Go
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)
|
|
}
|