package bootstrap import ( "context" "os" "path/filepath" "strings" "testing" "iop/apps/edge/internal/configrefresh" ) const legacyCredentialModeYAML = ` server: listen: "127.0.0.1:9090" credential_plane: enabled: false ` const managedCredentialModeYAML = ` server: listen: "127.0.0.1:9090" tls: enabled: true cert: "/cert" key: "/key" ca: "/ca" peer_role: "node" control_plane: enabled: true wire_addr: "control-plane:19081" tls: enabled: true cert: "/cert" key: "/key" ca: "/ca" server_name: "control-plane" peer_role: "control-plane" credential_plane: enabled: true lease_ttl_seconds: 30 lease_cache_size: 256 ` func writeCredentialModeConfig(t *testing.T, name, body string) string { t.Helper() path := filepath.Join(t.TempDir(), name) if err := os.WriteFile(path, []byte(body), 0o600); err != nil { t.Fatal(err) } return path } func TestCredentialModeRefreshAndRollbackRequireRestartWithoutPartialApply(t *testing.T) { managedEdgeKeyChanged := strings.Replace(managedCredentialModeYAML, " key: \"/key\"\n ca: \"/ca\"\n peer_role: \"node\"", " key: \"/rotated-edge-key\"\n ca: \"/ca\"\n peer_role: \"node\"", 1) managedControlPlaneKeyChanged := strings.Replace(managedCredentialModeYAML, " key: \"/key\"\n ca: \"/ca\"\n server_name:", " key: \"/rotated-control-plane-key\"\n ca: \"/ca\"\n server_name:", 1) managedOpenAI := managedCredentialModeYAML + ` openai: enabled: true provider_id: "provider-fixture" tls: enabled: true cert: "/openai-cert" key: "/openai-key" ` for _, tc := range []struct { name string currentBody string candidateBody string expectedPath string }{ {name: "legacy to managed", currentBody: legacyCredentialModeYAML, candidateBody: managedCredentialModeYAML, expectedPath: "credential_plane"}, {name: "managed rollback to legacy", currentBody: managedCredentialModeYAML, candidateBody: legacyCredentialModeYAML, expectedPath: "credential_plane"}, {name: "legacy source change", currentBody: legacyCredentialModeYAML, candidateBody: legacyCredentialModeYAML + "\nopenai:\n bearer_token: legacy-fixture\n", expectedPath: "openai"}, {name: "managed lease setting change", currentBody: managedCredentialModeYAML, candidateBody: strings.Replace(managedCredentialModeYAML, "lease_ttl_seconds: 30", "lease_ttl_seconds: 31", 1), expectedPath: "credential_plane"}, {name: "managed edge TLS key rotation", currentBody: managedCredentialModeYAML, candidateBody: managedEdgeKeyChanged, expectedPath: "tls"}, {name: "managed Control Plane TLS key rotation", currentBody: managedCredentialModeYAML, candidateBody: managedControlPlaneKeyChanged, expectedPath: "control_plane"}, {name: "managed OpenAI TLS key rotation", currentBody: managedOpenAI, candidateBody: strings.Replace(managedOpenAI, "/openai-key", "/rotated-openai-key", 1), expectedPath: "openai"}, } { t.Run(tc.name, func(t *testing.T) { currentPath := writeCredentialModeConfig(t, "current.yaml", tc.currentBody) candidatePath := writeCredentialModeConfig(t, "candidate.yaml", tc.candidateBody) current, err := configrefresh.LoadCandidate(currentPath) if err != nil { t.Fatalf("load current: %v", err) } runtime := &Runtime{Cfg: current} result, err := runtime.RefreshConfig(context.Background(), configrefresh.Request{ Mode: configrefresh.ModeApply, ConfigPath: candidatePath, RequestID: "credential-mode-transition", }) if err != nil { t.Fatalf("RefreshConfig: %v", err) } if result.Status != configrefresh.StatusRestartRequired { t.Fatalf("status = %q, want restart_required: %+v", result.Status, result.Changes) } found := false for _, change := range result.Changes { if change.Path == tc.expectedPath && change.Class == configrefresh.StatusRestartRequired { found = true } } if !found { t.Fatalf("restart path %q missing: %+v", tc.expectedPath, result.Changes) } if runtime.Cfg != current { t.Fatal("restart-required credential mode was partially applied") } }) } }