104 lines
2.6 KiB
Go
104 lines
2.6 KiB
Go
package config
|
|
|
|
import (
|
|
"log/slog"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestLoadRequiresDSN(t *testing.T) {
|
|
t.Setenv("HAMMY_DSN", "")
|
|
|
|
if _, err := Load(); err == nil {
|
|
t.Fatal("expected an error when HAMMY_DSN is unset")
|
|
}
|
|
}
|
|
|
|
func TestLoadDefaults(t *testing.T) {
|
|
t.Setenv("HAMMY_DSN", "postgres://u:p@localhost/hammy")
|
|
|
|
c, err := Load()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if c.Addr != "127.0.0.1:8080" {
|
|
t.Errorf("Addr %q, want localhost by default - binding 0.0.0.0 should be deliberate", c.Addr)
|
|
}
|
|
if c.MaxConns != 10 {
|
|
t.Errorf("MaxConns %d, want 10", c.MaxConns)
|
|
}
|
|
if c.LogLevel != slog.LevelInfo {
|
|
t.Errorf("LogLevel %v, want info", c.LogLevel)
|
|
}
|
|
if c.RedisAddr != "" {
|
|
t.Errorf("RedisAddr %q, want empty", c.RedisAddr)
|
|
}
|
|
}
|
|
|
|
func TestLoadOverrides(t *testing.T) {
|
|
t.Setenv("HAMMY_DSN", "postgres://u:p@localhost/hammy")
|
|
t.Setenv("HAMMY_ADDR", ":9000")
|
|
t.Setenv("HAMMY_MAX_CONNS", "25")
|
|
t.Setenv("HAMMY_LOG_LEVEL", "debug")
|
|
t.Setenv("HAMMY_SHUTDOWN_TIMEOUT", "45s")
|
|
t.Setenv("HAMMY_REDIS_ADDR", "localhost:6379")
|
|
|
|
c, err := Load()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if c.Addr != ":9000" || c.MaxConns != 25 || c.LogLevel != slog.LevelDebug {
|
|
t.Errorf("overrides not applied: %+v", c)
|
|
}
|
|
if c.ShutdownTimeout != 45*time.Second {
|
|
t.Errorf("ShutdownTimeout %v, want 45s", c.ShutdownTimeout)
|
|
}
|
|
}
|
|
|
|
func TestLoadRejectsBadValues(t *testing.T) {
|
|
t.Setenv("HAMMY_DSN", "postgres://u:p@localhost/hammy")
|
|
|
|
for _, c := range []struct{ key, val string }{
|
|
{"HAMMY_MAX_CONNS", "lots"},
|
|
{"HAMMY_LOG_LEVEL", "verbose"},
|
|
{"HAMMY_SHUTDOWN_TIMEOUT", "soon"},
|
|
} {
|
|
t.Run(c.key, func(t *testing.T) {
|
|
t.Setenv(c.key, c.val)
|
|
|
|
if _, err := Load(); err == nil {
|
|
t.Errorf("%s=%q accepted", c.key, c.val)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// A DSN in a startup log is a password in a log.
|
|
func TestRedactedHidesPassword(t *testing.T) {
|
|
cases := []string{
|
|
"postgres://hammy_app:[email protected]:5432/hammy",
|
|
"postgresql://hammy_app:p%40ss@localhost/hammy?sslmode=verify-full",
|
|
}
|
|
|
|
for _, dsn := range cases {
|
|
c := Config{DSN: dsn, Addr: ":8080", MaxConns: 10}
|
|
out := c.Redacted()
|
|
|
|
if strings.Contains(out, "hunter2") || strings.Contains(out, "p%40ss") {
|
|
t.Errorf("password leaked: %s", out)
|
|
}
|
|
if !strings.Contains(out, "hammy_app") {
|
|
t.Errorf("username should survive for diagnosis: %s", out)
|
|
}
|
|
}
|
|
|
|
// A DSN with no credentials must not panic or produce nonsense.
|
|
c := Config{DSN: "postgres:///hammy?host=/var/run/postgresql"}
|
|
if out := c.Redacted(); strings.Contains(out, "***") && !strings.Contains(out, "(set)") {
|
|
t.Logf("socket DSN rendered as: %s", out)
|
|
}
|
|
}
|