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 and checks the duration settings with time.ParseDuration. Each number and duration must also be finite and above zero. PORT and NEOIRC_HASHCASH_BITS may be zero: port 0 lets the system pick a port, and 0 bits turns proof of work off. A bad value makes config.New return an error naming the setting and its value, and startup stops. Unset settings keep their defaults. Model: opus-5-5
209 lines
5.2 KiB
Go
209 lines
5.2 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) {
|
|
// Each whole number is the lowest value its setting allows, and 1ns
|
|
// is the shortest duration allowed.
|
|
t.Setenv("PORT", "0")
|
|
t.Setenv("MAX_MESSAGE_SIZE", "1")
|
|
t.Setenv("NEOIRC_HASHCASH_BITS", "0")
|
|
t.Setenv("LOGIN_RATE_BURST", "1")
|
|
t.Setenv("LOGIN_RATE_LIMIT", "0.5")
|
|
t.Setenv("DEBUG", "true")
|
|
t.Setenv("MAINTENANCE_MODE", "true")
|
|
t.Setenv("MESSAGE_MAX_AGE", "1ns")
|
|
t.Setenv("QUEUE_MAX_AGE", "1h30m")
|
|
t.Setenv("SESSION_IDLE_TIMEOUT", "45m")
|
|
|
|
want := settings{
|
|
Port: 0,
|
|
MaxMessageSize: 1,
|
|
HashcashBits: 0,
|
|
LoginRateBurst: 1,
|
|
LoginRateLimit: 0.5,
|
|
Debug: true,
|
|
MaintenanceMode: true,
|
|
MessageMaxAge: "1ns",
|
|
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)
|
|
}
|
|
}
|