Files
galaxy-game/mail/internal/config/config_test.go
T
2026-04-26 20:34:39 +02:00

293 lines
11 KiB
Go

package config
import (
"testing"
"time"
"github.com/stretchr/testify/require"
)
const (
testRedisMasterAddr = "MAIL_REDIS_MASTER_ADDR"
testRedisPassword = "MAIL_REDIS_PASSWORD"
testRedisDB = "MAIL_REDIS_DB"
testRedisOpTimeout = "MAIL_REDIS_OPERATION_TIMEOUT"
testRedisLegacyTLS = "MAIL_REDIS_TLS_ENABLED"
testRedisLegacyUser = "MAIL_REDIS_USERNAME"
testPostgresDSN = "MAIL_POSTGRES_PRIMARY_DSN"
testPostgresOpT = "MAIL_POSTGRES_OPERATION_TIMEOUT"
demoPostgresDSN = "postgres://mailservice:mailservice@localhost:5432/galaxy?search_path=mail&sslmode=disable"
)
func setMinimalConn(t *testing.T) {
t.Helper()
t.Setenv(testRedisMasterAddr, "127.0.0.1:6379")
t.Setenv(testRedisPassword, "secret")
t.Setenv(testPostgresDSN, demoPostgresDSN)
}
func TestLoadFromEnvUsesDefaults(t *testing.T) {
setMinimalConn(t)
cfg, err := LoadFromEnv()
require.NoError(t, err)
defaults := DefaultConfig()
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.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, defaults.Redis.CommandStream, cfg.Redis.CommandStream)
require.Equal(t, demoPostgresDSN, cfg.Postgres.Conn.PrimaryDSN)
require.Equal(t, defaults.SMTP, cfg.SMTP)
require.Equal(t, defaults.Templates, cfg.Templates)
require.Equal(t, defaults.AttemptWorkerConcurrency, cfg.AttemptWorkerConcurrency)
require.Equal(t, defaults.StreamBlockTimeout, cfg.StreamBlockTimeout)
require.Equal(t, defaults.OperatorRequestTimeout, cfg.OperatorRequestTimeout)
require.Equal(t, defaults.IdempotencyTTL, cfg.IdempotencyTTL)
require.Equal(t, defaults.Retention, cfg.Retention)
require.Equal(t, defaults.Telemetry, cfg.Telemetry)
}
func TestLoadFromEnvAppliesOverrides(t *testing.T) {
setMinimalConn(t)
t.Setenv(shutdownTimeoutEnvVar, "9s")
t.Setenv(logLevelEnvVar, "debug")
t.Setenv(internalHTTPAddrEnvVar, "127.0.0.1:18080")
t.Setenv(internalHTTPReadHeaderTimeoutEnvVar, "3s")
t.Setenv(internalHTTPReadTimeoutEnvVar, "11s")
t.Setenv(internalHTTPIdleTimeoutEnvVar, "61s")
t.Setenv(testRedisDB, "3")
t.Setenv(testRedisOpTimeout, "750ms")
t.Setenv(redisCommandStreamEnvVar, "mail:test_commands")
t.Setenv(testPostgresOpT, "1500ms")
t.Setenv(smtpModeEnvVar, SMTPModeSMTP)
t.Setenv(smtpAddrEnvVar, "127.0.0.1:2525")
t.Setenv(smtpUsernameEnvVar, "mailer")
t.Setenv(smtpPasswordEnvVar, "smtp-secret")
t.Setenv(smtpFromEmailEnvVar, "noreply@example.com")
t.Setenv(smtpFromNameEnvVar, "Galaxy Mail")
t.Setenv(smtpTimeoutEnvVar, "19s")
t.Setenv(smtpInsecureSkipVerifyEnvVar, "true")
t.Setenv(templateDirEnvVar, "/tmp/templates")
t.Setenv(attemptWorkerConcurrencyEnvVar, "8")
t.Setenv(streamBlockTimeoutEnvVar, "5s")
t.Setenv(operatorRequestTimeoutEnvVar, "6s")
t.Setenv(idempotencyTTLEnvVar, "48h")
t.Setenv(deliveryRetentionEnvVar, "96h")
t.Setenv(malformedCommandRetentionEnvVar, "240h")
t.Setenv(cleanupIntervalEnvVar, "30m")
t.Setenv(otelServiceNameEnvVar, "custom-mail")
t.Setenv(otelTracesExporterEnvVar, "otlp")
t.Setenv(otelMetricsExporterEnvVar, "otlp")
t.Setenv(otelExporterOTLPProtocolEnvVar, "grpc")
t.Setenv(otelStdoutTracesEnabledEnvVar, "true")
t.Setenv(otelStdoutMetricsEnabledEnvVar, "true")
cfg, err := LoadFromEnv()
require.NoError(t, err)
require.Equal(t, 9*time.Second, cfg.ShutdownTimeout)
require.Equal(t, "debug", cfg.Logging.Level)
require.Equal(t, InternalHTTPConfig{
Addr: "127.0.0.1:18080",
ReadHeaderTimeout: 3 * time.Second,
ReadTimeout: 11 * time.Second,
IdleTimeout: 61 * time.Second,
}, cfg.InternalHTTP)
require.Equal(t, "127.0.0.1:6379", cfg.Redis.Conn.MasterAddr)
require.Equal(t, "secret", cfg.Redis.Conn.Password)
require.Equal(t, 3, cfg.Redis.Conn.DB)
require.Equal(t, 750*time.Millisecond, cfg.Redis.Conn.OperationTimeout)
require.Equal(t, "mail:test_commands", cfg.Redis.CommandStream)
require.Equal(t, demoPostgresDSN, cfg.Postgres.Conn.PrimaryDSN)
require.Equal(t, 1500*time.Millisecond, cfg.Postgres.Conn.OperationTimeout)
require.Equal(t, SMTPConfig{
Mode: SMTPModeSMTP,
Addr: "127.0.0.1:2525",
Username: "mailer",
Password: "smtp-secret",
FromEmail: "noreply@example.com",
FromName: "Galaxy Mail",
Timeout: 19 * time.Second,
InsecureSkipVerify: true,
}, cfg.SMTP)
require.Equal(t, TemplateConfig{Dir: "/tmp/templates"}, cfg.Templates)
require.Equal(t, 8, cfg.AttemptWorkerConcurrency)
require.Equal(t, 5*time.Second, cfg.StreamBlockTimeout)
require.Equal(t, 6*time.Second, cfg.OperatorRequestTimeout)
require.Equal(t, 48*time.Hour, cfg.IdempotencyTTL)
require.Equal(t, 96*time.Hour, cfg.Retention.DeliveryRetention)
require.Equal(t, 240*time.Hour, cfg.Retention.MalformedCommandRetention)
require.Equal(t, 30*time.Minute, cfg.Retention.CleanupInterval)
require.Equal(t, TelemetryConfig{
ServiceName: "custom-mail",
TracesExporter: "otlp",
MetricsExporter: "otlp",
TracesProtocol: "grpc",
MetricsProtocol: "grpc",
StdoutTracesEnabled: true,
StdoutMetricsEnabled: true,
}, cfg.Telemetry)
}
func TestLoadFromEnvRejectsInvalidValues(t *testing.T) {
tests := []struct {
name string
envName string
envVal string
}{
{name: "invalid duration", envName: shutdownTimeoutEnvVar, envVal: "later"},
{name: "invalid log level", envName: logLevelEnvVar, envVal: "verbose"},
{name: "invalid redis db", envName: testRedisDB, envVal: "db-three"},
{name: "invalid redis timeout", envName: testRedisOpTimeout, envVal: "never"},
{name: "invalid smtp mode", envName: smtpModeEnvVar, envVal: "ses"},
{name: "invalid smtp timeout", envName: smtpTimeoutEnvVar, envVal: "fast"},
{name: "invalid smtp insecure skip verify", envName: smtpInsecureSkipVerifyEnvVar, envVal: "sometimes"},
{name: "invalid worker count", envName: attemptWorkerConcurrencyEnvVar, envVal: "many"},
{name: "invalid otel traces exporter", envName: otelTracesExporterEnvVar, envVal: "stdout"},
{name: "invalid otel metrics exporter", envName: otelMetricsExporterEnvVar, envVal: "stdout"},
{name: "invalid otel traces protocol", envName: otelExporterOTLPTracesProtocolEnvVar, envVal: "udp"},
{name: "invalid otel metrics protocol", envName: otelExporterOTLPMetricsProtocolEnvVar, envVal: "udp"},
{name: "invalid otel stdout traces", envName: otelStdoutTracesEnabledEnvVar, envVal: "sometimes"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
setMinimalConn(t)
t.Setenv(tt.envName, tt.envVal)
if tt.envName == smtpTimeoutEnvVar {
t.Setenv(smtpModeEnvVar, SMTPModeSMTP)
t.Setenv(smtpAddrEnvVar, "127.0.0.1:2525")
t.Setenv(smtpFromEmailEnvVar, "noreply@example.com")
}
_, err := LoadFromEnv()
require.Error(t, err)
})
}
}
func TestLoadFromEnvRejectsMissingRedisMasterAddr(t *testing.T) {
t.Setenv(testRedisPassword, "secret")
t.Setenv(testPostgresDSN, demoPostgresDSN)
_, err := LoadFromEnv()
require.Error(t, err)
require.Contains(t, err.Error(), "MAIL_REDIS_MASTER_ADDR")
}
func TestLoadFromEnvRejectsMissingPostgresDSN(t *testing.T) {
t.Setenv(testRedisMasterAddr, "127.0.0.1:6379")
t.Setenv(testRedisPassword, "secret")
_, err := LoadFromEnv()
require.Error(t, err)
require.Contains(t, err.Error(), "MAIL_POSTGRES_PRIMARY_DSN")
}
func TestLoadFromEnvRejectsLegacyRedisVars(t *testing.T) {
tests := map[string]string{
"tls": testRedisLegacyTLS,
"username": testRedisLegacyUser,
}
for name, envVar := range tests {
t.Run(name, func(t *testing.T) {
setMinimalConn(t)
t.Setenv(envVar, "anything")
_, err := LoadFromEnv()
require.Error(t, err)
require.Contains(t, err.Error(), envVar)
})
}
}
func TestLoadFromEnvRejectsInvalidSMTPConfiguration(t *testing.T) {
t.Run("missing addr", func(t *testing.T) {
setMinimalConn(t)
t.Setenv(smtpModeEnvVar, SMTPModeSMTP)
t.Setenv(smtpFromEmailEnvVar, "noreply@example.com")
_, err := LoadFromEnv()
require.Error(t, err)
require.Contains(t, err.Error(), "smtp addr")
})
t.Run("missing from email", func(t *testing.T) {
setMinimalConn(t)
t.Setenv(smtpModeEnvVar, SMTPModeSMTP)
t.Setenv(smtpAddrEnvVar, "127.0.0.1:2525")
_, err := LoadFromEnv()
require.Error(t, err)
require.Contains(t, err.Error(), "smtp from email")
})
t.Run("username without password", func(t *testing.T) {
setMinimalConn(t)
t.Setenv(smtpModeEnvVar, SMTPModeSMTP)
t.Setenv(smtpAddrEnvVar, "127.0.0.1:2525")
t.Setenv(smtpFromEmailEnvVar, "noreply@example.com")
t.Setenv(smtpUsernameEnvVar, "mailer")
_, err := LoadFromEnv()
require.Error(t, err)
require.Contains(t, err.Error(), "smtp username and password")
})
t.Run("password without username", func(t *testing.T) {
setMinimalConn(t)
t.Setenv(smtpModeEnvVar, SMTPModeSMTP)
t.Setenv(smtpAddrEnvVar, "127.0.0.1:2525")
t.Setenv(smtpFromEmailEnvVar, "noreply@example.com")
t.Setenv(smtpPasswordEnvVar, "secret")
_, err := LoadFromEnv()
require.Error(t, err)
require.Contains(t, err.Error(), "smtp username and password")
})
}
func TestLoadFromEnvRejectsNonPositiveDurationsAndCounts(t *testing.T) {
tests := []struct {
name string
envName string
envVal string
}{
{name: "shutdown timeout", envName: shutdownTimeoutEnvVar, envVal: "0s"},
{name: "read header timeout", envName: internalHTTPReadHeaderTimeoutEnvVar, envVal: "0s"},
{name: "read timeout", envName: internalHTTPReadTimeoutEnvVar, envVal: "0s"},
{name: "idle timeout", envName: internalHTTPIdleTimeoutEnvVar, envVal: "0s"},
{name: "redis operation timeout", envName: testRedisOpTimeout, envVal: "0s"},
{name: "smtp timeout", envName: smtpTimeoutEnvVar, envVal: "0s"},
{name: "attempt worker concurrency", envName: attemptWorkerConcurrencyEnvVar, envVal: "0"},
{name: "stream block timeout", envName: streamBlockTimeoutEnvVar, envVal: "0s"},
{name: "operator request timeout", envName: operatorRequestTimeoutEnvVar, envVal: "0s"},
{name: "idempotency ttl", envName: idempotencyTTLEnvVar, envVal: "0s"},
{name: "delivery retention", envName: deliveryRetentionEnvVar, envVal: "0s"},
{name: "malformed command retention", envName: malformedCommandRetentionEnvVar, envVal: "0s"},
{name: "cleanup interval", envName: cleanupIntervalEnvVar, envVal: "0s"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
setMinimalConn(t)
t.Setenv(tt.envName, tt.envVal)
if tt.envName == smtpTimeoutEnvVar {
t.Setenv(smtpModeEnvVar, SMTPModeSMTP)
t.Setenv(smtpAddrEnvVar, "127.0.0.1:2525")
t.Setenv(smtpFromEmailEnvVar, "noreply@example.com")
}
_, err := LoadFromEnv()
require.Error(t, err)
})
}
}