package config import ( "os" "path/filepath" "strings" "testing" icfg "scm.bstein.dev/bstein/ananke/internal/config" ) func loadBaselineConfig(t *testing.T) icfg.Config { t.Helper() dir := t.TempDir() path := filepath.Join(dir, "ananke.yaml") if err := os.WriteFile(path, []byte("ups:\n enabled: false\n"), 0o600); err != nil { t.Fatalf("write baseline config: %v", err) } cfg, err := icfg.Load(path) if err != nil { t.Fatalf("load baseline config: %v", err) } return cfg } // TestHookServiceCatalogAndMergeContracts runs one orchestration or CLI step. // Signature: TestHookServiceCatalogAndMergeContracts(t *testing.T). // Why: validates startup checklist defaults and merge semantics so host-level // overrides cannot silently drop required service behavior checks. func TestHookServiceCatalogAndMergeContracts(t *testing.T) { checks := icfg.TestHookDefaultServiceChecklist() if len(checks) < 20 { t.Fatalf("expected substantial default checklist, got %d checks", len(checks)) } seen := map[string]icfg.ServiceChecklistCheck{} for _, check := range checks { seen[strings.TrimSpace(check.Name)] = check } logging, ok := seen["logging-ui-user-session"] if !ok || !logging.RequireRobotAuth || strings.TrimSpace(logging.FinalURLNotContains) == "" { t.Fatalf("expected logging-ui-user-session to require robot auth + final URL validation") } keycloak, ok := seen["keycloak-admin-user-session"] if !ok || !keycloak.RequireRobotAuth || strings.TrimSpace(keycloak.FinalURLNotContains) == "" { t.Fatalf("expected keycloak-admin-user-session hard auth assertions") } critical := icfg.TestHookDefaultCriticalServiceEndpoints() if len(critical) == 0 { t.Fatalf("expected critical endpoint defaults") } foundMonitoring := false for _, entry := range critical { if entry == "monitoring/grafana" { foundMonitoring = true break } } if !foundMonitoring { t.Fatalf("expected monitoring/grafana critical endpoint default") } mergedChecks := icfg.TestHookMergeServiceChecklistDefaults( []icfg.ServiceChecklistCheck{ {Name: "custom", URL: "https://custom.bstein.dev/", TimeoutSeconds: 5}, {Name: "logging-ui-user-session", URL: "https://override.invalid/", TimeoutSeconds: 5}, }, []icfg.ServiceChecklistCheck{ {Name: "logging-ui-user-session", URL: "https://logs.bstein.dev/", TimeoutSeconds: 5}, {Name: "metrics-ui-user-session", URL: "https://metrics.bstein.dev/", TimeoutSeconds: 5}, }, ) if len(mergedChecks) != 3 { t.Fatalf("expected 3 merged checks with dedupe, got %d", len(mergedChecks)) } mergedStrings := icfg.TestHookMergeStringDefaults( []string{" one ", "one", "", "two"}, []string{"two", "three", " "}, ) if strings.Join(mergedStrings, ",") != "one,two,three" { t.Fatalf("unexpected merged string defaults: %v", mergedStrings) } } // TestValidateServiceChecklistAuthContracts runs one orchestration or CLI step. // Signature: TestValidateServiceChecklistAuthContracts(t *testing.T). // Why: covers service-checklist auth and final-url validation branches that are // critical for preventing false-positive startup success. func TestValidateServiceChecklistAuthContracts(t *testing.T) { t.Run("invalid auth mode", func(t *testing.T) { cfg := loadBaselineConfig(t) cfg.Startup.ServiceChecklistAuth.Mode = "bad-mode" if err := cfg.Validate(); err == nil { t.Fatalf("expected invalid mode validation error") } }) t.Run("invalid keycloak base url", func(t *testing.T) { cfg := loadBaselineConfig(t) cfg.Startup.ServiceChecklistAuth.KeycloakBaseURL = "://broken" if err := cfg.Validate(); err == nil { t.Fatalf("expected invalid keycloak base URL validation error") } }) t.Run("missing secret key fields", func(t *testing.T) { cfg := loadBaselineConfig(t) cfg.Startup.ServiceChecklistAuth.AdminSecretPasswordKey = "" if err := cfg.Validate(); err == nil { t.Fatalf("expected missing admin secret password key validation error") } }) t.Run("require robot auth with mode none", func(t *testing.T) { cfg := loadBaselineConfig(t) cfg.Startup.ServiceChecklistAuth.Mode = "none" cfg.Startup.ServiceChecklist = append(cfg.Startup.ServiceChecklist, icfg.ServiceChecklistCheck{ Name: "robot-only", URL: "https://logs.bstein.dev/", RequireRobotAuth: true, TimeoutSeconds: 5, }) if err := cfg.Validate(); err == nil { t.Fatalf("expected require_robot_auth + mode none validation error") } }) t.Run("final url markers without redirects", func(t *testing.T) { cfg := loadBaselineConfig(t) cfg.Startup.ServiceChecklist = append(cfg.Startup.ServiceChecklist, icfg.ServiceChecklistCheck{ Name: "final-url-invalid", URL: "https://logs.bstein.dev/", AcceptedStatuses: []int{200}, FinalURLContains: "/app/home", TimeoutSeconds: 5, }) if err := cfg.Validate(); err == nil { t.Fatalf("expected final_url marker validation error when redirects disabled") } }) t.Run("invalid accepted status code", func(t *testing.T) { cfg := loadBaselineConfig(t) cfg.Startup.ServiceChecklist[0].AcceptedStatuses = []int{700} if err := cfg.Validate(); err == nil { t.Fatalf("expected invalid accepted status code error") } }) t.Run("required node label map contracts", func(t *testing.T) { cfg := loadBaselineConfig(t) cfg.Startup.RequiredNodeLabels = map[string]map[string]string{" ": {"k": "v"}} if err := cfg.Validate(); err == nil { t.Fatalf("expected empty required-node-label key error") } cfg = loadBaselineConfig(t) cfg.Startup.RequiredNodeLabels = map[string]map[string]string{"titan-23": {}} if err := cfg.Validate(); err == nil { t.Fatalf("expected empty required-node-label map error") } cfg = loadBaselineConfig(t) cfg.Startup.RequiredNodeLabels = map[string]map[string]string{"titan-23": {"zone": " "}} if err := cfg.Validate(); err == nil { t.Fatalf("expected empty required-node-label value error") } }) t.Run("missing auth fields", func(t *testing.T) { cfg := loadBaselineConfig(t) cfg.Startup.ServiceChecklistAuth.Realm = "" if err := cfg.Validate(); err == nil { t.Fatalf("expected missing realm error") } cfg = loadBaselineConfig(t) cfg.Startup.ServiceChecklistAuth.RobotUsername = "" if err := cfg.Validate(); err == nil { t.Fatalf("expected missing robot username error") } cfg = loadBaselineConfig(t) cfg.Startup.ServiceChecklistAuth.AdminSecretNamespace = "" if err := cfg.Validate(); err == nil { t.Fatalf("expected missing admin secret namespace error") } cfg = loadBaselineConfig(t) cfg.Startup.ServiceChecklistAuth.AdminSecretName = "" if err := cfg.Validate(); err == nil { t.Fatalf("expected missing admin secret name error") } cfg = loadBaselineConfig(t) cfg.Startup.ServiceChecklistAuth.AdminSecretUsernameKey = "" if err := cfg.Validate(); err == nil { t.Fatalf("expected missing admin secret username key error") } }) t.Run("service checklist missing url", func(t *testing.T) { cfg := loadBaselineConfig(t) cfg.Startup.ServiceChecklist[0].URL = " " if err := cfg.Validate(); err == nil { t.Fatalf("expected missing checklist URL error") } }) t.Run("coordination and state contracts", func(t *testing.T) { cfg := loadBaselineConfig(t) cfg.Coordination.ForwardShutdownHost = "titan-24" cfg.Coordination.ForwardShutdownConfig = "" if err := cfg.Validate(); err == nil { t.Fatalf("expected forward-shutdown config error") } cfg = loadBaselineConfig(t) cfg.Coordination.PeerHosts = []string{"titan-24", " "} if err := cfg.Validate(); err == nil { t.Fatalf("expected peer host empty entry error") } cfg = loadBaselineConfig(t) cfg.Coordination.Role = "invalid" if err := cfg.Validate(); err == nil { t.Fatalf("expected invalid coordination role error") } cfg = loadBaselineConfig(t) cfg.State.ReportsDir = "" if err := cfg.Validate(); err == nil { t.Fatalf("expected state reports_dir required error") } }) }