332 lines
8.6 KiB
Go
332 lines
8.6 KiB
Go
package session
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"galaxy/gateway/internal/config"
|
|
|
|
"github.com/alicebob/miniredis/v2"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestNewRedisCache(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
server := miniredis.RunT(t)
|
|
|
|
tests := []struct {
|
|
name string
|
|
cfg config.SessionCacheRedisConfig
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "valid config",
|
|
cfg: config.SessionCacheRedisConfig{
|
|
Addr: server.Addr(),
|
|
DB: 2,
|
|
KeyPrefix: "gateway:session:",
|
|
LookupTimeout: 250 * time.Millisecond,
|
|
},
|
|
},
|
|
{
|
|
name: "empty addr",
|
|
cfg: config.SessionCacheRedisConfig{
|
|
LookupTimeout: 250 * time.Millisecond,
|
|
},
|
|
wantErr: "redis addr must not be empty",
|
|
},
|
|
{
|
|
name: "negative db",
|
|
cfg: config.SessionCacheRedisConfig{
|
|
Addr: server.Addr(),
|
|
DB: -1,
|
|
LookupTimeout: 250 * time.Millisecond,
|
|
},
|
|
wantErr: "redis db must not be negative",
|
|
},
|
|
{
|
|
name: "non-positive lookup timeout",
|
|
cfg: config.SessionCacheRedisConfig{
|
|
Addr: server.Addr(),
|
|
},
|
|
wantErr: "lookup timeout must be positive",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cache, err := NewRedisCache(tt.cfg)
|
|
if tt.wantErr != "" {
|
|
require.Error(t, err)
|
|
require.ErrorContains(t, err, tt.wantErr)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() {
|
|
assert.NoError(t, cache.Close())
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRedisCachePing(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
server := miniredis.RunT(t)
|
|
cache := newTestRedisCache(t, server, config.SessionCacheRedisConfig{})
|
|
|
|
require.NoError(t, cache.Ping(context.Background()))
|
|
}
|
|
|
|
func TestRedisCacheLookup(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
revokedAtMS := int64(123456789)
|
|
|
|
tests := []struct {
|
|
name string
|
|
cfg config.SessionCacheRedisConfig
|
|
requestID string
|
|
seed func(*testing.T, *miniredis.Miniredis, config.SessionCacheRedisConfig)
|
|
want Record
|
|
wantErrIs error
|
|
wantErrText string
|
|
assertErrText string
|
|
}{
|
|
{
|
|
name: "active cache hit",
|
|
requestID: "device-session-123",
|
|
cfg: config.SessionCacheRedisConfig{
|
|
KeyPrefix: "gateway:session:",
|
|
},
|
|
seed: func(t *testing.T, server *miniredis.Miniredis, cfg config.SessionCacheRedisConfig) {
|
|
t.Helper()
|
|
setRedisSessionRecord(t, server, cfg.KeyPrefix+"device-session-123", redisRecord{
|
|
DeviceSessionID: "device-session-123",
|
|
UserID: "user-123",
|
|
ClientPublicKey: "public-key-123",
|
|
Status: StatusActive,
|
|
})
|
|
},
|
|
want: Record{
|
|
DeviceSessionID: "device-session-123",
|
|
UserID: "user-123",
|
|
ClientPublicKey: "public-key-123",
|
|
Status: StatusActive,
|
|
},
|
|
},
|
|
{
|
|
name: "missing session",
|
|
requestID: "device-session-404",
|
|
cfg: config.SessionCacheRedisConfig{
|
|
KeyPrefix: "gateway:session:",
|
|
},
|
|
wantErrIs: ErrNotFound,
|
|
assertErrText: "session cache record not found",
|
|
},
|
|
{
|
|
name: "revoked session",
|
|
requestID: "device-session-revoked",
|
|
cfg: config.SessionCacheRedisConfig{
|
|
KeyPrefix: "gateway:session:",
|
|
},
|
|
seed: func(t *testing.T, server *miniredis.Miniredis, cfg config.SessionCacheRedisConfig) {
|
|
t.Helper()
|
|
setRedisSessionRecord(t, server, cfg.KeyPrefix+"device-session-revoked", redisRecord{
|
|
DeviceSessionID: "device-session-revoked",
|
|
UserID: "user-777",
|
|
ClientPublicKey: "public-key-777",
|
|
Status: StatusRevoked,
|
|
RevokedAtMS: &revokedAtMS,
|
|
})
|
|
},
|
|
want: Record{
|
|
DeviceSessionID: "device-session-revoked",
|
|
UserID: "user-777",
|
|
ClientPublicKey: "public-key-777",
|
|
Status: StatusRevoked,
|
|
RevokedAtMS: &revokedAtMS,
|
|
},
|
|
},
|
|
{
|
|
name: "malformed json",
|
|
requestID: "device-session-bad-json",
|
|
cfg: config.SessionCacheRedisConfig{
|
|
KeyPrefix: "gateway:session:",
|
|
},
|
|
seed: func(t *testing.T, server *miniredis.Miniredis, cfg config.SessionCacheRedisConfig) {
|
|
t.Helper()
|
|
server.Set(cfg.KeyPrefix+"device-session-bad-json", "{")
|
|
},
|
|
wantErrText: "decode redis session record",
|
|
},
|
|
{
|
|
name: "unknown status",
|
|
requestID: "device-session-unknown-status",
|
|
cfg: config.SessionCacheRedisConfig{
|
|
KeyPrefix: "gateway:session:",
|
|
},
|
|
seed: func(t *testing.T, server *miniredis.Miniredis, cfg config.SessionCacheRedisConfig) {
|
|
t.Helper()
|
|
setRedisSessionRecord(t, server, cfg.KeyPrefix+"device-session-unknown-status", redisRecord{
|
|
DeviceSessionID: "device-session-unknown-status",
|
|
UserID: "user-1",
|
|
ClientPublicKey: "public-key-1",
|
|
Status: Status("paused"),
|
|
})
|
|
},
|
|
wantErrText: `status "paused" is unsupported`,
|
|
},
|
|
{
|
|
name: "missing required field",
|
|
requestID: "device-session-missing-user",
|
|
cfg: config.SessionCacheRedisConfig{
|
|
KeyPrefix: "gateway:session:",
|
|
},
|
|
seed: func(t *testing.T, server *miniredis.Miniredis, cfg config.SessionCacheRedisConfig) {
|
|
t.Helper()
|
|
setRedisSessionRecord(t, server, cfg.KeyPrefix+"device-session-missing-user", redisRecord{
|
|
DeviceSessionID: "device-session-missing-user",
|
|
ClientPublicKey: "public-key-1",
|
|
Status: StatusActive,
|
|
})
|
|
},
|
|
wantErrText: "user_id must not be empty",
|
|
},
|
|
{
|
|
name: "device session id mismatch",
|
|
requestID: "device-session-requested",
|
|
cfg: config.SessionCacheRedisConfig{
|
|
KeyPrefix: "gateway:session:",
|
|
},
|
|
seed: func(t *testing.T, server *miniredis.Miniredis, cfg config.SessionCacheRedisConfig) {
|
|
t.Helper()
|
|
setRedisSessionRecord(t, server, cfg.KeyPrefix+"device-session-requested", redisRecord{
|
|
DeviceSessionID: "device-session-other",
|
|
UserID: "user-1",
|
|
ClientPublicKey: "public-key-1",
|
|
Status: StatusActive,
|
|
})
|
|
},
|
|
wantErrText: `does not match requested "device-session-requested"`,
|
|
},
|
|
{
|
|
name: "key prefix is honored",
|
|
requestID: "device-session-prefixed",
|
|
cfg: config.SessionCacheRedisConfig{
|
|
KeyPrefix: "custom:session:",
|
|
},
|
|
seed: func(t *testing.T, server *miniredis.Miniredis, cfg config.SessionCacheRedisConfig) {
|
|
t.Helper()
|
|
setRedisSessionRecord(t, server, cfg.KeyPrefix+"device-session-prefixed", redisRecord{
|
|
DeviceSessionID: "device-session-prefixed",
|
|
UserID: "user-prefixed",
|
|
ClientPublicKey: "public-key-prefixed",
|
|
Status: StatusActive,
|
|
})
|
|
setRedisSessionRecord(t, server, "gateway:session:device-session-prefixed", redisRecord{
|
|
DeviceSessionID: "device-session-prefixed",
|
|
UserID: "wrong-user",
|
|
ClientPublicKey: "wrong-key",
|
|
Status: StatusRevoked,
|
|
})
|
|
},
|
|
want: Record{
|
|
DeviceSessionID: "device-session-prefixed",
|
|
UserID: "user-prefixed",
|
|
ClientPublicKey: "public-key-prefixed",
|
|
Status: StatusActive,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
server := miniredis.RunT(t)
|
|
|
|
cfg := tt.cfg
|
|
cfg.Addr = server.Addr()
|
|
cfg.DB = 0
|
|
cfg.LookupTimeout = 250 * time.Millisecond
|
|
|
|
if tt.seed != nil {
|
|
tt.seed(t, server, cfg)
|
|
}
|
|
|
|
cache := newTestRedisCache(t, server, cfg)
|
|
record, err := cache.Lookup(context.Background(), tt.requestID)
|
|
if tt.wantErrIs != nil || tt.wantErrText != "" {
|
|
require.Error(t, err)
|
|
if tt.wantErrIs != nil {
|
|
assert.ErrorIs(t, err, tt.wantErrIs)
|
|
}
|
|
if tt.wantErrText != "" {
|
|
assert.ErrorContains(t, err, tt.wantErrText)
|
|
}
|
|
if tt.assertErrText != "" {
|
|
assert.ErrorContains(t, err, tt.assertErrText)
|
|
}
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tt.want, record)
|
|
})
|
|
}
|
|
}
|
|
|
|
func newTestRedisCache(t *testing.T, server *miniredis.Miniredis, cfg config.SessionCacheRedisConfig) *RedisCache {
|
|
t.Helper()
|
|
|
|
if cfg.Addr == "" {
|
|
cfg.Addr = server.Addr()
|
|
}
|
|
if cfg.LookupTimeout == 0 {
|
|
cfg.LookupTimeout = 250 * time.Millisecond
|
|
}
|
|
|
|
cache, err := NewRedisCache(cfg)
|
|
require.NoError(t, err)
|
|
|
|
t.Cleanup(func() {
|
|
assert.NoError(t, cache.Close())
|
|
})
|
|
|
|
return cache
|
|
}
|
|
|
|
func setRedisSessionRecord(t *testing.T, server *miniredis.Miniredis, key string, record redisRecord) {
|
|
t.Helper()
|
|
|
|
payload, err := json.Marshal(record)
|
|
require.NoError(t, err)
|
|
|
|
server.Set(key, string(payload))
|
|
}
|
|
|
|
func TestRedisCacheLookupNilContext(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
server := miniredis.RunT(t)
|
|
cache := newTestRedisCache(t, server, config.SessionCacheRedisConfig{})
|
|
|
|
_, err := cache.Lookup(context.TODO(), "device-session-123")
|
|
require.Error(t, err)
|
|
assert.False(t, errors.Is(err, ErrNotFound))
|
|
assert.ErrorContains(t, err, "nil context")
|
|
}
|