check / check (push) Waiting to run
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
201 lines
4.7 KiB
Go
201 lines
4.7 KiB
Go
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)
|
|
}
|
|
}
|