mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-09-05 23:50:29 +08:00
- replace O(N log N) sorting under global write lock with container/list LRU eviction - track primary alias groups for O(1) group lookup and clean group eviction - capture timestamps inside lock and add nil receiver guards
171 lines
4.2 KiB
Go
171 lines
4.2 KiB
Go
package auth
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestSessionCache_CapacityEvictionOrder(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
maxEntries := 5
|
|
cache := NewSessionCacheWithCapacity(time.Hour, maxEntries)
|
|
defer cache.Stop()
|
|
|
|
// Insert 5 entries (fill cache to capacity)
|
|
for i := 1; i <= 5; i++ {
|
|
cache.Set(fmt.Sprintf("sess-%d", i), fmt.Sprintf("auth-%d", i))
|
|
}
|
|
|
|
if cache.Len() != 5 {
|
|
t.Fatalf("cache.Len() = %d, want 5", cache.Len())
|
|
}
|
|
|
|
// Insert 6th entry: oldest (sess-1) should be evicted
|
|
cache.Set("sess-6", "auth-6")
|
|
|
|
if cache.Len() > maxEntries {
|
|
t.Fatalf("cache.Len() = %d, want <= %d", cache.Len(), maxEntries)
|
|
}
|
|
|
|
if _, ok := cache.Get("sess-1"); ok {
|
|
t.Fatal("expected oldest entry sess-1 to be evicted")
|
|
}
|
|
|
|
for i := 2; i <= 6; i++ {
|
|
if got, ok := cache.Get(fmt.Sprintf("sess-%d", i)); !ok || got != fmt.Sprintf("auth-%d", i) {
|
|
t.Fatalf("Get(sess-%d) = %q, %v; want auth-%d, true", i, got, ok, i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSessionCache_MultiAliasGroupEviction(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
maxEntries := 4
|
|
cache := NewSessionCacheWithCapacity(time.Hour, maxEntries)
|
|
defer cache.Stop()
|
|
|
|
// Group 1: 2 aliases (s1-a, s1-b)
|
|
cache.SetAliases("auth-1", "s1-a", "s1-b")
|
|
// Group 2: 2 aliases (s2-a, s2-b)
|
|
cache.SetAliases("auth-2", "s2-a", "s2-b")
|
|
|
|
if cache.Len() != 4 {
|
|
t.Fatalf("cache.Len() = %d, want 4", cache.Len())
|
|
}
|
|
|
|
// Group 3: 1 alias (s3)
|
|
// Inserting s3 should evict Group 1 completely (both s1-a and s1-b)
|
|
cache.Set("s3", "auth-3")
|
|
|
|
if cache.Len() > maxEntries {
|
|
t.Fatalf("cache.Len() = %d, want <= %d", cache.Len(), maxEntries)
|
|
}
|
|
|
|
if _, ok := cache.Get("s1-a"); ok {
|
|
t.Fatal("expected s1-a to be evicted")
|
|
}
|
|
if _, ok := cache.Get("s1-b"); ok {
|
|
t.Fatal("expected s1-b to be evicted")
|
|
}
|
|
|
|
if got, ok := cache.Get("s2-a"); !ok || got != "auth-2" {
|
|
t.Fatalf("Get(s2-a) = %q, %v", got, ok)
|
|
}
|
|
if got, ok := cache.Get("s3"); !ok || got != "auth-3" {
|
|
t.Fatalf("Get(s3) = %q, %v", got, ok)
|
|
}
|
|
}
|
|
|
|
func TestSessionCache_HighThroughputCapacitySaturated(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
maxEntries := 1000
|
|
cache := NewSessionCacheWithCapacity(time.Hour, maxEntries)
|
|
defer cache.Stop()
|
|
|
|
// Rapidly write 10,000 entries through a small capacity cache to ensure O(1) eviction
|
|
for i := 0; i < 10000; i++ {
|
|
cache.Set(fmt.Sprintf("sess-%d", i), fmt.Sprintf("auth-%d", i%10))
|
|
}
|
|
|
|
if cache.Len() > maxEntries {
|
|
t.Fatalf("cache.Len() = %d, want <= %d", cache.Len(), maxEntries)
|
|
}
|
|
|
|
// Latest entries must be present
|
|
for i := 9900; i < 10000; i++ {
|
|
if got, ok := cache.Get(fmt.Sprintf("sess-%d", i)); !ok || got != fmt.Sprintf("auth-%d", i%10) {
|
|
t.Fatalf("Get(sess-%d) = %q, %v", i, got, ok)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSessionCache_ConcurrentSaturatedAccess(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
maxEntries := 50
|
|
cache := NewSessionCacheWithCapacity(time.Hour, maxEntries)
|
|
defer cache.Stop()
|
|
|
|
var wg sync.WaitGroup
|
|
for worker := 0; worker < 8; worker++ {
|
|
wg.Add(1)
|
|
go func(w int) {
|
|
defer wg.Done()
|
|
for i := 0; i < 500; i++ {
|
|
key := fmt.Sprintf("worker-%d-sess-%d", w, i)
|
|
cache.Set(key, fmt.Sprintf("auth-%d", w))
|
|
cache.Get(key)
|
|
if i%5 == 0 {
|
|
cache.Touch(key, fmt.Sprintf("auth-%d", w))
|
|
}
|
|
if i%7 == 0 {
|
|
cache.CompareAndDelete(key, fmt.Sprintf("auth-%d", w))
|
|
}
|
|
}
|
|
}(worker)
|
|
}
|
|
wg.Wait()
|
|
|
|
if cache.Len() > maxEntries {
|
|
t.Fatalf("cache.Len() = %d, want <= %d", cache.Len(), maxEntries)
|
|
}
|
|
}
|
|
|
|
func TestSessionCache_NilReceiverSafety(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var cache *SessionCache
|
|
if got, ok := cache.Get("session-1"); ok || got != "" {
|
|
t.Fatalf("nil.Get() = %q, %v; want '', false", got, ok)
|
|
}
|
|
if got, ok := cache.GetAndRefresh("session-1"); ok || got != "" {
|
|
t.Fatalf("nil.GetAndRefresh() = %q, %v; want '', false", got, ok)
|
|
}
|
|
cache.Set("session-1", "auth-1")
|
|
cache.SetAliases("auth-1", "s1", "s2")
|
|
if ok := cache.Touch("session-1", "auth-1"); ok {
|
|
t.Fatal("nil.Touch() unexpectedly succeeded")
|
|
}
|
|
if ok := cache.CompareAndDelete("session-1", "auth-1"); ok {
|
|
t.Fatal("nil.CompareAndDelete() unexpectedly succeeded")
|
|
}
|
|
cache.Invalidate("session-1")
|
|
cache.InvalidateAuth("auth-1")
|
|
if n := cache.Len(); n != 0 {
|
|
t.Fatalf("nil.Len() = %d, want 0", n)
|
|
}
|
|
cache.Stop()
|
|
}
|
|
|
|
func TestSessionCache_StopNilChannelNoPanic(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
zeroCache := &SessionCache{}
|
|
zeroCache.Stop()
|
|
}
|