Stop startup when a setting does not parse (closes #219)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
config.New read the number and true/false settings with viper's GetInt, GetFloat64 and GetBool. These return zero or false for a value that does not parse, so a typo silently changed how the server ran. config.New now parses these settings with strconv. It also checks that MESSAGE_MAX_AGE, QUEUE_MAX_AGE and SESSION_IDLE_TIMEOUT are valid Go durations. A value that does not parse makes config.New return an error naming the setting and its value, and startup stops. Unset settings keep their defaults. Model: opus-5-5
This commit is contained in:
@@ -2,6 +2,7 @@ package config_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -10,7 +11,8 @@ import (
|
||||
"sneak.berlin/go/neoirc/internal/logger"
|
||||
)
|
||||
|
||||
func newConfig(t *testing.T) *config.Config {
|
||||
// loadConfig runs config.New and returns its result.
|
||||
func loadConfig(t *testing.T) (*config.Config, error) {
|
||||
t.Helper()
|
||||
|
||||
globs := &globals.Globals{
|
||||
@@ -24,9 +26,15 @@ func newConfig(t *testing.T) *config.Config {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cfg, err := config.New(
|
||||
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)
|
||||
}
|
||||
@@ -34,6 +42,137 @@ func newConfig(t *testing.T) *config.Config {
|
||||
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", "")
|
||||
|
||||
Reference in New Issue
Block a user