feat: use postgres

This commit is contained in:
Ilia Denisov
2026-04-26 20:34:39 +02:00
committed by GitHub
parent 48b0056b49
commit fe829285a6
365 changed files with 29223 additions and 24049 deletions
+71 -98
View File
@@ -3,16 +3,20 @@
package config
import (
"crypto/tls"
"fmt"
"net"
"os"
"strconv"
"strings"
"time"
"galaxy/postgres"
"galaxy/redisconn"
)
const (
envPrefix = "USERSERVICE"
shutdownTimeoutEnvVar = "USERSERVICE_SHUTDOWN_TIMEOUT"
logLevelEnvVar = "USERSERVICE_LOG_LEVEL"
@@ -27,13 +31,6 @@ const (
adminHTTPReadTimeoutEnvVar = "USERSERVICE_ADMIN_HTTP_READ_TIMEOUT"
adminHTTPIdleTimeoutEnvVar = "USERSERVICE_ADMIN_HTTP_IDLE_TIMEOUT"
redisAddrEnvVar = "USERSERVICE_REDIS_ADDR"
redisUsernameEnvVar = "USERSERVICE_REDIS_USERNAME"
redisPasswordEnvVar = "USERSERVICE_REDIS_PASSWORD"
redisDBEnvVar = "USERSERVICE_REDIS_DB"
redisTLSEnabledEnvVar = "USERSERVICE_REDIS_TLS_ENABLED"
redisOperationTimeoutEnvVar = "USERSERVICE_REDIS_OPERATION_TIMEOUT"
redisKeyspacePrefixEnvVar = "USERSERVICE_REDIS_KEYSPACE_PREFIX"
redisDomainEventsStreamEnvVar = "USERSERVICE_REDIS_DOMAIN_EVENTS_STREAM"
redisDomainEventsStreamMaxLenEnvVar = "USERSERVICE_REDIS_DOMAIN_EVENTS_STREAM_MAX_LEN"
redisLifecycleEventsStreamEnvVar = "USERSERVICE_REDIS_LIFECYCLE_EVENTS_STREAM"
@@ -48,26 +45,23 @@ const (
otelStdoutTracesEnabledEnvVar = "USERSERVICE_OTEL_STDOUT_TRACES_ENABLED"
otelStdoutMetricsEnabledEnvVar = "USERSERVICE_OTEL_STDOUT_METRICS_ENABLED"
defaultShutdownTimeout = 5 * time.Second
defaultLogLevel = "info"
defaultInternalHTTPAddr = ":8091"
defaultAdminHTTPAddr = ""
defaultReadHeaderTimeout = 2 * time.Second
defaultReadTimeout = 10 * time.Second
defaultIdleTimeout = time.Minute
defaultRequestTimeout = 3 * time.Second
defaultRedisDB = 0
defaultRedisOperationTimeout = 250 * time.Millisecond
defaultRedisKeyspacePrefix = "user:"
defaultShutdownTimeout = 5 * time.Second
defaultLogLevel = "info"
defaultInternalHTTPAddr = ":8091"
defaultAdminHTTPAddr = ""
defaultReadHeaderTimeout = 2 * time.Second
defaultReadTimeout = 10 * time.Second
defaultIdleTimeout = time.Minute
defaultRequestTimeout = 3 * time.Second
defaultDomainEventsStream = "user:domain_events"
defaultDomainEventsStreamMaxLen = 1024
defaultLifecycleEventsStream = "user:lifecycle_events"
defaultLifecycleEventsStreamMaxLen = 1024
defaultOTelServiceName = "galaxy-user"
otelExporterNone = "none"
otelExporterOTLP = "otlp"
otelProtocolHTTPProtobuf = "http/protobuf"
otelProtocolGRPC = "grpc"
defaultOTelServiceName = "galaxy-user"
otelExporterNone = "none"
otelExporterOTLP = "otlp"
otelProtocolHTTPProtobuf = "http/protobuf"
otelProtocolGRPC = "grpc"
)
// Config stores the full user-service process configuration.
@@ -85,9 +79,14 @@ type Config struct {
// AdminHTTP configures the optional private admin HTTP listener.
AdminHTTP AdminHTTPConfig
// Redis configures the Redis-backed user store and domain-event publisher.
// Redis configures the Redis-backed event publishers (domain + lifecycle
// streams) plus the connection topology consumed via `pkg/redisconn`.
Redis RedisConfig
// Postgres configures the PostgreSQL-backed durable store consumed via
// `pkg/postgres`.
Postgres PostgresConfig
// Telemetry configures the process-wide OpenTelemetry runtime.
Telemetry TelemetryConfig
}
@@ -171,28 +170,12 @@ func (cfg AdminHTTPConfig) Validate() error {
}
}
// RedisConfig configures the Redis-backed store and domain-event publisher.
// RedisConfig configures the Redis-backed event publishers and the connection
// topology shared with `pkg/redisconn`.
type RedisConfig struct {
// Addr stores the Redis network address.
Addr string
// Username stores the optional Redis ACL username.
Username string
// Password stores the optional Redis ACL password.
Password string
// DB stores the Redis logical database index.
DB int
// TLSEnabled reports whether TLS must be used for Redis connections.
TLSEnabled bool
// OperationTimeout bounds one Redis round trip.
OperationTimeout time.Duration
// KeyspacePrefix stores the root prefix of the service-owned Redis keyspace.
KeyspacePrefix string
// Conn carries the connection topology (master, replicas, password, db,
// per-call timeout). Loaded via redisconn.LoadFromEnv("USERSERVICE").
Conn redisconn.Config
// DomainEventsStream stores the Redis Stream key used for auxiliary
// post-commit domain events.
@@ -203,8 +186,8 @@ type RedisConfig struct {
DomainEventsStreamMaxLen int64
// LifecycleEventsStream stores the Redis Stream key used for trusted
// user-lifecycle events (permanent_block, delete) consumed by
// `Game Lobby` for Race Name Directory cascade release.
// user-lifecycle events (permanent_block, delete) consumed by `Game
// Lobby` for Race Name Directory cascade release.
LifecycleEventsStream string
// LifecycleEventsStreamMaxLen bounds the lifecycle-events Redis Stream
@@ -212,27 +195,12 @@ type RedisConfig struct {
LifecycleEventsStreamMaxLen int64
}
// TLSConfig returns the conservative TLS configuration used by Redis adapters
// when TLSEnabled is true.
func (cfg RedisConfig) TLSConfig() *tls.Config {
if !cfg.TLSEnabled {
return nil
}
return &tls.Config{MinVersion: tls.VersionTLS12}
}
// Validate reports whether cfg stores a usable Redis configuration.
func (cfg RedisConfig) Validate() error {
if err := cfg.Conn.Validate(); err != nil {
return err
}
switch {
case strings.TrimSpace(cfg.Addr) == "":
return fmt.Errorf("redis addr must not be empty")
case cfg.DB < 0:
return fmt.Errorf("redis db must not be negative")
case cfg.OperationTimeout <= 0:
return fmt.Errorf("redis operation timeout must be positive")
case strings.TrimSpace(cfg.KeyspacePrefix) == "":
return fmt.Errorf("redis keyspace prefix must not be empty")
case strings.TrimSpace(cfg.DomainEventsStream) == "":
return fmt.Errorf("redis domain events stream must not be empty")
case cfg.DomainEventsStreamMaxLen <= 0:
@@ -246,6 +214,20 @@ func (cfg RedisConfig) Validate() error {
}
}
// PostgresConfig configures the PostgreSQL-backed durable store. It wraps
// the shared `pkg/postgres.Config` so callers receive the same struct shape
// across services.
type PostgresConfig struct {
// Conn stores the primary plus replica DSN topology and pool tuning.
// Loaded via postgres.LoadFromEnv("USERSERVICE").
Conn postgres.Config
}
// Validate reports whether cfg stores a usable PostgreSQL configuration.
func (cfg PostgresConfig) Validate() error {
return cfg.Conn.Validate()
}
// TelemetryConfig configures the user-service OpenTelemetry runtime.
type TelemetryConfig struct {
// ServiceName overrides the default OpenTelemetry service name.
@@ -313,7 +295,9 @@ func DefaultAdminHTTPConfig() AdminHTTPConfig {
}
// DefaultConfig returns the default process configuration with all optional
// values filled.
// values filled. Required connection coordinates (Redis master/password,
// Postgres primary DSN) remain zero-valued and must be supplied via
// LoadFromEnv.
func DefaultConfig() Config {
return Config{
ShutdownTimeout: defaultShutdownTimeout,
@@ -329,14 +313,15 @@ func DefaultConfig() Config {
},
AdminHTTP: DefaultAdminHTTPConfig(),
Redis: RedisConfig{
DB: defaultRedisDB,
OperationTimeout: defaultRedisOperationTimeout,
KeyspacePrefix: defaultRedisKeyspacePrefix,
Conn: redisconn.DefaultConfig(),
DomainEventsStream: defaultDomainEventsStream,
DomainEventsStreamMaxLen: defaultDomainEventsStreamMaxLen,
LifecycleEventsStream: defaultLifecycleEventsStream,
LifecycleEventsStreamMaxLen: defaultLifecycleEventsStreamMaxLen,
},
Postgres: PostgresConfig{
Conn: postgres.DefaultConfig(),
},
Telemetry: TelemetryConfig{
ServiceName: defaultOTelServiceName,
TracesExporter: otelExporterNone,
@@ -360,6 +345,9 @@ func (cfg Config) Validate() error {
if err := cfg.Redis.Validate(); err != nil {
return fmt.Errorf("redis config: %w", err)
}
if err := cfg.Postgres.Validate(); err != nil {
return fmt.Errorf("postgres config: %w", err)
}
if _, err := parseLogLevel(cfg.Logging.Level); err != nil {
return fmt.Errorf("logging config: %w", err)
}
@@ -370,7 +358,11 @@ func (cfg Config) Validate() error {
return nil
}
// LoadFromEnv loads Config from the process environment.
// LoadFromEnv loads Config from the process environment. Connection topology
// for Redis and PostgreSQL is delegated to the shared `pkg/redisconn` and
// `pkg/postgres` LoadFromEnv helpers, which enforce the architectural rules
// (mandatory Redis password, deprecated TLS/USERNAME variables hard-fail,
// required Postgres primary DSN).
func LoadFromEnv() (Config, error) {
cfg := DefaultConfig()
@@ -413,22 +405,11 @@ func LoadFromEnv() (Config, error) {
return Config{}, err
}
cfg.Redis.Addr = loadString(redisAddrEnvVar, cfg.Redis.Addr)
cfg.Redis.Username = loadString(redisUsernameEnvVar, cfg.Redis.Username)
cfg.Redis.Password = loadString(redisPasswordEnvVar, cfg.Redis.Password)
cfg.Redis.DB, err = loadInt(redisDBEnvVar, cfg.Redis.DB)
redisConn, err := redisconn.LoadFromEnv(envPrefix)
if err != nil {
return Config{}, err
}
cfg.Redis.TLSEnabled, err = loadBool(redisTLSEnabledEnvVar, cfg.Redis.TLSEnabled)
if err != nil {
return Config{}, err
}
cfg.Redis.OperationTimeout, err = loadDuration(redisOperationTimeoutEnvVar, cfg.Redis.OperationTimeout)
if err != nil {
return Config{}, err
}
cfg.Redis.KeyspacePrefix = loadString(redisKeyspacePrefixEnvVar, cfg.Redis.KeyspacePrefix)
cfg.Redis.Conn = redisConn
cfg.Redis.DomainEventsStream = loadString(redisDomainEventsStreamEnvVar, cfg.Redis.DomainEventsStream)
cfg.Redis.DomainEventsStreamMaxLen, err = loadInt64(redisDomainEventsStreamMaxLenEnvVar, cfg.Redis.DomainEventsStreamMaxLen)
if err != nil {
@@ -440,6 +421,12 @@ func LoadFromEnv() (Config, error) {
return Config{}, err
}
pgConn, err := postgres.LoadFromEnv(envPrefix)
if err != nil {
return Config{}, err
}
cfg.Postgres.Conn = pgConn
cfg.Telemetry.ServiceName = loadString(otelServiceNameEnvVar, cfg.Telemetry.ServiceName)
cfg.Telemetry.TracesExporter = normalizeExporterValue(loadString(otelTracesExporterEnvVar, cfg.Telemetry.TracesExporter))
cfg.Telemetry.MetricsExporter = normalizeExporterValue(loadString(otelMetricsExporterEnvVar, cfg.Telemetry.MetricsExporter))
@@ -492,20 +479,6 @@ func loadDuration(envName string, defaultValue time.Duration) (time.Duration, er
return duration, nil
}
func loadInt(envName string, defaultValue int) (int, error) {
value, ok := os.LookupEnv(envName)
if !ok {
return defaultValue, nil
}
parsedValue, err := strconv.Atoi(strings.TrimSpace(value))
if err != nil {
return 0, fmt.Errorf("%s: parse int: %w", envName, err)
}
return parsedValue, nil
}
func loadInt64(envName string, defaultValue int64) (int64, error) {
value, ok := os.LookupEnv(envName)
if !ok {
+130 -23
View File
@@ -1,14 +1,37 @@
package config
import (
"strings"
"testing"
"time"
"github.com/stretchr/testify/require"
)
const (
redisMasterAddrEnvVar = "USERSERVICE_REDIS_MASTER_ADDR"
redisReplicaAddrsEnvVar = "USERSERVICE_REDIS_REPLICA_ADDRS"
redisPasswordEnvVar = "USERSERVICE_REDIS_PASSWORD"
redisDBEnvVar = "USERSERVICE_REDIS_DB"
redisOperationTimeoutEnvVar = "USERSERVICE_REDIS_OPERATION_TIMEOUT"
redisLegacyAddrEnvVar = "USERSERVICE_REDIS_ADDR"
redisLegacyUsernameEnvVar = "USERSERVICE_REDIS_USERNAME"
redisLegacyTLSEnabledEnvVar = "USERSERVICE_REDIS_TLS_ENABLED"
redisLegacyKeyspacePrefixEnv = "USERSERVICE_REDIS_KEYSPACE_PREFIX"
postgresPrimaryDSNEnvVar = "USERSERVICE_POSTGRES_PRIMARY_DSN"
postgresReplicaDSNsEnvVar = "USERSERVICE_POSTGRES_REPLICA_DSNS"
postgresOperationTimeoutEnvVar = "USERSERVICE_POSTGRES_OPERATION_TIMEOUT"
postgresMaxOpenConnsEnvVar = "USERSERVICE_POSTGRES_MAX_OPEN_CONNS"
postgresMaxIdleConnsEnvVar = "USERSERVICE_POSTGRES_MAX_IDLE_CONNS"
postgresConnMaxLifetimeEnvVar = "USERSERVICE_POSTGRES_CONN_MAX_LIFETIME"
defaultPostgresDSN = "postgres://userservice:secret@127.0.0.1:5432/galaxy?search_path=user&sslmode=disable"
)
func TestLoadFromEnvUsesDefaults(t *testing.T) {
t.Setenv(redisAddrEnvVar, "127.0.0.1:6379")
t.Setenv(redisMasterAddrEnvVar, "127.0.0.1:6379")
t.Setenv(redisPasswordEnvVar, "secret")
t.Setenv(postgresPrimaryDSNEnvVar, defaultPostgresDSN)
cfg, err := LoadFromEnv()
require.NoError(t, err)
@@ -18,10 +41,18 @@ func TestLoadFromEnvUsesDefaults(t *testing.T) {
require.Equal(t, defaults.Logging.Level, cfg.Logging.Level)
require.Equal(t, defaults.InternalHTTP, cfg.InternalHTTP)
require.Equal(t, defaults.AdminHTTP, cfg.AdminHTTP)
require.Equal(t, "127.0.0.1:6379", cfg.Redis.Addr)
require.Equal(t, defaults.Redis.DB, cfg.Redis.DB)
require.Equal(t, "127.0.0.1:6379", cfg.Redis.Conn.MasterAddr)
require.Equal(t, "secret", cfg.Redis.Conn.Password)
require.Equal(t, defaults.Redis.Conn.DB, cfg.Redis.Conn.DB)
require.Equal(t, defaults.Redis.DomainEventsStream, cfg.Redis.DomainEventsStream)
require.Equal(t, defaults.Redis.DomainEventsStreamMaxLen, cfg.Redis.DomainEventsStreamMaxLen)
require.Equal(t, defaults.Redis.LifecycleEventsStream, cfg.Redis.LifecycleEventsStream)
require.Equal(t, defaults.Redis.LifecycleEventsStreamMaxLen, cfg.Redis.LifecycleEventsStreamMaxLen)
require.Equal(t, defaultPostgresDSN, cfg.Postgres.Conn.PrimaryDSN)
require.Equal(t, defaults.Postgres.Conn.OperationTimeout, cfg.Postgres.Conn.OperationTimeout)
require.Equal(t, defaults.Postgres.Conn.MaxOpenConns, cfg.Postgres.Conn.MaxOpenConns)
require.Equal(t, defaults.Postgres.Conn.MaxIdleConns, cfg.Postgres.Conn.MaxIdleConns)
require.Equal(t, defaults.Postgres.Conn.ConnMaxLifetime, cfg.Postgres.Conn.ConnMaxLifetime)
require.Equal(t, defaults.Telemetry, cfg.Telemetry)
}
@@ -33,15 +64,21 @@ func TestLoadFromEnvAppliesOverrides(t *testing.T) {
t.Setenv(internalHTTPRequestTimeoutEnvVar, "750ms")
t.Setenv(adminHTTPAddrEnvVar, "127.0.0.1:19091")
t.Setenv(adminHTTPIdleTimeoutEnvVar, "90s")
t.Setenv(redisAddrEnvVar, "127.0.0.1:6380")
t.Setenv(redisUsernameEnvVar, "alice")
t.Setenv(redisPasswordEnvVar, "secret")
t.Setenv(redisMasterAddrEnvVar, "127.0.0.1:6380")
t.Setenv(redisReplicaAddrsEnvVar, "127.0.0.1:6381,127.0.0.1:6382")
t.Setenv(redisPasswordEnvVar, "redis-secret")
t.Setenv(redisDBEnvVar, "3")
t.Setenv(redisTLSEnabledEnvVar, "true")
t.Setenv(redisOperationTimeoutEnvVar, "900ms")
t.Setenv(redisKeyspacePrefixEnvVar, "user:custom:")
t.Setenv(redisDomainEventsStreamEnvVar, "user:test_events")
t.Setenv(redisDomainEventsStreamMaxLenEnvVar, "2048")
t.Setenv(redisLifecycleEventsStreamEnvVar, "user:test_lifecycle")
t.Setenv(redisLifecycleEventsStreamMaxLenEnvVar, "512")
t.Setenv(postgresPrimaryDSNEnvVar, defaultPostgresDSN)
t.Setenv(postgresReplicaDSNsEnvVar, "postgres://userservice:secret@replica-a/galaxy?sslmode=disable,postgres://userservice:secret@replica-b/galaxy?sslmode=disable")
t.Setenv(postgresOperationTimeoutEnvVar, "2s")
t.Setenv(postgresMaxOpenConnsEnvVar, "40")
t.Setenv(postgresMaxIdleConnsEnvVar, "8")
t.Setenv(postgresConnMaxLifetimeEnvVar, "45m")
t.Setenv(otelServiceNameEnvVar, "galaxy-user-stage12")
t.Setenv(otelTracesExporterEnvVar, "otlp")
t.Setenv(otelMetricsExporterEnvVar, "otlp")
@@ -60,15 +97,24 @@ func TestLoadFromEnvAppliesOverrides(t *testing.T) {
require.Equal(t, 750*time.Millisecond, cfg.InternalHTTP.RequestTimeout)
require.Equal(t, "127.0.0.1:19091", cfg.AdminHTTP.Addr)
require.Equal(t, 90*time.Second, cfg.AdminHTTP.IdleTimeout)
require.Equal(t, "127.0.0.1:6380", cfg.Redis.Addr)
require.Equal(t, "alice", cfg.Redis.Username)
require.Equal(t, "secret", cfg.Redis.Password)
require.Equal(t, 3, cfg.Redis.DB)
require.True(t, cfg.Redis.TLSEnabled)
require.Equal(t, 900*time.Millisecond, cfg.Redis.OperationTimeout)
require.Equal(t, "user:custom:", cfg.Redis.KeyspacePrefix)
require.Equal(t, "127.0.0.1:6380", cfg.Redis.Conn.MasterAddr)
require.Equal(t, []string{"127.0.0.1:6381", "127.0.0.1:6382"}, cfg.Redis.Conn.ReplicaAddrs)
require.Equal(t, "redis-secret", cfg.Redis.Conn.Password)
require.Equal(t, 3, cfg.Redis.Conn.DB)
require.Equal(t, 900*time.Millisecond, cfg.Redis.Conn.OperationTimeout)
require.Equal(t, "user:test_events", cfg.Redis.DomainEventsStream)
require.Equal(t, int64(2048), cfg.Redis.DomainEventsStreamMaxLen)
require.Equal(t, "user:test_lifecycle", cfg.Redis.LifecycleEventsStream)
require.Equal(t, int64(512), cfg.Redis.LifecycleEventsStreamMaxLen)
require.Equal(t, defaultPostgresDSN, cfg.Postgres.Conn.PrimaryDSN)
require.Equal(t, []string{
"postgres://userservice:secret@replica-a/galaxy?sslmode=disable",
"postgres://userservice:secret@replica-b/galaxy?sslmode=disable",
}, cfg.Postgres.Conn.ReplicaDSNs)
require.Equal(t, 2*time.Second, cfg.Postgres.Conn.OperationTimeout)
require.Equal(t, 40, cfg.Postgres.Conn.MaxOpenConns)
require.Equal(t, 8, cfg.Postgres.Conn.MaxIdleConns)
require.Equal(t, 45*time.Minute, cfg.Postgres.Conn.ConnMaxLifetime)
require.Equal(t, "galaxy-user-stage12", cfg.Telemetry.ServiceName)
require.Equal(t, "otlp", cfg.Telemetry.TracesExporter)
require.Equal(t, "otlp", cfg.Telemetry.MetricsExporter)
@@ -78,29 +124,90 @@ func TestLoadFromEnvAppliesOverrides(t *testing.T) {
require.True(t, cfg.Telemetry.StdoutMetricsEnabled)
}
// TestLoadFromEnvRejectsLegacyRedisVars verifies the architectural rule from
// PG_PLAN.md §3 / ARCHITECTURE.md §Persistence Backends: legacy
// USERSERVICE_REDIS_TLS_ENABLED and USERSERVICE_REDIS_USERNAME variables must
// produce a startup error from `pkg/redisconn` so operators see the breaking
// rename immediately.
func TestLoadFromEnvRejectsLegacyRedisVars(t *testing.T) {
cases := []struct {
name string
envName string
}{
{name: "tls_enabled deprecated", envName: redisLegacyTLSEnabledEnvVar},
{name: "username deprecated", envName: redisLegacyUsernameEnvVar},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Setenv(redisMasterAddrEnvVar, "127.0.0.1:6379")
t.Setenv(redisPasswordEnvVar, "secret")
t.Setenv(postgresPrimaryDSNEnvVar, defaultPostgresDSN)
t.Setenv(tc.envName, "true")
_, err := LoadFromEnv()
require.Error(t, err)
require.True(t, strings.Contains(err.Error(), "no longer supported"))
})
}
}
// TestLoadFromEnvRequiresMandatoryFields covers the architectural rule that
// Redis password, master address and Postgres primary DSN are mandatory;
// missing any one returns a startup error.
func TestLoadFromEnvRequiresMandatoryFields(t *testing.T) {
t.Run("missing redis password", func(t *testing.T) {
t.Setenv(redisMasterAddrEnvVar, "127.0.0.1:6379")
t.Setenv(postgresPrimaryDSNEnvVar, defaultPostgresDSN)
_, err := LoadFromEnv()
require.Error(t, err)
})
t.Run("missing redis master addr", func(t *testing.T) {
t.Setenv(redisPasswordEnvVar, "secret")
t.Setenv(postgresPrimaryDSNEnvVar, defaultPostgresDSN)
_, err := LoadFromEnv()
require.Error(t, err)
})
t.Run("missing postgres dsn", func(t *testing.T) {
t.Setenv(redisMasterAddrEnvVar, "127.0.0.1:6379")
t.Setenv(redisPasswordEnvVar, "secret")
_, err := LoadFromEnv()
require.Error(t, err)
})
}
func TestLoadFromEnvRejectsInvalidValues(t *testing.T) {
tests := []struct {
cases := []struct {
name string
envName string
envVal string
}{
{name: "invalid duration", envName: shutdownTimeoutEnvVar, envVal: "later"},
{name: "invalid bool", envName: redisTLSEnabledEnvVar, envVal: "sometimes"},
{name: "invalid log level", envName: logLevelEnvVar, envVal: "verbose"},
{name: "invalid int", envName: redisDBEnvVar, envVal: "db-three"},
{name: "invalid redis db", envName: redisDBEnvVar, envVal: "db-three"},
{name: "invalid stream max len", envName: redisDomainEventsStreamMaxLenEnvVar, envVal: "many"},
{name: "invalid traces exporter", envName: otelTracesExporterEnvVar, envVal: "zipkin"},
{name: "invalid metrics protocol", envName: otelExporterOTLPMetricsProtocolEnvVar, envVal: "udp"},
{name: "invalid postgres operation timeout", envName: postgresOperationTimeoutEnvVar, envVal: "soon"},
{name: "invalid postgres max open conns", envName: postgresMaxOpenConnsEnvVar, envVal: "none"},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Setenv(redisAddrEnvVar, "127.0.0.1:6379")
t.Setenv(tt.envName, tt.envVal)
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Setenv(redisMasterAddrEnvVar, "127.0.0.1:6379")
t.Setenv(redisPasswordEnvVar, "secret")
t.Setenv(postgresPrimaryDSNEnvVar, defaultPostgresDSN)
t.Setenv(tc.envName, tc.envVal)
_, err := LoadFromEnv()
require.Error(t, err)
})
}
}
// Suppress unused-warning for legacy keyspace prefix env reference: keep the
// constant in test scope for documentation, though no current code uses it.
var _ = redisLegacyAddrEnvVar
var _ = redisLegacyKeyspacePrefixEnv