docs: reorder & testing

This commit is contained in:
Ilia Denisov
2026-05-07 00:58:53 +03:00
committed by GitHub
parent f446c6a2ac
commit 604fe40bcf
148 changed files with 9150 additions and 2757 deletions
+204
View File
@@ -0,0 +1,204 @@
package session_test
import (
"context"
"errors"
"sync"
"sync/atomic"
"testing"
"time"
"galaxy/gateway/internal/session"
)
// stubLookup is the BackendLookup test fake. lookups counts hits;
// records is the canonical source of truth keyed by device_session_id.
type stubLookup struct {
mu sync.Mutex
records map[string]session.Record
hits atomic.Int64
notFound bool
}
func newStubLookup() *stubLookup {
return &stubLookup{records: make(map[string]session.Record)}
}
func (s *stubLookup) put(rec session.Record) {
s.mu.Lock()
s.records[rec.DeviceSessionID] = rec
s.mu.Unlock()
}
func (s *stubLookup) LookupSession(_ context.Context, deviceSessionID string) (session.Record, error) {
s.hits.Add(1)
s.mu.Lock()
defer s.mu.Unlock()
if s.notFound {
return session.Record{}, session.ErrNotFound
}
rec, ok := s.records[deviceSessionID]
if !ok {
return session.Record{}, session.ErrNotFound
}
return rec, nil
}
func TestMemoryCacheLookupHitsCacheAfterFirstFetch(t *testing.T) {
stub := newStubLookup()
stub.put(session.Record{DeviceSessionID: "a", UserID: "u1", Status: session.StatusActive})
cache, err := session.NewMemoryCache(stub, session.MemoryCacheOptions{
MaxEntries: 10,
TTL: time.Hour,
})
if err != nil {
t.Fatalf("NewMemoryCache: %v", err)
}
if _, err := cache.Lookup(context.Background(), "a"); err != nil {
t.Fatalf("first lookup: %v", err)
}
if _, err := cache.Lookup(context.Background(), "a"); err != nil {
t.Fatalf("second lookup: %v", err)
}
if got := stub.hits.Load(); got != 1 {
t.Fatalf("backend hits = %d, want 1 (cache should serve the second call)", got)
}
}
func TestMemoryCacheLookupRefreshesOnTTLExpiry(t *testing.T) {
stub := newStubLookup()
stub.put(session.Record{DeviceSessionID: "a", UserID: "u1", Status: session.StatusActive})
clock := time.Unix(1_000_000, 0)
now := func() time.Time { return clock }
cache, err := session.NewMemoryCache(stub, session.MemoryCacheOptions{
MaxEntries: 10,
TTL: 100 * time.Millisecond,
Now: now,
})
if err != nil {
t.Fatalf("NewMemoryCache: %v", err)
}
if _, err := cache.Lookup(context.Background(), "a"); err != nil {
t.Fatalf("first lookup: %v", err)
}
clock = clock.Add(200 * time.Millisecond)
if _, err := cache.Lookup(context.Background(), "a"); err != nil {
t.Fatalf("post-TTL lookup: %v", err)
}
if got := stub.hits.Load(); got != 2 {
t.Fatalf("backend hits = %d, want 2 (TTL expiry should refetch)", got)
}
}
func TestMemoryCacheMarkRevokedFlipsCachedRecord(t *testing.T) {
stub := newStubLookup()
stub.put(session.Record{DeviceSessionID: "a", UserID: "u1", Status: session.StatusActive})
cache, err := session.NewMemoryCache(stub, session.MemoryCacheOptions{MaxEntries: 10, TTL: time.Hour})
if err != nil {
t.Fatalf("NewMemoryCache: %v", err)
}
if _, err := cache.Lookup(context.Background(), "a"); err != nil {
t.Fatalf("first lookup: %v", err)
}
cache.MarkRevoked("a")
rec, err := cache.Lookup(context.Background(), "a")
if err != nil {
t.Fatalf("post-revoke lookup: %v", err)
}
if rec.Status != session.StatusRevoked {
t.Fatalf("status = %q, want %q", rec.Status, session.StatusRevoked)
}
if got := stub.hits.Load(); got != 1 {
t.Fatalf("backend hits = %d, want 1 (MarkRevoked must not refetch)", got)
}
}
func TestMemoryCacheMarkAllRevokedForUserFlipsAllSessions(t *testing.T) {
stub := newStubLookup()
stub.put(session.Record{DeviceSessionID: "a", UserID: "u1", Status: session.StatusActive})
stub.put(session.Record{DeviceSessionID: "b", UserID: "u1", Status: session.StatusActive})
stub.put(session.Record{DeviceSessionID: "c", UserID: "u2", Status: session.StatusActive})
cache, err := session.NewMemoryCache(stub, session.MemoryCacheOptions{MaxEntries: 10, TTL: time.Hour})
if err != nil {
t.Fatalf("NewMemoryCache: %v", err)
}
for _, id := range []string{"a", "b", "c"} {
if _, err := cache.Lookup(context.Background(), id); err != nil {
t.Fatalf("seed %s: %v", id, err)
}
}
cache.MarkAllRevokedForUser("u1")
for _, id := range []string{"a", "b"} {
rec, err := cache.Lookup(context.Background(), id)
if err != nil {
t.Fatalf("post-revoke lookup %s: %v", id, err)
}
if rec.Status != session.StatusRevoked {
t.Fatalf("session %s status = %q, want revoked", id, rec.Status)
}
}
rec, err := cache.Lookup(context.Background(), "c")
if err != nil {
t.Fatalf("post-revoke lookup c: %v", err)
}
if rec.Status != session.StatusActive {
t.Fatalf("session c status = %q, want active (other user)", rec.Status)
}
}
func TestMemoryCacheLRUEvictsLeastRecentlyUsed(t *testing.T) {
stub := newStubLookup()
stub.put(session.Record{DeviceSessionID: "a", UserID: "u1", Status: session.StatusActive})
stub.put(session.Record{DeviceSessionID: "b", UserID: "u2", Status: session.StatusActive})
stub.put(session.Record{DeviceSessionID: "c", UserID: "u3", Status: session.StatusActive})
cache, err := session.NewMemoryCache(stub, session.MemoryCacheOptions{MaxEntries: 2, TTL: time.Hour})
if err != nil {
t.Fatalf("NewMemoryCache: %v", err)
}
if _, err := cache.Lookup(context.Background(), "a"); err != nil {
t.Fatalf("seed a: %v", err)
}
if _, err := cache.Lookup(context.Background(), "b"); err != nil {
t.Fatalf("seed b: %v", err)
}
if _, err := cache.Lookup(context.Background(), "c"); err != nil {
t.Fatalf("seed c: %v", err)
}
if got := cache.Len(); got != 2 {
t.Fatalf("Len = %d, want 2", got)
}
hitsBefore := stub.hits.Load()
if _, err := cache.Lookup(context.Background(), "a"); err != nil {
t.Fatalf("re-lookup a: %v", err)
}
if got := stub.hits.Load(); got != hitsBefore+1 {
t.Fatalf("backend hits = %d, want +1 (a was evicted)", got-hitsBefore)
}
}
func TestMemoryCachePropagatesBackendNotFound(t *testing.T) {
stub := newStubLookup()
stub.notFound = true
cache, err := session.NewMemoryCache(stub, session.MemoryCacheOptions{MaxEntries: 4, TTL: time.Hour})
if err != nil {
t.Fatalf("NewMemoryCache: %v", err)
}
_, err = cache.Lookup(context.Background(), "missing")
if !errors.Is(err, session.ErrNotFound) {
t.Fatalf("Lookup error = %v, want ErrNotFound", err)
}
}