cd07e435d9
New internal/config package: Config struct (HCL tags), Load(paths...), MergeOverrides(flags, env) with flag>env>file>default precedence (D-039). No package-level state (AD-023). --config persistent flag on root command; daemon uses cfg.ListenAddr when flag at default. 11 config tests + 2 cli tests. ---ci--- project: orca phase: 2 milestone: v0.7 status: verify requirements: covered: [REQ-054] partial: [] ---/ci---
198 lines
5.1 KiB
Go
198 lines
5.1 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
const exampleHCL = `
|
|
db_path = "/tmp/orca/test.db"
|
|
listen_addr = "127.0.0.1:9999"
|
|
ca_path = "/tmp/orca/ca.crt"
|
|
server_cert_path = "/tmp/orca/server.crt"
|
|
server_key_path = "/tmp/orca/server.key"
|
|
|
|
node_capacity {
|
|
cpu = 4
|
|
memory_mb = 8192
|
|
}
|
|
`
|
|
|
|
func writeFile(t *testing.T, dir, name, content string) string {
|
|
t.Helper()
|
|
p := filepath.Join(dir, name)
|
|
if err := os.WriteFile(p, []byte(content), 0644); err != nil {
|
|
t.Fatalf("write %s: %v", p, err)
|
|
}
|
|
return p
|
|
}
|
|
|
|
func TestLoad_Valid(t *testing.T) {
|
|
p := writeFile(t, t.TempDir(), "config.hcl", exampleHCL)
|
|
cfg, err := Load(p)
|
|
if err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
if cfg.DBPath != "/tmp/orca/test.db" {
|
|
t.Errorf("DBPath=%q", cfg.DBPath)
|
|
}
|
|
if cfg.ListenAddr != "127.0.0.1:9999" {
|
|
t.Errorf("ListenAddr=%q", cfg.ListenAddr)
|
|
}
|
|
if cfg.CAPath != "/tmp/orca/ca.crt" {
|
|
t.Errorf("CAPath=%q", cfg.CAPath)
|
|
}
|
|
if cfg.ServerCertPath != "/tmp/orca/server.crt" {
|
|
t.Errorf("ServerCertPath=%q", cfg.ServerCertPath)
|
|
}
|
|
if cfg.ServerKeyPath != "/tmp/orca/server.key" {
|
|
t.Errorf("ServerKeyPath=%q", cfg.ServerKeyPath)
|
|
}
|
|
if cfg.NodeCapacity == nil {
|
|
t.Fatal("NodeCapacity nil")
|
|
}
|
|
if cfg.NodeCapacity.CPU != 4 {
|
|
t.Errorf("CPU=%d", cfg.NodeCapacity.CPU)
|
|
}
|
|
if cfg.NodeCapacity.MemoryMB != 8192 {
|
|
t.Errorf("MemoryMB=%d", cfg.NodeCapacity.MemoryMB)
|
|
}
|
|
}
|
|
|
|
func TestLoad_Missing(t *testing.T) {
|
|
cfg, err := Load(filepath.Join(t.TempDir(), "nope.hcl"))
|
|
if err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
if cfg == nil {
|
|
t.Fatal("nil config")
|
|
}
|
|
if cfg.DBPath != "" || cfg.ListenAddr != "" || cfg.NodeCapacity != nil {
|
|
t.Errorf("expected zero config, got %+v", cfg)
|
|
}
|
|
}
|
|
|
|
func TestLoad_Malformed(t *testing.T) {
|
|
p := writeFile(t, t.TempDir(), "bad.hcl", "db_path = ")
|
|
cfg, err := Load(p)
|
|
if err == nil {
|
|
t.Fatalf("expected error, got %+v", cfg)
|
|
}
|
|
}
|
|
|
|
func TestLoad_FirstExisting(t *testing.T) {
|
|
dir := t.TempDir()
|
|
existing := writeFile(t, dir, "real.hcl", exampleHCL)
|
|
missing := filepath.Join(dir, "missing.hcl")
|
|
cfg, err := Load(missing, existing)
|
|
if err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
if cfg.DBPath != "/tmp/orca/test.db" {
|
|
t.Errorf("DBPath=%q", cfg.DBPath)
|
|
}
|
|
}
|
|
|
|
func strPtr(s string) *string { return &s }
|
|
func intPtr(i int) *int { return &i }
|
|
|
|
func TestMergeOverrides_FlagWins(t *testing.T) {
|
|
cfg := &Config{
|
|
DBPath: "/file.db",
|
|
ListenAddr: "127.0.0.1:9000",
|
|
NodeCapacity: &CapacityConfig{
|
|
CPU: 4,
|
|
MemoryMB: 8192,
|
|
},
|
|
}
|
|
flags := Flags{
|
|
DBPath: strPtr("/flag.db"),
|
|
ListenAddr: strPtr("0.0.0.0:1234"),
|
|
}
|
|
env := Environ{"ORCA_DB": "/env.db"}
|
|
out := cfg.MergeOverrides(flags, env)
|
|
if out.DBPath != "/flag.db" {
|
|
t.Errorf("DBPath=%q want /flag.db", out.DBPath)
|
|
}
|
|
if out.ListenAddr != "0.0.0.0:1234" {
|
|
t.Errorf("ListenAddr=%q want 0.0.0.0:1234", out.ListenAddr)
|
|
}
|
|
if cfg.DBPath != "/file.db" {
|
|
t.Errorf("receiver mutated: %q", cfg.DBPath)
|
|
}
|
|
}
|
|
|
|
func TestMergeOverrides_EnvWinsOverFile(t *testing.T) {
|
|
cfg := &Config{DBPath: "/file.db", ListenAddr: "127.0.0.1:9000"}
|
|
env := Environ{"ORCA_DB": "/env.db"}
|
|
out := cfg.MergeOverrides(Flags{}, env)
|
|
if out.DBPath != "/env.db" {
|
|
t.Errorf("DBPath=%q want /env.db", out.DBPath)
|
|
}
|
|
if out.ListenAddr != "127.0.0.1:9000" {
|
|
t.Errorf("ListenAddr=%q want 127.0.0.1:9000", out.ListenAddr)
|
|
}
|
|
}
|
|
|
|
func TestMergeOverrides_FileWinsOverDefault(t *testing.T) {
|
|
cfg := &Config{DBPath: "/file.db", ListenAddr: "127.0.0.1:9000"}
|
|
out := cfg.MergeOverrides(Flags{}, Environ{})
|
|
if out.DBPath != "/file.db" {
|
|
t.Errorf("DBPath=%q want /file.db", out.DBPath)
|
|
}
|
|
if out.ListenAddr != "127.0.0.1:9000" {
|
|
t.Errorf("ListenAddr=%q want 127.0.0.1:9000", out.ListenAddr)
|
|
}
|
|
}
|
|
|
|
func TestMergeOverrides_EmptyFlagDoesNotOverride(t *testing.T) {
|
|
cfg := &Config{DBPath: "/file.db"}
|
|
env := Environ{"ORCA_DB": "/env.db"}
|
|
out := cfg.MergeOverrides(Flags{}, env)
|
|
if out.DBPath != "/env.db" {
|
|
t.Errorf("DBPath=%q want /env.db", out.DBPath)
|
|
}
|
|
}
|
|
|
|
func TestMergeOverrides_EmptyEnvDoesNotOverride(t *testing.T) {
|
|
cfg := &Config{DBPath: "/file.db"}
|
|
env := Environ{"ORCA_DB": ""}
|
|
out := cfg.MergeOverrides(Flags{}, env)
|
|
if out.DBPath != "/file.db" {
|
|
t.Errorf("DBPath=%q want /file.db", out.DBPath)
|
|
}
|
|
}
|
|
|
|
func TestMergeOverrides_NodeCapacity(t *testing.T) {
|
|
cfg := &Config{
|
|
NodeCapacity: &CapacityConfig{CPU: 4, MemoryMB: 8192},
|
|
}
|
|
out := cfg.MergeOverrides(Flags{}, Environ{})
|
|
if out.NodeCapacity == nil {
|
|
t.Fatal("NodeCapacity nil")
|
|
}
|
|
if out.NodeCapacity.CPU != 4 {
|
|
t.Errorf("CPU=%d want 4", out.NodeCapacity.CPU)
|
|
}
|
|
if out.NodeCapacity.MemoryMB != 8192 {
|
|
t.Errorf("MemoryMB=%d want 8192", out.NodeCapacity.MemoryMB)
|
|
}
|
|
if cfg.NodeCapacity == out.NodeCapacity {
|
|
t.Error("NodeCapacity not cloned")
|
|
}
|
|
}
|
|
|
|
func TestMergeOverrides_NodeCapacityFlagAndEnv(t *testing.T) {
|
|
cfg := &Config{NodeCapacity: &CapacityConfig{CPU: 4, MemoryMB: 8192}}
|
|
flags := Flags{CPU: intPtr(8)}
|
|
env := Environ{"ORCA_NODE_MEMORY_MB": "16384"}
|
|
out := cfg.MergeOverrides(flags, env)
|
|
if out.NodeCapacity.CPU != 8 {
|
|
t.Errorf("CPU=%d want 8", out.NodeCapacity.CPU)
|
|
}
|
|
if out.NodeCapacity.MemoryMB != 16384 {
|
|
t.Errorf("MemoryMB=%d want 16384", out.NodeCapacity.MemoryMB)
|
|
}
|
|
}
|