137 lines
3.3 KiB
Go
137 lines
3.3 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestLoadReadsConfigAndBuildsDSN(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "config.yaml")
|
|
content := []byte(`server:
|
|
port: 19090
|
|
mode: production
|
|
jwt_secret: "0123456789abcdef0123456789abcdef"
|
|
metrics_auth: "metrics-api-key-123456"
|
|
database:
|
|
host: db
|
|
port: 15432
|
|
user: user
|
|
password: pass
|
|
dbname: aiops
|
|
sslmode: require
|
|
pool_size: 7
|
|
redis:
|
|
host: redis
|
|
port: 16379
|
|
password: redispass
|
|
db: 2
|
|
metrics:
|
|
prometheus_url: http://prom
|
|
retention_days: 14
|
|
`)
|
|
if err := os.WriteFile(path, content, 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cfg, err := Load(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cfg.Server.Port != 19090 || cfg.Database.Host != "db" || cfg.Redis.DB != 2 || cfg.Metrics.RetentionDays != 14 {
|
|
t.Fatalf("unexpected config: %+v", cfg)
|
|
}
|
|
dsn := cfg.Database.DSN()
|
|
for _, want := range []string{"host=db", "port=15432", "user=user", "password=pass", "dbname=aiops", "sslmode=require", "pool_max_conns=7"} {
|
|
if !strings.Contains(dsn, want) {
|
|
t.Fatalf("dsn %q missing %q", dsn, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLoadAppliesDefaultsAndSpringDatasourceCompatibility(t *testing.T) {
|
|
t.Setenv("SPRING_DATASOURCE_URL", "spring-host")
|
|
path := filepath.Join(t.TempDir(), "empty.yaml")
|
|
if err := os.WriteFile(path, []byte("{}\n"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cfg, err := Load(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cfg.Server.Port != 8080 || cfg.Database.Port != 5432 || cfg.Redis.Port != 6379 || cfg.Metrics.RetentionDays != 7 {
|
|
t.Fatalf("defaults not applied: %+v", cfg)
|
|
}
|
|
if cfg.Database.Host != "spring-host" {
|
|
t.Fatalf("spring datasource compatibility not applied: %s", cfg.Database.Host)
|
|
}
|
|
}
|
|
|
|
func TestLoadReturnsErrorForMalformedConfig(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "bad.yaml")
|
|
if err := os.WriteFile(path, []byte("server: ["), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := Load(path); err == nil {
|
|
t.Fatal("expected malformed config error")
|
|
}
|
|
}
|
|
|
|
func TestLoadRejectsWeakProductionSecrets(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "config.yaml")
|
|
content := []byte(`server:
|
|
mode: production
|
|
jwt_secret: short
|
|
metrics_auth: short
|
|
database:
|
|
host: db
|
|
port: 5432
|
|
user: aiops
|
|
password: aiops123
|
|
dbname: ai_ops
|
|
pool_size: 1
|
|
metrics:
|
|
retention_days: 7
|
|
`)
|
|
if err := os.WriteFile(path, content, 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err := Load(path)
|
|
if err == nil || !strings.Contains(err.Error(), "jwt_secret") {
|
|
t.Fatalf("expected weak jwt secret error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestLoadAppliesExplicitEnvironmentOverrides(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "config.yaml")
|
|
content := []byte(`server:
|
|
mode: production
|
|
jwt_secret: "0123456789abcdef0123456789abcdef"
|
|
metrics_auth: "metrics-api-key-123456"
|
|
database:
|
|
host: db
|
|
port: 5432
|
|
user: aiops
|
|
password: aiops123
|
|
dbname: ai_ops
|
|
pool_size: 1
|
|
metrics:
|
|
retention_days: 7
|
|
`)
|
|
if err := os.WriteFile(path, content, 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Setenv("AI_OPS_DATABASE_PASSWORD", "override-pass")
|
|
t.Setenv("AI_OPS_SERVER_METRICS_AUTH", "override-metrics-key")
|
|
cfg, err := Load(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cfg.Database.Password != "override-pass" || cfg.Server.MetricsAuth != "override-metrics-key" {
|
|
t.Fatalf("env overrides not applied: %+v", cfg)
|
|
}
|
|
}
|