package configprovider import ( "context" "strconv" "testing" "time" "galaxy/authsession/internal/adapters/contracttest" "galaxy/authsession/internal/ports" "github.com/alicebob/miniredis/v2" "github.com/redis/go-redis/v9" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func newRedisClient(t *testing.T, server *miniredis.Miniredis) *redis.Client { t.Helper() client := redis.NewClient(&redis.Options{ Addr: server.Addr(), Protocol: 2, DisableIdentity: true, }) t.Cleanup(func() { assert.NoError(t, client.Close()) }) return client } func TestStoreContract(t *testing.T) { t.Parallel() contracttest.RunConfigProviderContractTests(t, func(t *testing.T) contracttest.ConfigProviderHarness { t.Helper() server := miniredis.RunT(t) store := newTestStore(t, server, Config{}) return contracttest.ConfigProviderHarness{ Provider: store, SeedDisabled: func(t *testing.T) { t.Helper() server.Del(store.sessionLimitKey) }, SeedLimit: func(t *testing.T, limit int) { t.Helper() server.Set(store.sessionLimitKey, strconv.Itoa(limit)) }, } }) } func TestNew(t *testing.T) { t.Parallel() server := miniredis.RunT(t) client := newRedisClient(t, server) validCfg := Config{ SessionLimitKey: "authsession:config:active-session-limit", OperationTimeout: 250 * time.Millisecond, } tests := []struct { name string client *redis.Client cfg Config wantErr string }{ {name: "valid config", client: client, cfg: validCfg}, {name: "nil client", client: nil, cfg: validCfg, wantErr: "nil redis client"}, { name: "empty session limit key", client: client, cfg: Config{OperationTimeout: 250 * time.Millisecond}, wantErr: "session limit key must not be empty", }, { name: "non positive timeout", client: client, cfg: Config{SessionLimitKey: "authsession:config:active-session-limit"}, wantErr: "operation timeout must be positive", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() store, err := New(tt.client, tt.cfg) if tt.wantErr != "" { require.Error(t, err) assert.ErrorContains(t, err, tt.wantErr) return } require.NoError(t, err) require.NotNil(t, store) }) } } func TestStoreLoadSessionLimit(t *testing.T) { t.Parallel() tests := []struct { name string seed func(*testing.T, *miniredis.Miniredis, *Store) wantConfig ports.SessionLimitConfig }{ { name: "missing key means disabled", wantConfig: ports.SessionLimitConfig{}, }, { name: "valid positive integer", seed: func(t *testing.T, server *miniredis.Miniredis, store *Store) { t.Helper() server.Set(store.sessionLimitKey, "5") }, wantConfig: configWithLimit(5), }, { name: "empty string is invalid and disabled", seed: func(t *testing.T, server *miniredis.Miniredis, store *Store) { t.Helper() server.Set(store.sessionLimitKey, "") }, wantConfig: ports.SessionLimitConfig{}, }, { name: "whitespace only is invalid and disabled", seed: func(t *testing.T, server *miniredis.Miniredis, store *Store) { t.Helper() server.Set(store.sessionLimitKey, " ") }, wantConfig: ports.SessionLimitConfig{}, }, { name: "whitespace padded integer is invalid and disabled", seed: func(t *testing.T, server *miniredis.Miniredis, store *Store) { t.Helper() server.Set(store.sessionLimitKey, " 5 ") }, wantConfig: ports.SessionLimitConfig{}, }, { name: "non integer text is invalid and disabled", seed: func(t *testing.T, server *miniredis.Miniredis, store *Store) { t.Helper() server.Set(store.sessionLimitKey, "five") }, wantConfig: ports.SessionLimitConfig{}, }, { name: "zero is invalid and disabled", seed: func(t *testing.T, server *miniredis.Miniredis, store *Store) { t.Helper() server.Set(store.sessionLimitKey, "0") }, wantConfig: ports.SessionLimitConfig{}, }, { name: "negative integer is invalid and disabled", seed: func(t *testing.T, server *miniredis.Miniredis, store *Store) { t.Helper() server.Set(store.sessionLimitKey, "-3") }, wantConfig: ports.SessionLimitConfig{}, }, { name: "overflow is invalid and disabled", seed: func(t *testing.T, server *miniredis.Miniredis, store *Store) { t.Helper() server.Set(store.sessionLimitKey, "999999999999999999999999999999") }, wantConfig: ports.SessionLimitConfig{}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() server := miniredis.RunT(t) store := newTestStore(t, server, Config{}) if tt.seed != nil { tt.seed(t, server, store) } got, err := store.LoadSessionLimit(context.Background()) require.NoError(t, err) assert.Equal(t, tt.wantConfig, got) }) } } func TestStoreLoadSessionLimitBackendFailure(t *testing.T) { t.Parallel() server := miniredis.RunT(t) store := newTestStore(t, server, Config{}) server.Close() _, err := store.LoadSessionLimit(context.Background()) require.Error(t, err) assert.ErrorContains(t, err, "load session limit from redis") } func TestStoreLoadSessionLimitNilContext(t *testing.T) { t.Parallel() server := miniredis.RunT(t) store := newTestStore(t, server, Config{}) _, err := store.LoadSessionLimit(nil) require.Error(t, err) assert.ErrorContains(t, err, "nil context") } func newTestStore(t *testing.T, server *miniredis.Miniredis, cfg Config) *Store { t.Helper() if cfg.SessionLimitKey == "" { cfg.SessionLimitKey = "authsession:config:active-session-limit" } if cfg.OperationTimeout == 0 { cfg.OperationTimeout = 250 * time.Millisecond } store, err := New(newRedisClient(t, server), cfg) require.NoError(t, err) return store } func configWithLimit(limit int) ports.SessionLimitConfig { return ports.SessionLimitConfig{ ActiveSessionLimit: &limit, } }