Files
galaxy-game/notification/internal/config/config_test.go
T
2026-04-22 08:49:45 +02:00

253 lines
10 KiB
Go

package config
import (
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestLoadFromEnvUsesDefaults(t *testing.T) {
t.Setenv(redisAddrEnvVar, "127.0.0.1:6379")
t.Setenv(userServiceBaseURLEnvVar, "http://user-service.internal")
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.Addr)
require.Equal(t, defaults.Redis.DB, cfg.Redis.DB)
require.Equal(t, defaults.Redis.OperationTimeout, cfg.Redis.OperationTimeout)
require.Equal(t, defaults.Streams, cfg.Streams)
require.Equal(t, defaults.Retry, cfg.Retry)
require.Equal(t, UserServiceConfig{
BaseURL: "http://user-service.internal",
Timeout: defaults.UserService.Timeout,
}, cfg.UserService)
require.Equal(t, defaults.AdminRouting, cfg.AdminRouting)
require.Equal(t, defaults.Telemetry, cfg.Telemetry)
}
func TestLoadFromEnvAppliesOverrides(t *testing.T) {
t.Setenv(shutdownTimeoutEnvVar, "9s")
t.Setenv(logLevelEnvVar, "debug")
t.Setenv(internalHTTPAddrEnvVar, "127.0.0.1:18092")
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(intentsStreamEnvVar, "notification:test_intents")
t.Setenv(intentsReadBlockTimeoutEnvVar, "3500ms")
t.Setenv(gatewayClientEventsStreamEnvVar, "gateway:test_client-events")
t.Setenv(gatewayClientEventsStreamMaxEnvVar, "2048")
t.Setenv(mailDeliveryCommandsStreamEnvVar, "mail:test_delivery_commands")
t.Setenv(pushRetryMaxAttemptsEnvVar, "5")
t.Setenv(emailRetryMaxAttemptsEnvVar, "9")
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(userServiceBaseURLEnvVar, "https://user-service.internal/api/")
t.Setenv(userServiceTimeoutEnvVar, "1500ms")
t.Setenv(adminEmailsGeoReviewRecommendedEnvVar, "First@example.com, second@example.com, first@example.com")
t.Setenv(adminEmailsGameGenerationFailedEnvVar, "ops@example.com")
t.Setenv(adminEmailsLobbyRuntimePausedAfterEnvVar, "pause@example.com, PAUSE@example.com")
t.Setenv(adminEmailsLobbyApplicationSubmittedEnvVar, "owner@example.com, OWNER@example.com")
t.Setenv(otelServiceNameEnvVar, "custom-notification")
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:18092",
ReadHeaderTimeout: 3 * time.Second,
ReadTimeout: 11 * time.Second,
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,
}, cfg.Redis)
require.Equal(t, StreamsConfig{
Intents: "notification:test_intents",
GatewayClientEvents: "gateway:test_client-events",
GatewayClientEventsStreamMaxLen: 2048,
MailDeliveryCommands: "mail:test_delivery_commands",
}, cfg.Streams)
require.Equal(t, 3500*time.Millisecond, cfg.IntentsReadBlockTimeout)
require.Equal(t, RetryConfig{
PushMaxAttempts: 5,
EmailMaxAttempts: 9,
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, UserServiceConfig{
BaseURL: "https://user-service.internal/api",
Timeout: 1500 * time.Millisecond,
}, cfg.UserService)
require.Equal(t, AdminRoutingConfig{
GeoReviewRecommended: []string{"first@example.com", "second@example.com"},
GameGenerationFailed: []string{"ops@example.com"},
LobbyRuntimePausedAfterStart: []string{"pause@example.com"},
LobbyApplicationSubmitted: []string{"owner@example.com"},
}, cfg.AdminRouting)
require.Equal(t, TelemetryConfig{
ServiceName: "custom-notification",
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: redisDBEnvVar, envVal: "db-three"},
{name: "invalid redis tls", envName: redisTLSEnabledEnvVar, envVal: "sometimes"},
{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 traces exporter", envName: otelTracesExporterEnvVar, envVal: "stdout"},
{name: "invalid metrics protocol", envName: otelExporterOTLPMetricsProtocolEnvVar, envVal: "udp"},
{name: "invalid stdout traces", envName: otelStdoutTracesEnabledEnvVar, envVal: "sometimes"},
}
for _, tt := range tests {
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")
t.Setenv(tt.envName, tt.envVal)
_, err := LoadFromEnv()
require.Error(t, err)
})
}
}
func TestLoadFromEnvRejectsMissingRequiredValues(t *testing.T) {
t.Run("missing redis addr", func(t *testing.T) {
t.Setenv(userServiceBaseURLEnvVar, "http://user-service.internal")
_, err := LoadFromEnv()
require.Error(t, err)
require.Contains(t, err.Error(), redisAddrEnvVar)
})
t.Run("missing user service base url", func(t *testing.T) {
t.Setenv(redisAddrEnvVar, "127.0.0.1:6379")
_, err := LoadFromEnv()
require.Error(t, err)
require.Contains(t, err.Error(), userServiceBaseURLEnvVar)
})
}
func TestLoadFromEnvRejectsInvalidConfiguration(t *testing.T) {
tests := []struct {
name string
envName string
envVal string
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"},
{name: "invalid public application admin email", envName: adminEmailsLobbyApplicationSubmittedEnvVar, envVal: "Owner <owner@example.com>", want: "must not include a display name"},
{name: "nonpositive gateway client events stream max len", envName: gatewayClientEventsStreamMaxEnvVar, envVal: "0", want: "must be positive"},
{name: "backoff min above max", envName: routeBackoffMinEnvVar, envVal: "10m", want: "must not exceed"},
}
for _, tt := range tests {
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")
t.Setenv(routeBackoffMaxEnvVar, "5m")
t.Setenv(tt.envName, tt.envVal)
_, err := LoadFromEnv()
require.Error(t, err)
require.Contains(t, err.Error(), tt.want)
})
}
}
func TestLoadFromEnvRejectsNonPositiveValues(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 timeout", envName: redisOperationTimeoutEnvVar, 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"},
{name: "gateway client events stream max len", envName: gatewayClientEventsStreamMaxEnvVar, envVal: "0"},
{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: "user service timeout", envName: userServiceTimeoutEnvVar, envVal: "0s"},
}
for _, tt := range tests {
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")
t.Setenv(tt.envName, tt.envVal)
_, err := LoadFromEnv()
require.Error(t, err)
})
}
}