feat: use postgres
This commit is contained in:
@@ -4,12 +4,42 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"galaxy/postgres"
|
||||
"galaxy/redisconn"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLoadFromEnvUsesDefaults(t *testing.T) {
|
||||
t.Setenv(redisAddrEnvVar, "127.0.0.1:6379")
|
||||
const (
|
||||
envRedisMasterAddr = "NOTIFICATION_REDIS_MASTER_ADDR"
|
||||
envRedisReplicaAddrs = "NOTIFICATION_REDIS_REPLICA_ADDRS"
|
||||
envRedisPassword = "NOTIFICATION_REDIS_PASSWORD"
|
||||
envRedisDB = "NOTIFICATION_REDIS_DB"
|
||||
envRedisOpTimeout = "NOTIFICATION_REDIS_OPERATION_TIMEOUT"
|
||||
envRedisTLSEnabled = "NOTIFICATION_REDIS_TLS_ENABLED"
|
||||
envRedisUsername = "NOTIFICATION_REDIS_USERNAME"
|
||||
|
||||
envPostgresPrimaryDSN = "NOTIFICATION_POSTGRES_PRIMARY_DSN"
|
||||
envPostgresOpTimeout = "NOTIFICATION_POSTGRES_OPERATION_TIMEOUT"
|
||||
envPostgresMaxOpenConns = "NOTIFICATION_POSTGRES_MAX_OPEN_CONNS"
|
||||
envPostgresMaxIdleConns = "NOTIFICATION_POSTGRES_MAX_IDLE_CONNS"
|
||||
envPostgresConnMaxLife = "NOTIFICATION_POSTGRES_CONN_MAX_LIFETIME"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultPrimaryDSN = "postgres://notificationservice:notificationservice@127.0.0.1:5432/galaxy?search_path=notification&sslmode=disable"
|
||||
)
|
||||
|
||||
func setRequiredConnEnv(t *testing.T) {
|
||||
t.Helper()
|
||||
t.Setenv(envRedisMasterAddr, "127.0.0.1:6379")
|
||||
t.Setenv(envRedisPassword, "secret")
|
||||
t.Setenv(envPostgresPrimaryDSN, defaultPrimaryDSN)
|
||||
t.Setenv(userServiceBaseURLEnvVar, "http://user-service.internal")
|
||||
}
|
||||
|
||||
func TestLoadFromEnvUsesDefaults(t *testing.T) {
|
||||
setRequiredConnEnv(t)
|
||||
|
||||
cfg, err := LoadFromEnv()
|
||||
require.NoError(t, err)
|
||||
@@ -18,11 +48,14 @@ func TestLoadFromEnvUsesDefaults(t *testing.T) {
|
||||
require.Equal(t, defaults.ShutdownTimeout, cfg.ShutdownTimeout)
|
||||
require.Equal(t, defaults.Logging, cfg.Logging)
|
||||
require.Equal(t, defaults.InternalHTTP, cfg.InternalHTTP)
|
||||
require.Equal(t, "127.0.0.1:6379", cfg.Redis.Addr)
|
||||
require.Equal(t, defaults.Redis.DB, cfg.Redis.DB)
|
||||
require.Equal(t, defaults.Redis.OperationTimeout, cfg.Redis.OperationTimeout)
|
||||
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.Conn.OperationTimeout, cfg.Redis.Conn.OperationTimeout)
|
||||
require.Equal(t, defaultPrimaryDSN, cfg.Postgres.Conn.PrimaryDSN)
|
||||
require.Equal(t, defaults.Streams, cfg.Streams)
|
||||
require.Equal(t, defaults.Retry, cfg.Retry)
|
||||
require.Equal(t, defaults.Retention, cfg.Retention)
|
||||
require.Equal(t, UserServiceConfig{
|
||||
BaseURL: "http://user-service.internal",
|
||||
Timeout: defaults.UserService.Timeout,
|
||||
@@ -38,12 +71,19 @@ func TestLoadFromEnvAppliesOverrides(t *testing.T) {
|
||||
t.Setenv(internalHTTPReadHeaderTimeoutEnvVar, "3s")
|
||||
t.Setenv(internalHTTPReadTimeoutEnvVar, "11s")
|
||||
t.Setenv(internalHTTPIdleTimeoutEnvVar, "61s")
|
||||
t.Setenv(redisAddrEnvVar, "127.0.0.1:6380")
|
||||
t.Setenv(redisUsernameEnvVar, "alice")
|
||||
t.Setenv(redisPasswordEnvVar, "secret")
|
||||
t.Setenv(redisDBEnvVar, "3")
|
||||
t.Setenv(redisTLSEnabledEnvVar, "true")
|
||||
t.Setenv(redisOperationTimeoutEnvVar, "750ms")
|
||||
|
||||
t.Setenv(envRedisMasterAddr, "127.0.0.1:6380")
|
||||
t.Setenv(envRedisReplicaAddrs, "127.0.0.1:6381,127.0.0.1:6382")
|
||||
t.Setenv(envRedisPassword, "topsecret")
|
||||
t.Setenv(envRedisDB, "3")
|
||||
t.Setenv(envRedisOpTimeout, "750ms")
|
||||
|
||||
t.Setenv(envPostgresPrimaryDSN, defaultPrimaryDSN)
|
||||
t.Setenv(envPostgresOpTimeout, "1500ms")
|
||||
t.Setenv(envPostgresMaxOpenConns, "32")
|
||||
t.Setenv(envPostgresMaxIdleConns, "8")
|
||||
t.Setenv(envPostgresConnMaxLife, "45m")
|
||||
|
||||
t.Setenv(intentsStreamEnvVar, "notification:test_intents")
|
||||
t.Setenv(intentsReadBlockTimeoutEnvVar, "3500ms")
|
||||
t.Setenv(gatewayClientEventsStreamEnvVar, "gateway:test_client-events")
|
||||
@@ -54,9 +94,10 @@ func TestLoadFromEnvAppliesOverrides(t *testing.T) {
|
||||
t.Setenv(routeLeaseTTLEnvVar, "7s")
|
||||
t.Setenv(routeBackoffMinEnvVar, "2s")
|
||||
t.Setenv(routeBackoffMaxEnvVar, "7m")
|
||||
t.Setenv(deadLetterTTLEnvVar, "120h")
|
||||
t.Setenv(recordTTLEnvVar, "240h")
|
||||
t.Setenv(idempotencyTTLEnvVar, "48h")
|
||||
t.Setenv(recordRetentionEnvVar, "21d")
|
||||
t.Setenv(malformedIntentRetentionEnvVar, "168h")
|
||||
t.Setenv(cleanupIntervalEnvVar, "30m")
|
||||
t.Setenv(userServiceBaseURLEnvVar, "https://user-service.internal/api/")
|
||||
t.Setenv(userServiceTimeoutEnvVar, "1500ms")
|
||||
t.Setenv(adminEmailsGeoReviewRecommendedEnvVar, "First@example.com, second@example.com, first@example.com")
|
||||
@@ -70,6 +111,9 @@ func TestLoadFromEnvAppliesOverrides(t *testing.T) {
|
||||
t.Setenv(otelStdoutTracesEnabledEnvVar, "true")
|
||||
t.Setenv(otelStdoutMetricsEnabledEnvVar, "true")
|
||||
|
||||
// Time package does not support `21d`; use 504h directly.
|
||||
t.Setenv(recordRetentionEnvVar, "504h")
|
||||
|
||||
cfg, err := LoadFromEnv()
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -82,18 +126,28 @@ func TestLoadFromEnvAppliesOverrides(t *testing.T) {
|
||||
IdleTimeout: 61 * time.Second,
|
||||
}, cfg.InternalHTTP)
|
||||
require.Equal(t, RedisConfig{
|
||||
Addr: "127.0.0.1:6380",
|
||||
Username: "alice",
|
||||
Password: "secret",
|
||||
DB: 3,
|
||||
TLSEnabled: true,
|
||||
OperationTimeout: 750 * time.Millisecond,
|
||||
Conn: redisconn.Config{
|
||||
MasterAddr: "127.0.0.1:6380",
|
||||
ReplicaAddrs: []string{"127.0.0.1:6381", "127.0.0.1:6382"},
|
||||
Password: "topsecret",
|
||||
DB: 3,
|
||||
OperationTimeout: 750 * time.Millisecond,
|
||||
},
|
||||
}, cfg.Redis)
|
||||
require.Equal(t, PostgresConfig{
|
||||
Conn: postgres.Config{
|
||||
PrimaryDSN: defaultPrimaryDSN,
|
||||
OperationTimeout: 1500 * time.Millisecond,
|
||||
MaxOpenConns: 32,
|
||||
MaxIdleConns: 8,
|
||||
ConnMaxLifetime: 45 * time.Minute,
|
||||
},
|
||||
}, cfg.Postgres)
|
||||
require.Equal(t, StreamsConfig{
|
||||
Intents: "notification:test_intents",
|
||||
GatewayClientEvents: "gateway:test_client-events",
|
||||
Intents: "notification:test_intents",
|
||||
GatewayClientEvents: "gateway:test_client-events",
|
||||
GatewayClientEventsStreamMaxLen: 2048,
|
||||
MailDeliveryCommands: "mail:test_delivery_commands",
|
||||
MailDeliveryCommands: "mail:test_delivery_commands",
|
||||
}, cfg.Streams)
|
||||
require.Equal(t, 3500*time.Millisecond, cfg.IntentsReadBlockTimeout)
|
||||
require.Equal(t, RetryConfig{
|
||||
@@ -102,10 +156,13 @@ func TestLoadFromEnvAppliesOverrides(t *testing.T) {
|
||||
RouteLeaseTTL: 7 * time.Second,
|
||||
RouteBackoffMin: 2 * time.Second,
|
||||
RouteBackoffMax: 7 * time.Minute,
|
||||
DeadLetterTTL: 120 * time.Hour,
|
||||
RecordTTL: 240 * time.Hour,
|
||||
IdempotencyTTL: 48 * time.Hour,
|
||||
}, cfg.Retry)
|
||||
require.Equal(t, RetentionConfig{
|
||||
RecordRetention: 504 * time.Hour,
|
||||
MalformedIntentRetention: 168 * time.Hour,
|
||||
CleanupInterval: 30 * time.Minute,
|
||||
}, cfg.Retention)
|
||||
require.Equal(t, UserServiceConfig{
|
||||
BaseURL: "https://user-service.internal/api",
|
||||
Timeout: 1500 * time.Millisecond,
|
||||
@@ -127,6 +184,27 @@ func TestLoadFromEnvAppliesOverrides(t *testing.T) {
|
||||
}, cfg.Telemetry)
|
||||
}
|
||||
|
||||
func TestLoadFromEnvRejectsDeprecatedRedisVars(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
envName string
|
||||
}{
|
||||
{name: "tls enabled rejected", envName: envRedisTLSEnabled},
|
||||
{name: "username rejected", envName: envRedisUsername},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
setRequiredConnEnv(t)
|
||||
t.Setenv(tt.envName, "true")
|
||||
|
||||
_, err := LoadFromEnv()
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tt.envName)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadFromEnvRejectsInvalidValues(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -135,14 +213,16 @@ func TestLoadFromEnvRejectsInvalidValues(t *testing.T) {
|
||||
}{
|
||||
{name: "invalid duration", envName: shutdownTimeoutEnvVar, envVal: "later"},
|
||||
{name: "invalid log level", envName: logLevelEnvVar, envVal: "verbose"},
|
||||
{name: "invalid redis db", envName: redisDBEnvVar, envVal: "db-three"},
|
||||
{name: "invalid redis tls", envName: redisTLSEnabledEnvVar, envVal: "sometimes"},
|
||||
{name: "invalid redis db", envName: envRedisDB, envVal: "db-three"},
|
||||
{name: "invalid push retries", envName: pushRetryMaxAttemptsEnvVar, envVal: "many"},
|
||||
{name: "invalid email retries", envName: emailRetryMaxAttemptsEnvVar, envVal: "several"},
|
||||
{name: "invalid gateway client events stream max len", envName: gatewayClientEventsStreamMaxEnvVar, envVal: "many"},
|
||||
{name: "invalid user service timeout", envName: userServiceTimeoutEnvVar, envVal: "soon"},
|
||||
{name: "invalid intents read block timeout", envName: intentsReadBlockTimeoutEnvVar, envVal: "later"},
|
||||
{name: "invalid route lease ttl", envName: routeLeaseTTLEnvVar, envVal: "eventually"},
|
||||
{name: "invalid record retention", envName: recordRetentionEnvVar, envVal: "later"},
|
||||
{name: "invalid malformed intent retention", envName: malformedIntentRetentionEnvVar, envVal: "later"},
|
||||
{name: "invalid cleanup interval", envName: cleanupIntervalEnvVar, envVal: "later"},
|
||||
{name: "invalid traces exporter", envName: otelTracesExporterEnvVar, envVal: "stdout"},
|
||||
{name: "invalid metrics protocol", envName: otelExporterOTLPMetricsProtocolEnvVar, envVal: "udp"},
|
||||
{name: "invalid stdout traces", envName: otelStdoutTracesEnabledEnvVar, envVal: "sometimes"},
|
||||
@@ -152,8 +232,7 @@ func TestLoadFromEnvRejectsInvalidValues(t *testing.T) {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Setenv(redisAddrEnvVar, "127.0.0.1:6379")
|
||||
t.Setenv(userServiceBaseURLEnvVar, "http://user-service.internal")
|
||||
setRequiredConnEnv(t)
|
||||
t.Setenv(tt.envName, tt.envVal)
|
||||
|
||||
_, err := LoadFromEnv()
|
||||
@@ -163,20 +242,44 @@ func TestLoadFromEnvRejectsInvalidValues(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoadFromEnvRejectsMissingRequiredValues(t *testing.T) {
|
||||
t.Run("missing redis addr", func(t *testing.T) {
|
||||
t.Run("missing redis master addr", func(t *testing.T) {
|
||||
t.Setenv(envRedisPassword, "secret")
|
||||
t.Setenv(envPostgresPrimaryDSN, defaultPrimaryDSN)
|
||||
t.Setenv(userServiceBaseURLEnvVar, "http://user-service.internal")
|
||||
|
||||
_, err := LoadFromEnv()
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), redisAddrEnvVar)
|
||||
require.Contains(t, err.Error(), envRedisMasterAddr)
|
||||
})
|
||||
|
||||
t.Run("missing user service base url", func(t *testing.T) {
|
||||
t.Setenv(redisAddrEnvVar, "127.0.0.1:6379")
|
||||
t.Run("missing redis password", func(t *testing.T) {
|
||||
t.Setenv(envRedisMasterAddr, "127.0.0.1:6379")
|
||||
t.Setenv(envPostgresPrimaryDSN, defaultPrimaryDSN)
|
||||
t.Setenv(userServiceBaseURLEnvVar, "http://user-service.internal")
|
||||
|
||||
_, err := LoadFromEnv()
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), userServiceBaseURLEnvVar)
|
||||
require.Contains(t, err.Error(), envRedisPassword)
|
||||
})
|
||||
|
||||
t.Run("missing postgres primary dsn", func(t *testing.T) {
|
||||
t.Setenv(envRedisMasterAddr, "127.0.0.1:6379")
|
||||
t.Setenv(envRedisPassword, "secret")
|
||||
t.Setenv(userServiceBaseURLEnvVar, "http://user-service.internal")
|
||||
|
||||
_, err := LoadFromEnv()
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), envPostgresPrimaryDSN)
|
||||
})
|
||||
|
||||
t.Run("missing user service base url", func(t *testing.T) {
|
||||
t.Setenv(envRedisMasterAddr, "127.0.0.1:6379")
|
||||
t.Setenv(envRedisPassword, "secret")
|
||||
t.Setenv(envPostgresPrimaryDSN, defaultPrimaryDSN)
|
||||
|
||||
_, err := LoadFromEnv()
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "user service base URL")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -188,7 +291,6 @@ func TestLoadFromEnvRejectsInvalidConfiguration(t *testing.T) {
|
||||
want string
|
||||
}{
|
||||
{name: "invalid internal http addr", envName: internalHTTPAddrEnvVar, envVal: "127.0.0.1", want: "internal HTTP addr"},
|
||||
{name: "invalid redis addr", envName: redisAddrEnvVar, envVal: "127.0.0.1", want: "redis addr"},
|
||||
{name: "relative user service url", envName: userServiceBaseURLEnvVar, envVal: "/internal/users", want: "absolute http(s) URL"},
|
||||
{name: "invalid admin email", envName: adminEmailsGeoReviewRecommendedEnvVar, envVal: "broken-email", want: "invalid email address"},
|
||||
{name: "blank admin email slot", envName: adminEmailsGameGenerationFailedEnvVar, envVal: "ops@example.com, , second@example.com", want: "must not be empty"},
|
||||
@@ -201,8 +303,7 @@ func TestLoadFromEnvRejectsInvalidConfiguration(t *testing.T) {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Setenv(redisAddrEnvVar, "127.0.0.1:6379")
|
||||
t.Setenv(userServiceBaseURLEnvVar, "http://user-service.internal")
|
||||
setRequiredConnEnv(t)
|
||||
t.Setenv(routeBackoffMaxEnvVar, "5m")
|
||||
t.Setenv(tt.envName, tt.envVal)
|
||||
|
||||
@@ -223,7 +324,7 @@ func TestLoadFromEnvRejectsNonPositiveValues(t *testing.T) {
|
||||
{name: "read header timeout", envName: internalHTTPReadHeaderTimeoutEnvVar, envVal: "0s"},
|
||||
{name: "read timeout", envName: internalHTTPReadTimeoutEnvVar, envVal: "0s"},
|
||||
{name: "idle timeout", envName: internalHTTPIdleTimeoutEnvVar, envVal: "0s"},
|
||||
{name: "redis timeout", envName: redisOperationTimeoutEnvVar, envVal: "0s"},
|
||||
{name: "redis timeout", envName: envRedisOpTimeout, envVal: "0s"},
|
||||
{name: "intents read block timeout", envName: intentsReadBlockTimeoutEnvVar, envVal: "0s"},
|
||||
{name: "push retries", envName: pushRetryMaxAttemptsEnvVar, envVal: "0"},
|
||||
{name: "email retries", envName: emailRetryMaxAttemptsEnvVar, envVal: "0"},
|
||||
@@ -231,9 +332,10 @@ func TestLoadFromEnvRejectsNonPositiveValues(t *testing.T) {
|
||||
{name: "route lease ttl", envName: routeLeaseTTLEnvVar, envVal: "0s"},
|
||||
{name: "route backoff min", envName: routeBackoffMinEnvVar, envVal: "0s"},
|
||||
{name: "route backoff max", envName: routeBackoffMaxEnvVar, envVal: "0s"},
|
||||
{name: "dead letter ttl", envName: deadLetterTTLEnvVar, envVal: "0s"},
|
||||
{name: "record ttl", envName: recordTTLEnvVar, envVal: "0s"},
|
||||
{name: "idempotency ttl", envName: idempotencyTTLEnvVar, envVal: "0s"},
|
||||
{name: "record retention", envName: recordRetentionEnvVar, envVal: "0s"},
|
||||
{name: "malformed intent retention", envName: malformedIntentRetentionEnvVar, envVal: "0s"},
|
||||
{name: "cleanup interval", envName: cleanupIntervalEnvVar, envVal: "0s"},
|
||||
{name: "user service timeout", envName: userServiceTimeoutEnvVar, envVal: "0s"},
|
||||
}
|
||||
|
||||
@@ -241,8 +343,7 @@ func TestLoadFromEnvRejectsNonPositiveValues(t *testing.T) {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Setenv(redisAddrEnvVar, "127.0.0.1:6379")
|
||||
t.Setenv(userServiceBaseURLEnvVar, "http://user-service.internal")
|
||||
setRequiredConnEnv(t)
|
||||
t.Setenv(tt.envName, tt.envVal)
|
||||
|
||||
_, err := LoadFromEnv()
|
||||
|
||||
Reference in New Issue
Block a user