Files
cmroubao/backend-api/internal/config/config_test.go
T

272 lines
6.6 KiB
Go
Raw Normal View History

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.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)
}
2026-07-29 09:51:41 +08:00
if cfg.ShunyunbaoURL != "https://www.shunyunbaoerp.com" ||
cfg.ShunyunbaoUsername != "" || cfg.ShunyunbaoPassword != "" ||
cfg.ShunyunbaoTimeout != 30*time.Second {
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",
2026-07-29 09:51:41 +08:00
ShunyunbaoURLEnvironment: "https://erp.example.test:8443",
ShunyunbaoUsernameEnvironment: "service-user",
ShunyunbaoPasswordEnvironment: " pass with spaces ",
}
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,
)
}
2026-07-29 09:51:41 +08:00
if cfg.ShunyunbaoURL != values[ShunyunbaoURLEnvironment] ||
cfg.ShunyunbaoUsername != values[ShunyunbaoUsernameEnvironment] ||
cfg.ShunyunbaoPassword != values[ShunyunbaoPasswordEnvironment] {
t.Fatalf("shunyunbao config was not preserved")
}
}
func TestLoadRejectsUnsafeOrInvalidValues(t *testing.T) {
tests := []struct {
name string
values map[string]string
}{
2026-07-29 09:51:41 +08:00
{
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: "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
}
}