package configprovider import ( "context" "strconv" "testing" "time" "galaxy/authsession/internal/adapters/contracttest" "galaxy/authsession/internal/ports" "github.com/alicebob/miniredis/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) 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) tests := []struct { name string cfg Config wantErr string }{ { name: "valid config", cfg: Config{ Addr: server.Addr(), DB: 2, SessionLimitKey: "authsession:config:active-session-limit", OperationTimeout: 250 * time.Millisecond, }, }, { name: "empty addr", cfg: Config{ SessionLimitKey: "authsession:config:active-session-limit", OperationTimeout: 250 * time.Millisecond, }, wantErr: "redis addr must not be empty", }, { name: "negative db", cfg: Config{ Addr: server.Addr(), DB: -1, SessionLimitKey: "authsession:config:active-session-limit", OperationTimeout: 250 * time.Millisecond, }, wantErr: "redis db must not be negative", }, { name: "empty session limit key", cfg: Config{ Addr: server.Addr(), OperationTimeout: 250 * time.Millisecond, }, wantErr: "session limit key must not be empty", }, { name: "non positive timeout", cfg: Config{ Addr: server.Addr(), SessionLimitKey: "authsession:config:active-session-limit", }, wantErr: "operation timeout must be positive", }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() store, err := New(tt.cfg) if tt.wantErr != "" { require.Error(t, err) assert.ErrorContains(t, err, tt.wantErr) return } require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, store.Close()) }) }) } } func TestStorePing(t *testing.T) { t.Parallel() server := miniredis.RunT(t) store := newTestStore(t, server, Config{}) require.NoError(t, store.Ping(context.Background())) } 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 { tt := tt 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 TestStorePingNilContext(t *testing.T) { t.Parallel() server := miniredis.RunT(t) store := newTestStore(t, server, Config{}) err := store.Ping(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.Addr == "" { cfg.Addr = server.Addr() } if cfg.SessionLimitKey == "" { cfg.SessionLimitKey = "authsession:config:active-session-limit" } if cfg.OperationTimeout == 0 { cfg.OperationTimeout = 250 * time.Millisecond } store, err := New(cfg) require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, store.Close()) }) return store } func configWithLimit(limit int) ports.SessionLimitConfig { return ports.SessionLimitConfig{ ActiveSessionLimit: &limit, } }