package config_test import ( "os" "strings" "testing" "time" "sneak.berlin/go/neoirc/internal/config" "sneak.berlin/go/neoirc/internal/globals" "sneak.berlin/go/neoirc/internal/logger" ) // loadConfig runs config.New and returns its result. func loadConfig(t *testing.T) (*config.Config, error) { t.Helper() globs := &globals.Globals{ Appname: "neoirc-test", Version: "test", StartTime: time.Now(), } log, err := logger.New(nil, logger.Params{Globals: globs}) if err != nil { t.Fatal(err) } return config.New( nil, config.Params{Globals: globs, Logger: log}, ) } func newConfig(t *testing.T) *config.Config { t.Helper() cfg, err := loadConfig(t) if err != nil { t.Fatal(err) } return cfg } func TestUnsetSettingsKeepDefaults(t *testing.T) { // Viper counts an empty environment variable as unset. t.Setenv("PORT", "") t.Setenv("MAX_MESSAGE_SIZE", "") t.Setenv("NEOIRC_HASHCASH_BITS", "") t.Setenv("LOGIN_RATE_BURST", "") t.Setenv("LOGIN_RATE_LIMIT", "") t.Setenv("DEBUG", "") t.Setenv("MAINTENANCE_MODE", "") t.Setenv("MESSAGE_MAX_AGE", "") t.Setenv("QUEUE_MAX_AGE", "") t.Setenv("SESSION_IDLE_TIMEOUT", "") // Each duration setting defaults to 30 days. const thirtyDays = "720h" want := settings{ Port: 8080, MaxMessageSize: 4096, HashcashBits: 20, LoginRateBurst: 5, LoginRateLimit: 1, Debug: false, MaintenanceMode: false, MessageMaxAge: thirtyDays, QueueMaxAge: thirtyDays, SessionIdleTimeout: thirtyDays, } got := settingsOf(newConfig(t)) if got != want { t.Errorf("settings = %+v, want %+v", got, want) } } func TestValidSettingsAreUsed(t *testing.T) { t.Setenv("PORT", "9000") t.Setenv("MAX_MESSAGE_SIZE", "1024") t.Setenv("NEOIRC_HASHCASH_BITS", "0") t.Setenv("LOGIN_RATE_BURST", "10") t.Setenv("LOGIN_RATE_LIMIT", "0.5") t.Setenv("DEBUG", "true") t.Setenv("MAINTENANCE_MODE", "true") t.Setenv("MESSAGE_MAX_AGE", "24h") t.Setenv("QUEUE_MAX_AGE", "1h30m") t.Setenv("SESSION_IDLE_TIMEOUT", "0") want := settings{ Port: 9000, MaxMessageSize: 1024, HashcashBits: 0, LoginRateBurst: 10, LoginRateLimit: 0.5, Debug: true, MaintenanceMode: true, MessageMaxAge: "24h", QueueMaxAge: "1h30m", SessionIdleTimeout: "0", } got := settingsOf(newConfig(t)) if got != want { t.Errorf("settings = %+v, want %+v", got, want) } } // settings holds the Config fields whose settings config.New parses. type settings struct { Port int MaxMessageSize int HashcashBits int LoginRateBurst int LoginRateLimit float64 Debug bool MaintenanceMode bool MessageMaxAge string QueueMaxAge string SessionIdleTimeout string } func settingsOf(cfg *config.Config) settings { return settings{ Port: cfg.Port, MaxMessageSize: cfg.MaxMessageSize, HashcashBits: cfg.HashcashBits, LoginRateBurst: cfg.LoginRateBurst, LoginRateLimit: cfg.LoginRateLimit, Debug: cfg.Debug, MaintenanceMode: cfg.MaintenanceMode, MessageMaxAge: cfg.MessageMaxAge, QueueMaxAge: cfg.QueueMaxAge, SessionIdleTimeout: cfg.SessionIdleTimeout, } } func TestUnparseableSettingStopsStartup(t *testing.T) { tests := []struct { key string value string }{ {key: "PORT", value: "80a"}, {key: "MAX_MESSAGE_SIZE", value: "4k"}, {key: "NEOIRC_HASHCASH_BITS", value: "twenty"}, {key: "LOGIN_RATE_BURST", value: "five"}, {key: "LOGIN_RATE_LIMIT", value: "fast"}, {key: "DEBUG", value: "yes"}, {key: "MAINTENANCE_MODE", value: "on"}, {key: "MESSAGE_MAX_AGE", value: "30d"}, {key: "QUEUE_MAX_AGE", value: "720"}, {key: "SESSION_IDLE_TIMEOUT", value: "a month"}, } for _, test := range tests { t.Run(test.key, func(t *testing.T) { t.Setenv(test.key, test.value) _, err := loadConfig(t) if err == nil { t.Fatalf("%s=%q: got no error", test.key, test.value) } msg := err.Error() if !strings.Contains(msg, test.key) || !strings.Contains(msg, test.value) { t.Errorf("error %q does not name %s and %q", msg, test.key, test.value) } }) } } func TestIRCListenAddrUnsetKeepsDefault(t *testing.T) { // t.Setenv puts back whatever value was there after the test. t.Setenv("IRC_LISTEN_ADDR", "") err := os.Unsetenv("IRC_LISTEN_ADDR") if err != nil { t.Fatal(err) } const want = ":6667" cfg := newConfig(t) if cfg.IRCListenAddr != want { t.Errorf("IRCListenAddr = %q, want %q", cfg.IRCListenAddr, want) } } func TestIRCListenAddrEmptyTurnsListenerOff(t *testing.T) { t.Setenv("IRC_LISTEN_ADDR", "") cfg := newConfig(t) if cfg.IRCListenAddr != "" { t.Errorf("IRCListenAddr = %q, want empty", cfg.IRCListenAddr) } }