Files
warmbly/internal/app/token/verify_test.go
T
Matthew Meszaros ddd76eeb73 fix: stop a Redis outage locking everyone out, and let the tracking encoder register its schema (#537)
* feat: treat a session cache that cannot answer as a miss rather than an authentication failure, because the session lives in Postgres and reading it from there is what the caller already falls back to, so a Redis outage costs a database read per request instead of locking every user out of the dashboard with a 500

* feat: carry the tracking event schema with the subject-name strategy so the encoder registers it instead of only looking one up, because a registry with no tracking-events subject had nothing to find and answered every event with 'Could not get id from response', dropping every open and click on the floor
2026-09-15 21:19:03 -07:00

60 lines
1.8 KiB
Go

package token
import (
"context"
"testing"
"time"
"github.com/redis/go-redis/v9"
"github.com/google/uuid"
"github.com/warmbly/warmbly/internal/errx"
"github.com/warmbly/warmbly/internal/infrastructure/cache"
"github.com/warmbly/warmbly/internal/models"
"github.com/warmbly/warmbly/internal/repository"
)
// A cache that cannot answer must not be able to sign everyone out.
//
// It could. The session read returned InternalError on any Redis error that was
// not a plain miss, and the write that followed a database read returned its
// error too. So when Redis began refusing writes on a quota, every
// authenticated request failed with a 500 and nobody could log in, while the
// sessions themselves sat intact in Postgres the whole time.
//
// The cache here points at a closed port, which is the real failure rather than
// a mocked one.
type sessionRepo struct {
repository.TokenRepository
session *models.Session
calls int
}
func (r *sessionRepo) GetSession(_ context.Context, _ uuid.UUID) (*models.Session, *errx.Error) {
r.calls++
return r.session, nil
}
func TestASessionSurvivesACacheThatIsDown(t *testing.T) {
// Built directly rather than through cache.New, which pings on construction
// and would fail before the test could start.
down := &cache.Cache{Client: redis.NewClient(&redis.Options{
Addr: "127.0.0.1:1",
DialTimeout: 200 * time.Millisecond,
})}
id := uuid.New()
repo := &sessionRepo{session: &models.Session{ID: id, UserID: uuid.New()}}
s := &tokenService{cache: down, tokenRepository: repo}
got, xerr := s.GetSession(context.Background(), id)
if xerr != nil {
t.Fatalf("a cache outage failed the request: %v", xerr)
}
if got == nil || got.ID != id {
t.Fatalf("session = %+v, want id %s", got, id)
}
if repo.calls != 1 {
t.Fatalf("read the database %d times, want 1", repo.calls)
}
}