package config import ( "fmt" "os" "reflect" "strings" "testing" ) func TestMain(m *testing.M) { // Set OPERATOR_NAMESPACE to avoid log.Fatal in GetOperatorNamespace // when running tests outside a Kubernetes pod os.Setenv("OPERATOR_NAMESPACE", "default") os.Exit(m.Run()) } var getMapPairsFromStringTest = []struct { in string expected []string err error }{ {"log_statement:all, work_mem:'4GB'", []string{"log_statement:all", "work_mem:'4GB'"}, nil}, {`log_statement:none, search_path:'"$user", public'`, []string{"log_statement:none", `search_path:'"$user", public'`}, nil}, {`search_path:'"$user"`, nil, fmt.Errorf("unmatched quote starting at position 13")}, {"", []string{""}, nil}, {",,log_statement:all ,", []string{"", "", "log_statement:all", ""}, nil}, } func TestGetMapPairsFromString(t *testing.T) { for _, tt := range getMapPairsFromStringTest { got, err := getMapPairsFromString(tt.in) if err != tt.err && ((err == nil || tt.err == nil) || (err.Error() != tt.err.Error())) { t.Errorf("TestGetMapPairsFromString with %s: expected error: %#v, got %#v", tt.in, tt.err, err) } if !reflect.DeepEqual(got, tt.expected) { t.Errorf("TestGetMapPairsFromString with %s: expected %#v, got %#v", tt.in, tt.expected, got) } } } func int32Ptr(i int32) *int32 { return &i } func boolPtr(b bool) *bool { return &b } var validateTests = []struct { description string cfg Config expectError bool errorMsg string }{ { description: "valid config", cfg: Config{ Resources: Resources{ MinInstances: 1, MaxInstances: 5, }, Auth: Auth{ SuperUsername: "postgres", }, ConnectionPooler: ConnectionPooler{ NumberOfInstances: int32Ptr(2), User: "pooler", }, Workers: 4, }, expectError: false, }, { description: "min instances greater than max instances", cfg: Config{ Resources: Resources{ MinInstances: 10, MaxInstances: 5, }, Auth: Auth{ SuperUsername: "postgres", }, ConnectionPooler: ConnectionPooler{ NumberOfInstances: int32Ptr(2), User: "pooler", }, Workers: 4, }, expectError: true, errorMsg: "minimum number of instances", }, { description: "workers set to zero", cfg: Config{ Resources: Resources{ MinInstances: 1, MaxInstances: 5, }, Auth: Auth{ SuperUsername: "postgres", }, ConnectionPooler: ConnectionPooler{ NumberOfInstances: int32Ptr(2), User: "pooler", }, Workers: 0, }, expectError: true, errorMsg: "number of workers should be higher than 0", }, { description: "connection pooler instances below minimum", cfg: Config{ Resources: Resources{ MinInstances: 1, MaxInstances: 5, }, Auth: Auth{ SuperUsername: "postgres", }, ConnectionPooler: ConnectionPooler{ NumberOfInstances: int32Ptr(0), User: "pooler", }, Workers: 4, }, expectError: true, errorMsg: "number of connection pooler instances", }, { description: "connection pooler user same as super user", cfg: Config{ Resources: Resources{ MinInstances: 1, MaxInstances: 5, }, Auth: Auth{ SuperUsername: "postgres", }, ConnectionPooler: ConnectionPooler{ NumberOfInstances: int32Ptr(2), User: "postgres", }, Workers: 4, }, expectError: true, errorMsg: "connection pool user is not allowed to be the same as super user", }, { description: "min and max instances both negative (disabled)", cfg: Config{ Resources: Resources{ MinInstances: -1, MaxInstances: -1, }, Auth: Auth{ SuperUsername: "postgres", }, ConnectionPooler: ConnectionPooler{ NumberOfInstances: int32Ptr(2), User: "pooler", }, Workers: 4, }, expectError: false, }, } func TestValidate(t *testing.T) { for _, tt := range validateTests { t.Run(tt.description, func(t *testing.T) { err := validate(&tt.cfg) if tt.expectError { if err == nil { t.Errorf("expected error containing %q, got nil", tt.errorMsg) return } if !strings.Contains(err.Error(), tt.errorMsg) { t.Errorf("expected error containing %q, got %q", tt.errorMsg, err.Error()) } } else { if err != nil { t.Errorf("expected no error, got %v", err) } } }) } } var newFromMapTests = []struct { description string input map[string]string expectPanic bool panicMsg string validateFunc func(t *testing.T, cfg *Config) }{ { description: "empty map uses defaults", input: map[string]string{}, expectPanic: false, validateFunc: func(t *testing.T, cfg *Config) { if cfg.Workers != 8 { t.Errorf("expected default Workers=8, got %d", cfg.Workers) } if cfg.SuperUsername != "postgres" { t.Errorf("expected default SuperUsername=postgres, got %s", cfg.SuperUsername) } if cfg.ReplicationUsername != "standby" { t.Errorf("expected default ReplicationUsername=standby, got %s", cfg.ReplicationUsername) } }, }, { description: "custom values override defaults", input: map[string]string{ "workers": "16", "super_username": "admin", }, expectPanic: false, validateFunc: func(t *testing.T, cfg *Config) { if cfg.Workers != 16 { t.Errorf("expected Workers=16, got %d", cfg.Workers) } if cfg.SuperUsername != "admin" { t.Errorf("expected SuperUsername=admin, got %s", cfg.SuperUsername) } }, }, { description: "duration parsing", input: map[string]string{ "ready_wait_interval": "10s", "ready_wait_timeout": "1m", }, expectPanic: false, validateFunc: func(t *testing.T, cfg *Config) { if cfg.ReadyWaitInterval.Seconds() != 10 { t.Errorf("expected ReadyWaitInterval=10s, got %v", cfg.ReadyWaitInterval) } if cfg.ReadyWaitTimeout.Minutes() != 1 { t.Errorf("expected ReadyWaitTimeout=1m, got %v", cfg.ReadyWaitTimeout) } }, }, { description: "boolean parsing", input: map[string]string{ "enable_teams_api": "false", "debug_logging": "false", }, expectPanic: false, validateFunc: func(t *testing.T, cfg *Config) { if cfg.EnableTeamsAPI != false { t.Errorf("expected EnableTeamsAPI=false, got %v", cfg.EnableTeamsAPI) } if cfg.DebugLogging != false { t.Errorf("expected DebugLogging=false, got %v", cfg.DebugLogging) } }, }, { description: "map parsing", input: map[string]string{ "cluster_labels": "app:myapp,env:prod", }, expectPanic: false, validateFunc: func(t *testing.T, cfg *Config) { if cfg.ClusterLabels["app"] != "myapp" { t.Errorf("expected ClusterLabels[app]=myapp, got %s", cfg.ClusterLabels["app"]) } if cfg.ClusterLabels["env"] != "prod" { t.Errorf("expected ClusterLabels[env]=prod, got %s", cfg.ClusterLabels["env"]) } }, }, { description: "slice parsing", input: map[string]string{ "inherited_labels": "label1,label2,label3", }, expectPanic: false, validateFunc: func(t *testing.T, cfg *Config) { expected := []string{"label1", "label2", "label3"} if !reflect.DeepEqual(cfg.InheritedLabels, expected) { t.Errorf("expected InheritedLabels=%v, got %v", expected, cfg.InheritedLabels) } }, }, { description: "invalid workers triggers validation panic", input: map[string]string{ "workers": "0", }, expectPanic: true, panicMsg: "number of workers should be higher than 0", }, { description: "invalid integer causes panic", input: map[string]string{ "workers": "invalid", }, expectPanic: true, panicMsg: "invalid syntax", }, } func TestNewFromMap(t *testing.T) { for _, tt := range newFromMapTests { t.Run(tt.description, func(t *testing.T) { if tt.expectPanic { defer func() { r := recover() if r == nil { t.Errorf("expected panic with message containing %q, but no panic occurred", tt.panicMsg) return } errMsg := fmt.Sprintf("%v", r) if !strings.Contains(errMsg, tt.panicMsg) { t.Errorf("expected panic message containing %q, got %q", tt.panicMsg, errMsg) } }() } cfg := NewFromMap(tt.input) if tt.expectPanic { t.Errorf("expected panic but NewFromMap returned successfully") return } if tt.validateFunc != nil { tt.validateFunc(t, cfg) } }) } }