package config import ( "bytes" "fmt" "os" "path/filepath" "reflect" "strings" "sync" "testing" log "github.com/sirupsen/logrus" logtest "github.com/sirupsen/logrus/hooks/test" "gopkg.in/yaml.v3" ) func TestV8ExampleLoadsAndRoundTrips(t *testing.T) { example, err := os.ReadFile(filepath.Join("..", "..", "config.example.yaml")) if err != nil { t.Fatal(err) } active, err := ParseConfigBytes(example) if err != nil { t.Fatalf("load active v8 example: %v", err) } if active.Port != 8317 || len(active.APIKeys) != 3 || active.RequestRetry != 3 || !active.QuotaExceeded.AntigravityCredits { t.Fatal("v8 template lost its default configuration") } if err = ValidateV8Config(example); err != nil { t.Fatalf("active template must use the v8 layout: %v", err) } for _, count := range []int{len(active.GeminiKey), len(active.CodexKey), len(active.ClaudeKey), len(active.VertexCompatAPIKey), len(active.XAIKey), len(active.MetaKey), len(active.InteractionsKey), len(active.OpenAICompatibility)} { if count != 0 { t.Fatal("placeholder upstream credentials must remain commented") } } if _, changed, errNormalize := NormalizeConfigLayout(example, false); errNormalize != nil || changed { t.Fatalf("v8 template unexpectedly needs conflict cleanup: changed=%v error=%v", changed, errNormalize) } // Validate the provider examples exactly as operators uncomment them. text := strings.ReplaceAll(string(example), "\r\n", "\n") _, block, found := strings.Cut(text, "# BEGIN API KEY EXAMPLES\n") if !found { t.Fatal("config.example.yaml is missing the API-key examples") } block, _, found = strings.Cut(block, "# END API KEY EXAMPLES") if !found { t.Fatal("config.example.yaml is missing the end of the API-key examples") } var uncommented strings.Builder for _, line := range strings.Split(strings.TrimSuffix(block, "\n"), "\n") { if line == "" { continue } if !strings.HasPrefix(line, "#") { t.Fatal("placeholder upstream examples must remain commented") } uncommented.WriteString(strings.TrimPrefix(strings.TrimPrefix(line, "#"), " ")) uncommented.WriteByte('\n') } data := []byte(text + "\n" + uncommented.String()) if err = ValidateV8Config(data); err != nil { t.Fatal(err) } cfg, err := ParseConfigBytes(data) if err != nil { t.Fatal(err) } if cfg.Port != 8317 || len(cfg.APIKeys) != 3 || len(cfg.GeminiKey) != 3 || len(cfg.CodexKey) != 1 || len(cfg.ClaudeKey) != 2 || len(cfg.VertexCompatAPIKey) != 1 || len(cfg.XAIKey) != 1 || len(cfg.MetaKey) != 1 || len(cfg.InteractionsKey) != 1 || len(cfg.OpenAICompatibility) != 1 { t.Fatal("v8 example fields did not reach runtime config") } if !cfg.QuotaExceeded.AntigravityCredits || cfg.QuotaExceeded.SwitchProject || cfg.QuotaExceeded.SwitchPreviewModel { t.Fatal("legacy-only quota examples must remain commented") } path := filepath.Join(t.TempDir(), "config.yaml") if err = os.WriteFile(path, data, 0600); err != nil { t.Fatal(err) } if err = SaveConfigPreserveComments(path, cfg); err != nil { t.Fatal(err) } reloaded, err := LoadConfig(path) if err != nil { t.Fatal(err) } var beforeNode, afterNode yaml.Node if err = beforeNode.Encode(cfg); err != nil { t.Fatal(err) } if err = afterNode.Encode(reloaded); err != nil { t.Fatal(err) } if !nodesStructurallyEqual(&beforeNode, &afterNode) { t.Fatal("runtime values changed after saving v8 example") } saved, _ := os.ReadFile(path) var node yaml.Node if err = yaml.Unmarshal(saved, &node); err != nil { t.Fatal(err) } if groups := yamlPath(node.Content[0], "api-keys.gemini"); groups == nil || len(groups.Content) != 2 || yamlPath(groups.Content[0], "name").Value != "gemini-1" { t.Fatal("save lost upstream group identity") } } func TestV8PresencePrecedenceAndCleanup(t *testing.T) { for _, tc := range []struct { name, raw string retry int cooling bool keys int unchanged bool }{ {"legacy", "request-retry: 4\ndisable-cooling: true\napi-keys: [old]\n", 4, true, 1, true}, {"mixed explicit zero", "request-retry: 4\ndisable-cooling: true\napi-keys: [old]\nrouting:\n retry: {request-retry: 0}\n cooldown: {disable-cooling: false}\naccess: {api-keys: []}\n", 0, false, 0, false}, {"version does not force migration", "config-version: 8\nrequest-retry: 4\ndisable-cooling: true\napi-keys: [old]\n", 4, true, 1, true}, {"partial new block", "request-retry: 4\ndisable-cooling: true\napi-keys: [old]\nrouting: {retry: {max-retry-credentials: 2}}\n", 4, true, 1, true}, } { t.Run(tc.name, func(t *testing.T) { path := filepath.Join(t.TempDir(), "config.yaml") if err := os.WriteFile(path, []byte(tc.raw), 0600); err != nil { t.Fatal(err) } cfg, err := LoadConfig(path) if err != nil { t.Fatal(err) } if cfg.RequestRetry != tc.retry || cfg.DisableCooling != tc.cooling || len(cfg.APIKeys) != tc.keys { t.Fatalf("unexpected effective config: retry=%d cooling=%v keys=%d", cfg.RequestRetry, cfg.DisableCooling, len(cfg.APIKeys)) } saved, _ := os.ReadFile(path) if tc.unchanged && string(saved) != tc.raw { t.Fatal("legacy config was rewritten") } if !tc.unchanged { var doc yaml.Node _ = yaml.Unmarshal(saved, &doc) if yamlPath(doc.Content[0], "request-retry") != nil || legacyPath(doc.Content[0], "api-keys") != nil || yamlPath(doc.Content[0], "disable-cooling") != nil { t.Fatal("conflicting legacy fields remain") } } }) } } func TestV8KeyInheritance(t *testing.T) { for _, provider := range []string{"gemini", "interactions", "vertex", "codex", "claude", "xai", "meta"} { t.Run(provider, func(t *testing.T) { raw := "request-retry: 9\napi-keys:\n " + provider + ":\n" + ` - name: shared base-url: https://example.invalid priority: 7 prefix: group proxy-url: direct headers: {X-Group: yes} models: [{name: model, alias: alias}] excluded-models: [blocked] disable-cooling: true request-retry: 3 keys: - api-key: inherited priority: null headers: null disable-cooling: null request-retry: null - api-key: overridden weight: 0 priority: 0 prefix: '' proxy-url: '' headers: {} models: [] excluded-models: [] disable-cooling: false request-retry: 0 - api-key: global-retry request-retry: -1 ` cfg, err := ParseConfigBytes([]byte(raw)) if err != nil { t.Fatal(err) } var effective yaml.Node if err = effective.Encode(cfg); err != nil { t.Fatal(err) } var old string for _, family := range v8KeyFamilies { if family.current == provider { old = family.old } } keys := yamlPath(&effective, old) if keys == nil || len(keys.Content) != 3 { t.Fatal("group did not expand to three keys") } first, second, third := keys.Content[0], keys.Content[1], keys.Content[2] if yamlPath(first, "priority").Value != "7" || yamlPath(first, "request-retry").Value != "3" || yamlPath(first, "disable-cooling").Value != "true" || yamlPath(first, "headers") == nil { t.Fatal("null did not inherit") } if yamlPath(second, "request-retry").Value != "0" || yamlPath(second, "disable-cooling").Value != "false" || yamlPath(second, "weight").Value != "0" || yamlPath(third, "request-retry").Value != "-1" { t.Fatal("explicit overrides lost") } for _, name := range []string{"headers", "models", "excluded-models"} { if node := yamlPath(second, name); node != nil && len(node.Content) != 0 { t.Fatalf("empty %s inherited group value", name) } } }) } } func TestV8MigrationPreservesLegacySemantics(t *testing.T) { raw := []byte(`host: 127.0.0.1 port: 8317 api-keys: [client] request-retry: 0 ws-auth: false quota-exceeded: {switch-project: true, switch-preview-model: true, antigravity-credits: true} codex-api-key: - api-key: a base-url: https://example.invalid headers: {X-Test: first} request-retry: 0 disable-cooling: false - api-key: b base-url: https://example.invalid headers: {X-Test: second} request-retry: -1 openai-compatibility: - name: compatible base-url: https://example.invalid api-key-entries: [{api-key: a, weight: 0}, {api-key: b, proxy-url: direct}] `) before, err := ParseConfigBytes(raw) if err != nil { t.Fatal(err) } migrated, _, err := NormalizeConfigLayout(raw, true) if err != nil { t.Fatal(err) } if err = ValidateV8Config(migrated); err != nil { t.Fatal(err) } after, err := ParseConfigBytes(migrated) if err != nil { t.Fatal(err) } // Scope metadata is intentionally added when fields move under oauth.providers. after.OAuthOnlyFields = nil if !reflect.DeepEqual(before, after) { t.Fatal("migration changed effective config") } var doc yaml.Node _ = yaml.Unmarshal(migrated, &doc) groups := yamlPath(doc.Content[0], "api-keys.codex") if len(groups.Content) != 2 { t.Fatal("different shared settings were combined") } if !after.QuotaExceeded.SwitchProject || !after.QuotaExceeded.SwitchPreviewModel { t.Fatal("legacy-only options lost") } } func TestV8MigrationCommentsUnknownLegacySections(t *testing.T) { raw := []byte(`home: enabled: true host: ignored.example enable-gemini-cli-endpoint: true forgotten-setting: items: [first, second] proxy-url: old `) unchanged, changed, err := NormalizeConfigLayout(raw, false) if err != nil || changed || string(unchanged) != string(raw) { t.Fatalf("read-only normalization changed the legacy file: changed=%v error=%v", changed, err) } migrated, changed, err := NormalizeConfigLayout(raw, true) if err != nil || !changed { t.Fatalf("migrate legacy file: changed=%v error=%v", changed, err) } var doc yaml.Node if err = yaml.Unmarshal(migrated, &doc); err != nil { t.Fatal(err) } for _, key := range []string{"home", "enable-gemini-cli-endpoint", "forgotten-setting", "proxy-url"} { if yamlPath(doc.Content[0], key) != nil { t.Fatalf("legacy section %s remained active after migration", key) } } for _, text := range []string{"# home:", "# enabled: true", "# host: ignored.example", "# enable-gemini-cli-endpoint: true", "# forgotten-setting:", "# items: [first, second]"} { if !strings.Contains(string(migrated), text) { t.Fatalf("unknown legacy section was not preserved as a comment: %s\n%s", text, migrated) } } if err = ValidateV8Config(migrated); err != nil { t.Fatalf("migrated file is invalid: %v", err) } cfg, err := ParseConfigBytes(migrated) if err != nil || cfg.ProxyURL != "old" || cfg.Home.Enabled { t.Fatalf("migration changed effective settings: cfg=%+v error=%v", cfg, err) } remigrated, _, err := NormalizeConfigLayout(migrated, true) if err != nil || strings.Count(string(remigrated), "# home:") != 1 || strings.Count(string(remigrated), "# forgotten-setting:") != 1 { t.Fatalf("repeated migration lost or duplicated comments: %v\n%s", err, remigrated) } } func TestV8MigrationCommentsUnknownNestedFields(t *testing.T) { raw := []byte(`server: {port: 8317} routing: {strategy: fill-first, session-affinity: true} oauth: providers: codex: disable-codex-cloaking: true retired-setting: {mode: old} `) unchanged, changed, err := NormalizeConfigLayout(raw, false) if err != nil || changed || string(unchanged) != string(raw) { t.Fatalf("read-only normalization changed existing config: changed=%v error=%v", changed, err) } migrated, _, err := NormalizeConfigLayout(raw, true) if err != nil { t.Fatal(err) } if err = ValidateV8Config(migrated); err != nil { t.Fatalf("migrated config is invalid: %v\n%s", err, migrated) } var doc yaml.Node if err = yaml.Unmarshal(migrated, &doc); err != nil { t.Fatal(err) } if yamlPath(doc.Content[0], "oauth.providers.codex.retired-setting") != nil || !strings.Contains(string(migrated), "# oauth.providers.codex.retired-setting:") { t.Fatalf("unknown nested field was not preserved as a comment: %s", migrated) } cfg, err := ParseConfigBytes(migrated) if err != nil || cfg.Routing.Strategy != "fill-first" || !cfg.Routing.SessionAffinity || !cfg.Codex.DisableCodexCloaking { t.Fatalf("migration changed known settings: cfg=%+v error=%v", cfg, err) } remigrated, _, err := NormalizeConfigLayout(migrated, true) if err != nil || strings.Count(string(remigrated), "# oauth.providers.codex.retired-setting:") != 1 { t.Fatalf("repeated migration lost or duplicated the comment: %v\n%s", err, remigrated) } } func TestV8MigrationCommentsUnknownLegacySectionsWarnsConsole(t *testing.T) { logger := log.StandardLogger() previousHooks := logger.ReplaceHooks(make(log.LevelHooks)) previousLevel := logger.GetLevel() previousOut := logger.Out hook := logtest.NewLocal(logger) logger.SetLevel(log.WarnLevel) t.Cleanup(func() { logger.ReplaceHooks(previousHooks) logger.SetLevel(previousLevel) logger.SetOutput(previousOut) }) raw := []byte(`host: "127.0.0.1" port: 8317 some-obsolete-legacy-block: alpha: 1 beta: two another-legacy-key: gamma: 3 `) migrated, changed, errMigrate := NormalizeConfigLayout(raw, true) if errMigrate != nil || !changed { t.Fatalf("NormalizeConfigLayout() error = %v, changed = %v", errMigrate, changed) } if !strings.Contains(string(migrated), "# some-obsolete-legacy-block:") || !strings.Contains(string(migrated), "# another-legacy-key:") { t.Fatalf("expected unknown sections to be commented out, got: %s", string(migrated)) } foundSections := make(map[string]bool) for _, entry := range hook.AllEntries() { if entry.Level == log.WarnLevel { if strings.Contains(entry.Message, "some-obsolete-legacy-block") { foundSections["some-obsolete-legacy-block"] = true } if strings.Contains(entry.Message, "another-legacy-key") { foundSections["another-legacy-key"] = true } if !strings.Contains(entry.Message, "unrecognized") || !strings.Contains(entry.Message, "commented out") { t.Fatalf("expected warning message to convey 'unrecognized' and 'commented out', got: %s", entry.Message) } if strings.Contains(entry.Message, "server") || strings.Contains(entry.Message, "host") || strings.Contains(entry.Message, "port") { t.Fatalf("known section falsely reported in warning: %s", entry.Message) } } } if !foundSections["some-obsolete-legacy-block"] || !foundSections["another-legacy-key"] { t.Fatalf("expected warnings for all unmapped sections, found: %+v", foundSections) } // Repeated migration should not repeat warnings hook.Reset() remigrated, _, errRemigrate := NormalizeConfigLayout(migrated, true) if errRemigrate != nil { t.Fatalf("repeated NormalizeConfigLayout() error = %v", errRemigrate) } if len(hook.AllEntries()) != 0 { t.Fatalf("expected no warnings on repeated migration, got: %+v", hook.AllEntries()) } _ = remigrated // Test warning hook customization var hookBuf bytes.Buffer var hookMu sync.Mutex SetV8MigrationWarnFunc(func(section, msg string) { log.Warn(msg) hookMu.Lock() _, _ = fmt.Fprintf(&hookBuf, "HOOK: %s -> %s\n", section, msg) hookMu.Unlock() }) t.Cleanup(func() { SetV8MigrationWarnFunc(nil) }) hook.Reset() _, _, errMigrateHook := NormalizeConfigLayout(raw, true) if errMigrateHook != nil { t.Fatalf("NormalizeConfigLayout() under custom hook error = %v", errMigrateHook) } hookOutput := hookBuf.String() if !strings.Contains(hookOutput, "HOOK: some-obsolete-legacy-block") || !strings.Contains(hookOutput, "HOOK: another-legacy-key") { t.Fatalf("expected custom hook to capture warnings, got: %s", hookOutput) } // Known-only config produces no warnings hook.Reset() hookBuf.Reset() knownRaw := []byte(`host: "127.0.0.1" port: 8317 debug: true `) _, _, errKnown := NormalizeConfigLayout(knownRaw, true) if errKnown != nil { t.Fatalf("NormalizeConfigLayout() error = %v", errKnown) } if len(hook.AllEntries()) != 0 || hookBuf.Len() != 0 { t.Fatalf("expected no warnings for purely known configuration, got logs=%+v hook=%s", hook.AllEntries(), hookBuf.String()) } } func TestV8MigrationConcurrentOutputSwitch(t *testing.T) { raw := []byte(`host: "127.0.0.1" port: 8317 some-concurrent-legacy-block: data: true `) var warnMu sync.Mutex var buf bytes.Buffer customWarn := func(section, msg string) { warnMu.Lock() _, _ = fmt.Fprintf(&buf, "%s: %s\n", section, msg) warnMu.Unlock() } SetV8MigrationWarnFunc(customWarn) t.Cleanup(func() { SetV8MigrationWarnFunc(nil) }) var wg sync.WaitGroup for i := 0; i < 20; i++ { wg.Add(2) go func() { defer wg.Done() SetV8MigrationWarnFunc(customWarn) SetV8MigrationWarnFunc(nil) }() go func() { defer wg.Done() _, _, _ = NormalizeConfigLayout(raw, true) }() } wg.Wait() } func TestV8SaveCommentsObsoleteSections(t *testing.T) { path := filepath.Join(t.TempDir(), "config.yaml") raw := `auth: {old: true} ampcode: {old: true} amp-upstream-url: https://old.example amp-upstream-api-key: old-secret generative-language-api-key: old-key home: {enabled: true} proxy-url: old ` if err := os.WriteFile(path, []byte(raw), 0600); err != nil { t.Fatal(err) } cfg, err := LoadConfig(path) if err != nil { t.Fatal(err) } if err = SaveConfigPreserveComments(path, cfg, true); err != nil { t.Fatal(err) } saved, err := os.ReadFile(path) if err != nil { t.Fatal(err) } if err = ValidateV8Config(saved); err != nil { t.Fatalf("saved migration is invalid: %v", err) } for _, key := range []string{"auth", "ampcode", "amp-upstream-url", "amp-upstream-api-key", "generative-language-api-key", "home"} { if !strings.Contains(string(saved), "# "+key+":") { t.Errorf("obsolete setting %s was discarded rather than commented", key) } } if !strings.Contains(string(saved), "# amp-upstream-api-key: old-secret") { t.Fatal("obsolete setting lost its value") } } func TestV8MigrationPreservesEmptyLegacyContainers(t *testing.T) { for _, section := range []configPath{ {"tls", "server.tls"}, {"remote-management", "management"}, {"pprof", "observability.pprof"}, {"discovery", "server.discovery"}, {"discovery.interfaces", "server.discovery.interfaces"}, {"credential-concurrency", "credentials.concurrency"}, {"credential-in-flight", "credentials.in-flight"}, {"streaming", "requests.streaming"}, {"payload", "requests.payload"}, {"codex", "oauth.providers.codex"}, {"codex.live-media-relay", "oauth.providers.codex.live-media-relay"}, {"codex-header-defaults", "oauth.providers.codex.header-defaults"}, {"claude", "upstream.claude"}, {"claude-code", "upstream.claude"}, {"claude-header-defaults", "upstream.claude.header-defaults"}, {"antigravity", "oauth.providers.antigravity"}, {"antigravity.connection-pool", "oauth.providers.antigravity.connection-pool"}, {"xai", "upstream.xai"}, {"devin", "oauth.providers.devin"}, } { for _, empty := range []string{"{}", "null"} { t.Run(section.old+"/"+empty, func(t *testing.T) { var doc, value yaml.Node if err := yaml.Unmarshal([]byte("port: 8317\nplugins: {configs: {sample: {enabled: false, options: {}}}}\n"), &doc); err != nil { t.Fatal(err) } if err := yaml.Unmarshal([]byte(empty), &value); err != nil { t.Fatal(err) } value.Content[0].LineComment = "Keep this empty block comment" setYAMLPath(doc.Content[0], section.old, value.Content[0]) raw, err := yaml.Marshal(&doc) if err != nil { t.Fatal(err) } before, err := ParseConfigBytes(raw) if err != nil { t.Fatal(err) } if unchanged, changed, errNormalize := NormalizeConfigLayout(raw, false); errNormalize != nil || changed || string(unchanged) != string(raw) { t.Fatalf("legacy-only load changed the file: %v", errNormalize) } migrated, _, err := NormalizeConfigLayout(raw, true) if err != nil { t.Fatal(err) } if err = ValidateV8Config(migrated); err != nil { t.Fatalf("migration left an invalid block: %v", err) } after, err := ParseConfigBytes(migrated) if err != nil { t.Fatal(err) } var beforeNode, afterNode yaml.Node if err = beforeNode.Encode((*legacyConfig)(before)); err != nil { t.Fatal(err) } if err = afterNode.Encode((*legacyConfig)(after)); err != nil { t.Fatal(err) } if !nodesStructurallyEqual(&beforeNode, &afterNode) { t.Fatal("migration changed effective defaults or plugin options") } if err = yaml.Unmarshal(migrated, &doc); err != nil { t.Fatal(err) } moved := yamlPath(doc.Content[0], section.current) if yamlPath(doc.Content[0], section.old) != nil || moved == nil || moved.Kind != yaml.MappingNode || len(moved.Content) != 0 { t.Fatal("empty legacy container was not moved to its v8 path") } if !strings.Contains(string(migrated), "Keep this empty block comment") { t.Fatal("migration dropped the empty block comment") } }) } } } func TestV8EmptyLegacyContainersKeepNewValues(t *testing.T) { raw := []byte(`port: 8317 tls: null codex: {disable-codex-cloaking: true, live-media-relay: {}} server: {tls: {enable: true, cert: server.crt, key: server.key}} oauth: {providers: {codex: {live-media-relay: {max-sessions: 12}}}} `) for _, migrate := range []bool{false, true} { data, _, err := NormalizeConfigLayout(raw, migrate) if err != nil { t.Fatal(err) } cfg, err := ParseConfigBytes(data) if err != nil { t.Fatal(err) } if !cfg.TLS.Enable || cfg.TLS.Cert != "server.crt" || cfg.TLS.Key != "server.key" || cfg.Codex.LiveMediaRelay.MaxSessions != 12 || !cfg.Codex.DisableCodexCloaking { t.Fatal("empty legacy block overwrote new values or a non-empty sibling") } var doc yaml.Node if err = yaml.Unmarshal(data, &doc); err != nil { t.Fatal(err) } if yamlPath(doc.Content[0], "tls") != nil || yamlPath(doc.Content[0], "codex.live-media-relay") != nil { t.Fatal("conflicting empty legacy blocks were not removed") } if !migrate && yamlPath(doc.Content[0], "codex.disable-codex-cloaking") == nil { t.Fatal("conflict cleanup migrated a non-conflicting legacy sibling") } } } func TestV8PrivateIPAliasPrecedenceAndMigration(t *testing.T) { for _, tc := range []struct { name, raw string want bool }{ {"allow", "codex: {live-media-relay: {allow-private-remote-ips: true}}\n", false}, {"deny", "codex: {live-media-relay: {allow-private-remote-ips: false}}\n", true}, {"new wins", "codex: {live-media-relay: {allow-private-remote-ips: false}}\noauth: {providers: {codex: {live-media-relay: {disable-private-remote-ips: false}}}}\n", false}, } { t.Run(tc.name, func(t *testing.T) { cfg, err := ParseConfigBytes([]byte(tc.raw)) if err != nil { t.Fatal(err) } if cfg.Codex.LiveMediaRelay.DisablePrivateRemoteIPs != tc.want { t.Fatal("wrong private-IP policy") } path := filepath.Join(t.TempDir(), "config.yaml") if err = os.WriteFile(path, []byte(tc.raw), 0600); err != nil { t.Fatal(err) } if err = SaveConfigPreserveComments(path, cfg, true); err != nil { t.Fatal(err) } after, err := LoadConfig(path) if err != nil { t.Fatal(err) } if after.Codex.LiveMediaRelay.DisablePrivateRemoteIPs != tc.want { t.Fatal("migration inverted the policy") } data, err := os.ReadFile(path) if err != nil || strings.Contains(string(data), "allow-private-remote-ips") { t.Fatalf("legacy alias remained: %v", err) } }) } } func TestV8SaveLegacyAdapterAndManualFallback(t *testing.T) { path := filepath.Join(t.TempDir(), "config.yaml") raw := "config-version: 8\nrouting: {retry: {request-retry: 1}}\noauth: {providers: {aistudio: {ws-auth: true}}}\n" if err := os.WriteFile(path, []byte(raw), 0600); err != nil { t.Fatal(err) } cfg, err := LoadConfig(path) if err != nil { t.Fatal(err) } cfg.RequestRetry, cfg.WebsocketAuth = 0, false if err = SaveConfigPreserveComments(path, cfg); err != nil { t.Fatal(err) } reloaded, err := LoadConfig(path) if err != nil { t.Fatal(err) } if reloaded.RequestRetry != 0 || reloaded.WebsocketAuth { t.Fatal("legacy API write was shadowed by stale v8 values") } if err = os.WriteFile(path, []byte("config-version: 8\nrequest-retry: 5\nws-auth: true\n"), 0600); err != nil { t.Fatal(err) } reloaded, err = LoadConfig(path) if err != nil { t.Fatal(err) } if reloaded.RequestRetry != 5 || !reloaded.WebsocketAuth { t.Fatal("manual legacy fallback failed") } } func TestV8RejectsInvalidGroups(t *testing.T) { for _, raw := range []string{ "api-keys: {codex: [{name: a, keys: [{api-key: a, weight: 1.5}]}]}", "api-keys: {codex: [{name: a, keys: [{api-key: a, base-url: https://invalid}]}]}", "api-keys: {codex: [{name: a, keys: {api-key: a}}]}", "server: true", "config-version: 9", "server: {port: bad}", } { t.Run(raw, func(t *testing.T) { if _, err := ParseConfigBytes([]byte(raw)); err == nil { t.Fatal("accepted invalid v8 config") } }) } } func TestV8ValidationRejectsLegacyWriteLayout(t *testing.T) { for _, raw := range []string{ "debug: true", "server: {port: 8317}\nport: 8318", "api-keys: [client]", "codex-api-key: []", "codex: {}", "quota-exceeded: {antigravity-credits: true}", "home: {enabled: true}", "enable-gemini-cli-endpoint: true", "unknown-root: true", "<<: {debug: true}", } { t.Run(raw, func(t *testing.T) { if _, err := ParseConfigBytes([]byte(raw)); err != nil { t.Fatalf("legacy file compatibility failed: %v", err) } if err := ValidateV8Config([]byte(raw)); err == nil { t.Fatal("v8 API accepted the legacy write layout") } }) } } func TestV8SecretHashUsesNewPath(t *testing.T) { path := filepath.Join(t.TempDir(), "config.yaml") if err := os.WriteFile(path, []byte("management: {secret-key: test-secret}\n"), 0600); err != nil { t.Fatal(err) } cfg, err := LoadConfig(path) if err != nil { t.Fatal(err) } saved, _ := os.ReadFile(path) if !looksLikeBcrypt(cfg.RemoteManagement.SecretKey) || strings.Contains(string(saved), "test-secret") || strings.Contains(string(saved), "remote-management") { t.Fatal("secret was not hashed at the v8 path") } } func TestV8LegacyClientKeysDoNotOverwriteUpstreamGroups(t *testing.T) { path := filepath.Join(t.TempDir(), "config.yaml") if err := os.WriteFile(path, []byte("api-keys: {codex: [{name: upstream, base-url: 'https://example.invalid', keys: [{api-key: upstream-key}]}]}\n"), 0600); err != nil { t.Fatal(err) } cfg, err := LoadConfig(path) if err != nil { t.Fatal(err) } cfg.APIKeys = []string{"client-key"} if err = SaveConfigPreserveComments(path, cfg); err != nil { t.Fatal(err) } cfg, err = LoadConfig(path) if err != nil { t.Fatal(err) } if len(cfg.APIKeys) != 1 || cfg.APIKeys[0] != "client-key" || len(cfg.CodexKey) != 1 { t.Fatal("client/upstream key collision lost credentials") } } func TestV8AliasesAndMergeKeys(t *testing.T) { raw := []byte(`routing: &routing strategy: round-robin retry: {request-retry: 0} codex-api-key: - &key api-key: first base-url: https://example.invalid request-retry: 0 - <<: *key api-key: second api-keys: gemini: - &upstream name: first base-url: https://example.invalid request-retry: 2 keys: [{api-key: one}] - <<: *upstream name: second keys: [{api-key: two, request-retry: 0}] `) before, err := ParseConfigBytes(raw) if err != nil { t.Fatal(err) } migrated, _, err := NormalizeConfigLayout(raw, true) if err != nil { t.Fatal(err) } after, err := ParseConfigBytes(migrated) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(before, after) || len(after.CodexKey) != 2 || len(after.GeminiKey) != 2 { t.Fatal("alias migration changed effective credentials") } if *after.GeminiKey[0].RequestRetry != 2 || *after.GeminiKey[1].RequestRetry != 0 { t.Fatal("merge override precedence changed") } } func TestV8LegacyAddsExplicitZeroRetryOverride(t *testing.T) { for _, raw := range []string{ "request-retry: 3\napi-keys: {codex: [{name: upstream, base-url: 'https://example.invalid', keys: [{api-key: upstream-key}]}]}\n", "request-retry: 3\ncodex-api-key: [{base-url: 'https://example.invalid', api-key: upstream-key}]\n", } { path := filepath.Join(t.TempDir(), "config.yaml") if err := os.WriteFile(path, []byte(raw), 0600); err != nil { t.Fatal(err) } cfg, err := LoadConfig(path) if err != nil { t.Fatal(err) } cfg.CodexKey[0].RequestRetry = new(0) if err = SaveConfigPreserveComments(path, cfg); err != nil { t.Fatal(err) } cfg, err = LoadConfig(path) if err != nil { t.Fatal(err) } if cfg.CodexKey[0].RequestRetry == nil || *cfg.CodexKey[0].RequestRetry != 0 { t.Fatal("new zero retry override was discarded") } } } func TestV8LegacyNullRouting(t *testing.T) { for _, raw := range []string{ "port: 8317\nrouting: null\n", "port: 8317\nrouting: ~\n", "port: 8317\nrouting:\n", "port: 8317\nrouting: null\nrequest-retry: 3\n", "server: {port: 8317}\nrouting: null\n", } { t.Run(strings.TrimSpace(raw), func(t *testing.T) { cfg, err := ParseConfigBytes([]byte(raw)) if err != nil { t.Fatalf("parse legacy null routing: %v", err) } if cfg.Port != 8317 || !reflect.DeepEqual(cfg.Routing, RoutingConfig{}) { t.Fatal("null routing did not preserve default routing") } path := filepath.Join(t.TempDir(), "config.yaml") if err = os.WriteFile(path, []byte(raw), 0600); err != nil { t.Fatal(err) } loaded, err := LoadConfig(path) if err != nil { t.Fatal(err) } data, err := os.ReadFile(path) if err != nil || string(data) != raw { t.Fatalf("loading legacy null routing rewrote the file: %v", err) } if err = SaveConfigPreserveComments(path, loaded, true); err != nil { t.Fatalf("migrate null routing: %v", err) } reloaded, err := LoadConfig(path) if err != nil || !reflect.DeepEqual(loaded.Routing, reloaded.Routing) || loaded.RequestRetry != reloaded.RequestRetry { t.Fatalf("migrating null routing changed effective settings: %v", err) } }) } for _, raw := range []string{"routing: false", "routing: []", "routing: {retry: false}", "server: null"} { if _, err := ParseConfigBytes([]byte(raw)); err == nil { t.Errorf("accepted invalid container: %s", raw) } } } func TestV8SecretHashResolvesReferences(t *testing.T) { const secret = "test-management-reference-secret" for _, tc := range []struct { name string raw string path string allowRemote bool }{ {"v8 alias", "defaults: &management\n secret-key: " + secret + "\n allow-remote: true\nmanagement: *management\nother: *management\n", "management", true}, {"v8 merge", "defaults: &management\n secret-key: " + secret + "\n allow-remote: true\nmanagement: {<<: *management, allow-remote: false}\nother: *management\n", "management", false}, {"v8 scalar alias", "password: &password " + secret + "\nmanagement: {secret-key: *password, allow-remote: true}\n", "management", true}, {"v8 root merge", "defaults: &root\n management: {secret-key: " + secret + ", allow-remote: true}\n<<: *root\n", "management", true}, {"v8 wins legacy", "remote-management: {secret-key: stale-secret, allow-remote: false}\ndefaults: &management {secret-key: " + secret + ", allow-remote: true}\nmanagement: *management\n", "management", true}, {"legacy alias", "defaults: &management\n secret-key: " + secret + "\n allow-remote: true\nremote-management: *management\nother: *management\n", "remote-management", true}, } { t.Run(tc.name, func(t *testing.T) { path := filepath.Join(t.TempDir(), "config.yaml") raw := tc.raw + "# Keep this comment\nport: 8317\n" if err := os.WriteFile(path, []byte(raw), 0600); err != nil { t.Fatal(err) } cfg, err := LoadConfig(path) if err != nil { t.Fatal(err) } saved, err := os.ReadFile(path) if err != nil { t.Fatal(err) } var doc yaml.Node if err = yaml.Unmarshal(saved, &doc); err != nil { t.Fatal(err) } effective := expandConfigAliases(doc.Content[0]) stored := yamlPath(effective, tc.path+".secret-key") if stored == nil || stored.Value != cfg.RemoteManagement.SecretKey || !looksLikeBcrypt(stored.Value) { t.Fatal("effective management secret was not hashed at the correct path") } if cfg.RemoteManagement.AllowRemote != tc.allowRemote || cfg.Port != 8317 || !strings.Contains(string(saved), "# Keep this comment") { t.Fatal("hashing changed unrelated settings or comments") } if other := yamlPath(effective, "other.secret-key"); other != nil && other.Value != secret { t.Fatal("hashing mutated another use of the shared anchor") } if tc.path == "management" && yamlPath(effective, "remote-management.secret-key") != nil { t.Fatal("hashing left a conflicting legacy secret") } reloaded, err := LoadConfig(path) if err != nil { t.Fatal(err) } second, err := os.ReadFile(path) if err != nil || string(second) != string(saved) || reloaded.RemoteManagement.SecretKey != cfg.RemoteManagement.SecretKey || reloaded.RemoteManagement.AllowRemote != tc.allowRemote { t.Fatalf("second load changed the persisted hash or settings: %v", err) } }) } } func TestV8ExampleDocumentsSessionAffinityCompactionGuidance(t *testing.T) { example, errRead := os.ReadFile(filepath.Join("..", "..", "config.example.yaml")) if errRead != nil { t.Fatal(errRead) } var doc yaml.Node if errUnmarshal := yaml.Unmarshal(example, &doc); errUnmarshal != nil { t.Fatal(errUnmarshal) } routing := yamlPath(doc.Content[0], "routing") if routing == nil { t.Fatal("config.example.yaml is missing routing block") } idx := findMapKeyIndex(routing, "session-affinity") if idx < 0 { t.Fatal("config.example.yaml is missing routing.session-affinity") } comment := routing.Content[idx].HeadComment for _, required := range []string{ "Merkle LCP prefix/compaction matching", "2 shared trailing turns", "replaces all earlier turns with a summary", "send X-Session-ID", } { if !strings.Contains(comment, required) { t.Fatalf("routing.session-affinity comment in config.example.yaml is missing %q; got:\n%s", required, comment) } } }