package config import ( "path/filepath" "testing" "time" ) func TestLoadUsesSafeDefaults(t *testing.T) { cfg, err := Load(emptyEnvironment) if err != nil { t.Fatalf("Load() error = %v", err) } if cfg.HTTPAddress != "127.0.0.1:8080" { t.Fatalf("HTTPAddress = %q", cfg.HTTPAddress) } if cfg.DatabasePath != filepath.FromSlash("var/cmroubao.db") { t.Fatalf("DatabasePath = %q", cfg.DatabasePath) } if cfg.AssetDirectory != filepath.FromSlash("var/assets") { t.Fatalf("AssetDirectory = %q", cfg.AssetDirectory) } if cfg.ReadHeaderTimeout <= 0 || cfg.ReadTimeout <= 0 || cfg.WriteTimeout <= 0 || cfg.IdleTimeout <= 0 || cfg.ShutdownTimeout <= 0 || cfg.MaxHeaderBytes <= 0 { t.Fatal("server safety limits must all be positive") } if cfg.WriteTimeout != 70*time.Second { t.Fatalf("WriteTimeout = %s", cfg.WriteTimeout) } if cfg.ClaimLease != 10*time.Minute || cfg.RunningLease != 30*time.Minute || cfg.ReadinessTTL != 2*time.Minute { t.Fatalf( "lifecycle durations = %s / %s / %s", cfg.ClaimLease, cfg.RunningLease, cfg.ReadinessTTL, ) } if cfg.ShutdownTimeout > 30*time.Second { t.Fatalf("ShutdownTimeout = %s", cfg.ShutdownTimeout) } if cfg.ShunyunbaoURL != "https://www.shunyunbaoerp.com" || cfg.ShunyunbaoUsername != "" || cfg.ShunyunbaoPassword != "" || cfg.ShunyunbaoTimeout != 30*time.Second || cfg.ERPDebugLog { t.Fatalf( "shunyunbao defaults = %q / %q / %q / %s", cfg.ShunyunbaoURL, cfg.ShunyunbaoUsername, cfg.ShunyunbaoPassword, cfg.ShunyunbaoTimeout, ) } } func TestLoadAcceptsExplicitConfiguration(t *testing.T) { values := map[string]string{ HTTPAddressEnvironment: "192.0.2.10:9090", DatabasePathEnvironment: "tmp/test.db", AssetDirectoryEnvironment: "tmp/assets", TLSCertificateEnvironment: "tmp/server.crt", TLSPrivateKeyEnvironment: "tmp/server.key", ClaimLeaseEnvironment: "15m", RunningLeaseEnvironment: "45m", ReadinessTTLEnvironment: "3m", ShunyunbaoURLEnvironment: "https://erp.example.test:8443", ShunyunbaoUsernameEnvironment: "service-user", ShunyunbaoPasswordEnvironment: " pass with spaces ", ERPDebugLogEnvironment: "true", } cfg, err := Load(mapEnvironment(values)) if err != nil { t.Fatalf("Load() error = %v", err) } if cfg.HTTPAddress != values[HTTPAddressEnvironment] { t.Fatalf("HTTPAddress = %q", cfg.HTTPAddress) } if cfg.DatabasePath != filepath.Clean(values[DatabasePathEnvironment]) { t.Fatalf("DatabasePath = %q", cfg.DatabasePath) } if cfg.AssetDirectory != filepath.Clean(values[AssetDirectoryEnvironment]) { t.Fatalf("AssetDirectory = %q", cfg.AssetDirectory) } if cfg.TLSCertificate != filepath.Clean(values[TLSCertificateEnvironment]) || cfg.TLSPrivateKey != filepath.Clean(values[TLSPrivateKeyEnvironment]) { t.Fatalf( "TLS files = %q / %q", cfg.TLSCertificate, cfg.TLSPrivateKey, ) } if cfg.ClaimLease != 15*time.Minute || cfg.RunningLease != 45*time.Minute || cfg.ReadinessTTL != 3*time.Minute { t.Fatalf( "lifecycle durations = %s / %s / %s", cfg.ClaimLease, cfg.RunningLease, cfg.ReadinessTTL, ) } if cfg.ShunyunbaoURL != values[ShunyunbaoURLEnvironment] || cfg.ShunyunbaoUsername != values[ShunyunbaoUsernameEnvironment] || cfg.ShunyunbaoPassword != values[ShunyunbaoPasswordEnvironment] || !cfg.ERPDebugLog { t.Fatalf("shunyunbao config was not preserved") } } func TestLoadRejectsUnsafeOrInvalidValues(t *testing.T) { tests := []struct { name string values map[string]string }{ { name: "non HTTPS shunyunbao URL", values: map[string]string{ ShunyunbaoURLEnvironment: "http://erp.example.test", }, }, { name: "shunyunbao URL path", values: map[string]string{ ShunyunbaoURLEnvironment: "https://erp.example.test/private", }, }, { name: "shunyunbao username without password", values: map[string]string{ ShunyunbaoUsernameEnvironment: "service-user", }, }, { name: "shunyunbao password without username", values: map[string]string{ ShunyunbaoPasswordEnvironment: "password", }, }, { name: "invalid ERP debug log", values: map[string]string{ ERPDebugLogEnvironment: "yes", }, }, { name: "blank explicit address", values: map[string]string{ HTTPAddressEnvironment: " ", }, }, { name: "address without host", values: map[string]string{ HTTPAddressEnvironment: ":8080", }, }, { name: "invalid port", values: map[string]string{ HTTPAddressEnvironment: "127.0.0.1:70000", }, }, { name: "memory database", values: map[string]string{ DatabasePathEnvironment: ":memory:", }, }, { name: "database DSN", values: map[string]string{ DatabasePathEnvironment: "file:test.db?mode=memory", }, }, { name: "database directory", values: map[string]string{ DatabasePathEnvironment: "./", }, }, { name: "non SQLite extension", values: map[string]string{ DatabasePathEnvironment: "var/database.txt", }, }, { name: "asset root directory", values: map[string]string{ AssetDirectoryEnvironment: string(filepath.Separator), }, }, { name: "asset current directory", values: map[string]string{ AssetDirectoryEnvironment: ".", }, }, { name: "non loopback without TLS", values: map[string]string{ HTTPAddressEnvironment: "0.0.0.0:8080", }, }, { name: "TLS certificate without key", values: map[string]string{ TLSCertificateEnvironment: "tmp/server.crt", }, }, { name: "blank TLS key", values: map[string]string{ TLSCertificateEnvironment: "tmp/server.crt", TLSPrivateKeyEnvironment: " ", }, }, { name: "claim lease below minimum", values: map[string]string{ ClaimLeaseEnvironment: "59s", }, }, { name: "running lease below minimum", values: map[string]string{ RunningLeaseEnvironment: "4m59s", }, }, { name: "running lease above maximum", values: map[string]string{ RunningLeaseEnvironment: "121m", }, }, { name: "invalid readiness TTL", values: map[string]string{ ReadinessTTLEnvironment: "soon", }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if _, err := Load(mapEnvironment(test.values)); err == nil { t.Fatal("Load() error = nil") } }) } } func TestLoadDatabasePathIgnoresHTTPConfiguration(t *testing.T) { path, err := LoadDatabasePath(mapEnvironment(map[string]string{ HTTPAddressEnvironment: "invalid", DatabasePathEnvironment: "tmp/migration.sqlite", })) if err != nil { t.Fatalf("LoadDatabasePath() error = %v", err) } if path != filepath.Clean("tmp/migration.sqlite") { t.Fatalf("path = %q", path) } } func emptyEnvironment(string) (string, bool) { return "", false } func mapEnvironment(values map[string]string) LookupEnvironment { return func(name string) (string, bool) { value, exists := values[name] return value, exists } }