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", "45m") want := settings{ Port: 9000, MaxMessageSize: 1024, HashcashBits: 0, LoginRateBurst: 10, LoginRateLimit: 0.5, Debug: true, MaintenanceMode: true, MessageMaxAge: "24h", QueueMaxAge: "1h30m", SessionIdleTimeout: "45m", } 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 TestBadSettingStopsStartup(t *testing.T) { // For each setting, the first value does not parse and the others // parse but are out of range. tests := []struct { key string values []string }{ {key: "PORT", values: []string{"80a", "-1"}}, {key: "MAX_MESSAGE_SIZE", values: []string{"4k", "0", "-4096"}}, {key: "NEOIRC_HASHCASH_BITS", values: []string{"twenty", "-1"}}, {key: "LOGIN_RATE_BURST", values: []string{"five", "0", "-5"}}, {key: "LOGIN_RATE_LIMIT", values: []string{ "fast", "0", "-1", "NaN", "Inf", "-Inf", }}, {key: "DEBUG", values: []string{"yes"}}, {key: "MAINTENANCE_MODE", values: []string{"on"}}, {key: "MESSAGE_MAX_AGE", values: []string{"30d", "0", "-1h"}}, {key: "QUEUE_MAX_AGE", values: []string{"720", "0s", "-24h"}}, {key: "SESSION_IDLE_TIMEOUT", values: []string{"a month", "0", "-1m"}}, } for _, test := range tests { for _, value := range test.values { t.Run(test.key+"="+value, func(t *testing.T) { t.Setenv(test.key, value) _, err := loadConfig(t) if err == nil { t.Fatalf("%s=%q: got no error", test.key, value) } msg := err.Error() if !strings.Contains(msg, test.key) || !strings.Contains(msg, value) { t.Errorf("error %q does not name %s and %q", msg, test.key, 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) } }