proxy-pool/internal/gateway/affinity/table_test.go
2026-08-02 16:47:51 +08:00

109 lines
3.2 KiB
Go

package affinity
import (
"testing"
"time"
)
func TestTableKeepsBindingsIsolatedAndExpiresThem(t *testing.T) {
t.Parallel()
now := time.Date(2026, time.August, 2, 9, 0, 0, 0, time.UTC)
table, err := NewTable(Options{
MaxEntries: 2,
Now: func() time.Time { return now },
})
if err != nil {
t.Fatalf("NewTable() error = %v", err)
}
first, err := NewKey("client-a", "route-a", "session-a")
if err != nil {
t.Fatalf("NewKey(first) error = %v", err)
}
second, err := NewKey("client-b", "route-a", "session-a")
if err != nil {
t.Fatalf("NewKey(second) error = %v", err)
}
if first == second {
t.Fatal("same session under distinct clients must have different affinity keys")
}
table.Bind(first, "proxy-a", now.Add(time.Minute))
table.Bind(second, "proxy-b", now.Add(time.Minute))
if binding, ok := table.Lookup(first); !ok || binding.ProxyID != "proxy-a" {
t.Fatalf("Lookup(first) = (%+v, %v), want proxy-a", binding, ok)
}
now = now.Add(time.Minute)
if _, ok := table.Lookup(first); ok {
t.Fatal("Lookup(first) retained an expired binding")
}
}
func TestTableEvictsLeastRecentlyUsedBindingAtCapacity(t *testing.T) {
t.Parallel()
now := time.Date(2026, time.August, 2, 9, 0, 0, 0, time.UTC)
table, err := NewTable(Options{
MaxEntries: 2,
Now: func() time.Time { return now },
})
if err != nil {
t.Fatalf("NewTable() error = %v", err)
}
first := mustKey(t, "client-a", "route-a", "session-a")
second := mustKey(t, "client-a", "route-a", "session-b")
third := mustKey(t, "client-a", "route-a", "session-c")
expiresAt := now.Add(time.Minute)
table.Bind(first, "proxy-a", expiresAt)
table.Bind(second, "proxy-b", expiresAt)
if _, ok := table.Lookup(first); !ok {
t.Fatal("Lookup(first) must retain the binding")
}
table.Bind(third, "proxy-c", expiresAt)
if _, ok := table.Lookup(second); ok {
t.Fatal("Lookup(second) retained least recently used binding after capacity eviction")
}
if binding, ok := table.Lookup(first); !ok || binding.ProxyID != "proxy-a" {
t.Fatalf("Lookup(first) = (%+v, %v), want proxy-a", binding, ok)
}
if binding, ok := table.Lookup(third); !ok || binding.ProxyID != "proxy-c" {
t.Fatalf("Lookup(third) = (%+v, %v), want proxy-c", binding, ok)
}
}
func TestNewKeyRejectsInvalidInputs(t *testing.T) {
t.Parallel()
for _, test := range []struct {
name string
clientID string
routing string
sessionID string
}{
{name: "empty client", routing: "route-a", sessionID: "session-a"},
{name: "empty routing", clientID: "client-a", sessionID: "session-a"},
{name: "empty session", clientID: "client-a", routing: "route-a"},
{name: "oversized session", clientID: "client-a", routing: "route-a", sessionID: string(make([]byte, 129))},
{name: "space session", clientID: "client-a", routing: "route-a", sessionID: "session a"},
} {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
if _, err := NewKey(test.clientID, test.routing, test.sessionID); err == nil {
t.Fatal("NewKey() error = nil, want invalid input")
}
})
}
}
func mustKey(t *testing.T, clientID, routing, sessionID string) Key {
t.Helper()
key, err := NewKey(clientID, routing, sessionID)
if err != nil {
t.Fatalf("NewKey() error = %v", err)
}
return key
}