//go:build integration package inttest import ( "context" "errors" "testing" "github.com/google/uuid" "scrabble/backend/internal/account" "scrabble/backend/internal/session" ) // TestSessionLifecycle covers create, cache-hit resolve, DB-fallback resolve // after a cold cache warm, idempotent revoke, and post-revoke resolution. func TestSessionLifecycle(t *testing.T) { ctx := context.Background() acc, err := account.NewStore(testDB).ProvisionByIdentity(ctx, account.KindTelegram, "tg-"+uuid.NewString()) if err != nil { t.Fatalf("provision account: %v", err) } store := session.NewStore(testDB) svc := session.NewService(store, session.NewCache()) token, sess, err := svc.Create(ctx, acc.ID) if err != nil { t.Fatalf("create session: %v", err) } if sess.AccountID != acc.ID { t.Errorf("session account = %s, want %s", sess.AccountID, acc.ID) } if token == sess.TokenHash { t.Error("plaintext token must not equal the stored hash") } // Resolve via the warm write-through cache. got, err := svc.Resolve(ctx, token) if err != nil { t.Fatalf("resolve (cache): %v", err) } if got.ID != sess.ID { t.Errorf("resolve id = %s, want %s", got.ID, sess.ID) } // An unknown token is not found. if _, err := svc.Resolve(ctx, "not-a-real-token"); !errors.Is(err, session.ErrNotFound) { t.Errorf("resolve unknown = %v, want ErrNotFound", err) } // A fresh service with a cold cache resolves through the DB after Warm. cold := session.NewCache() svc2 := session.NewService(store, cold) if err := svc2.Warm(ctx); err != nil { t.Fatalf("warm: %v", err) } if !cold.Ready() { t.Error("cache must be ready after Warm") } if _, ok := cold.Get(session.HashToken(token)); !ok { t.Error("Warm must load the active session into the cache") } if got2, err := svc2.Resolve(ctx, token); err != nil || got2.ID != sess.ID { t.Errorf("resolve after warm = (%s, %v), want %s", got2.ID, err, sess.ID) } // Revoke, then the token no longer resolves; revoke again is a no-op. if err := svc.Revoke(ctx, token); err != nil { t.Fatalf("revoke: %v", err) } if _, err := svc.Resolve(ctx, token); !errors.Is(err, session.ErrNotFound) { t.Errorf("resolve after revoke = %v, want ErrNotFound", err) } if err := svc.Revoke(ctx, token); err != nil { t.Errorf("idempotent revoke: %v", err) } }