feat(config): redesign settings navigation and preserve v7 config migration
- reorganize advanced settings with horizontal navigation, search, and contextual help - remove LAN discovery and trusted proxy settings - add robust v7-to-v8 migration for custom, plugin, routing, TLS, and credential fields - preserve unsaved drafts during external configuration refresh - add migration and settings UI coverage
This commit is contained in:
1 parent
c89e2fb020
commit
fdf56ee7eb
22 files changed
+1497
-632
No files matched your search
@@ -65,10 +65,8 @@ const PATHS: &[(&str, &str)] = &[
|
||||
("oauth/providers/xai", "xai"),
|
||||
("server/host", "host"),
|
||||
("server/port", "port"),
|
||||
("server/trusted-proxies", "trusted-proxies"),
|
||||
("server/tls", "tls"),
|
||||
("server/commercial-mode", "commercial-mode"),
|
||||
("server/discovery", "discovery"),
|
||||
("management", "remote-management"),
|
||||
("credentials/concurrency", "credential-concurrency"),
|
||||
("credentials/in-flight", "credential-in-flight"),
|
||||
@@ -139,6 +137,18 @@ const PATHS: &[(&str, &str)] = &[
|
||||
("observability/pprof", "pprof"),
|
||||
];
|
||||
|
||||
pub(crate) fn legacy_extended_config_paths() -> Vec<(Vec<&'static str>, Vec<&'static str>)> {
|
||||
PATHS
|
||||
.iter()
|
||||
.map(|(native, legacy)| {
|
||||
(
|
||||
legacy.split('/').collect::<Vec<_>>(),
|
||||
native.split('/').collect::<Vec<_>>(),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn parts(path: &str) -> Vec<String> {
|
||||
path.split('/').map(str::to_owned).collect()
|
||||
}
|
||||
@@ -285,8 +295,6 @@ fn legacy_path(path: &[String]) -> Vec<String> {
|
||||
// Only template-backed settings are writable. Existing GUI-owned settings and
|
||||
// system metadata cannot be changed by this command, even via a parent object.
|
||||
const OBJECT_PATHS: &[&str] = &[
|
||||
"server/discovery",
|
||||
"server/discovery/interfaces",
|
||||
"credentials/concurrency",
|
||||
"credentials/in-flight",
|
||||
"client/codex",
|
||||
@@ -306,11 +314,6 @@ const OBJECT_PATHS: &[&str] = &[
|
||||
"oauth/providers/xai",
|
||||
];
|
||||
const FIELD_GROUPS: &[(&str, &str, &str)] = &[
|
||||
("server", "strings", "trusted-proxies"),
|
||||
("server/discovery", "bool", "enabled auth-required advertise-management"),
|
||||
("server/discovery", "string", "service-name service-type"),
|
||||
("server/discovery", "strings", "subtypes"),
|
||||
("server/discovery/interfaces", "strings", "include exclude"),
|
||||
("management", "bool", "allow-remote disable-control-panel disable-auto-update-panel"),
|
||||
("management", "string", "base-url panel-github-repository"),
|
||||
("credentials/concurrency", "string", "cpa-heartbeat-timeout cpa-cancel-bound reclaim-grace cleanup-interval release-flush-interval release-max-backoff busy-retry-min busy-retry-max"),
|
||||
@@ -421,17 +424,6 @@ fn duration_nanos(value: &str) -> Option<i64> {
|
||||
(end == text.len() && nanos < i64::MAX as f64)
|
||||
.then(|| (nanos as i64) * if value.starts_with('-') { -1 } else { 1 })
|
||||
}
|
||||
fn valid_ip_or_cidr(value: &str) -> bool {
|
||||
if let Some((address, prefix)) = value.split_once('/') {
|
||||
address.parse::<IpAddr>().is_ok_and(|address| {
|
||||
prefix
|
||||
.parse::<u8>()
|
||||
.is_ok_and(|prefix| prefix <= if address.is_ipv4() { 32 } else { 128 })
|
||||
})
|
||||
} else {
|
||||
value.parse::<IpAddr>().is_ok()
|
||||
}
|
||||
}
|
||||
fn valid_ice_url(value: &str) -> bool {
|
||||
let Some((scheme, address)) = value.split_once(':') else {
|
||||
return false;
|
||||
@@ -722,10 +714,9 @@ fn validate_value(path: &[String], value: &Json, before: &Json) -> Result<(), St
|
||||
Some("strings")
|
||||
if value.as_array().is_some_and(|items| {
|
||||
items.iter().all(|value| {
|
||||
value.as_str().is_some_and(|text| {
|
||||
!text.chars().any(char::is_control)
|
||||
&& (key != "server/trusted-proxies" || valid_ip_or_cidr(text))
|
||||
})
|
||||
value
|
||||
.as_str()
|
||||
.is_some_and(|text| !text.chars().any(char::is_control))
|
||||
})
|
||||
}) => {}
|
||||
Some("image-mode")
|
||||
@@ -1075,7 +1066,6 @@ fn prepare_patch(
|
||||
"server/host",
|
||||
"server/port",
|
||||
"server/tls",
|
||||
"server/trusted-proxies",
|
||||
"management",
|
||||
"oauth/auth-dir",
|
||||
]
|
||||
@@ -1242,7 +1232,7 @@ client: {codex: {optimize-multi-agent-v2: false}}
|
||||
json!(true),
|
||||
false,
|
||||
),
|
||||
change(content, "server/trusted-proxies", json!([]), false),
|
||||
change(content, "management/allow-remote", json!(true), false),
|
||||
];
|
||||
let (rendered, result) = prepare_patch(content, &changes).unwrap();
|
||||
assert!(rendered.contains("# keep heading"));
|
||||
@@ -1404,7 +1394,6 @@ client: {codex: {optimize-multi-agent-v2: false}}
|
||||
"999999999999999999"
|
||||
));
|
||||
let rejected = [
|
||||
("server/trusted-proxies", json!(["192.0.2.1/40"])),
|
||||
("observability/pprof/addr", json!("bad-address")),
|
||||
(
|
||||
"oauth/providers/codex/live-media-relay/public-ip",
|
||||
@@ -1449,7 +1438,6 @@ client: {codex: {optimize-multi-agent-v2: false}}
|
||||
);
|
||||
}
|
||||
let accepted = [
|
||||
("server/trusted-proxies", json!(["192.0.2.1/24", "::1/128"])),
|
||||
("observability/pprof/addr", json!("[::1]:8316")),
|
||||
(
|
||||
"oauth/providers/codex/live-media-relay/public-ip",
|
||||
@@ -1526,8 +1514,6 @@ client: {codex: {optimize-multi-agent-v2: false}}
|
||||
fn extended_restart_hint_distinguishes_live_updates_from_startup_settings() {
|
||||
let content = "config-version: 8\n";
|
||||
for (path, value) in [
|
||||
("server/discovery", json!({"enabled": true})),
|
||||
("server/discovery/service-name", json!("office")),
|
||||
(
|
||||
"observability/pprof",
|
||||
json!({"enable": true, "addr": "127.0.0.1:8316"}),
|
||||
@@ -1541,7 +1527,6 @@ client: {codex: {optimize-multi-agent-v2: false}}
|
||||
assert!(!result.restart_required, "{path} supports live updates");
|
||||
}
|
||||
for (path, value) in [
|
||||
("server/trusted-proxies", json!(["127.0.0.1"])),
|
||||
("management/allow-remote", json!(true)),
|
||||
("oauth/auth-dir", json!("~/selected-oauth")),
|
||||
] {
|
||||
@@ -1549,13 +1534,13 @@ client: {codex: {optimize-multi-agent-v2: false}}
|
||||
prepare_patch(content, &[change(content, path, value, false)]).unwrap();
|
||||
assert!(result.restart_required, "{path} requires a restart");
|
||||
}
|
||||
let unchanged = "config-version: 8\nserver: {trusted-proxies: []}\n";
|
||||
let unchanged = "config-version: 8\nmanagement: {allow-remote: false}\n";
|
||||
let (_, result) = prepare_patch(
|
||||
unchanged,
|
||||
&[change(
|
||||
unchanged,
|
||||
"server/trusted-proxies",
|
||||
json!([]),
|
||||
"management/allow-remote",
|
||||
json!(false),
|
||||
false,
|
||||
)],
|
||||
)
|
||||
|
||||
@@ -1189,6 +1189,14 @@ pub(crate) fn core_config_uses_v8(document: &serde_norway::Value) -> bool {
|
||||
|| yaml_mapping_value(root, "api-keys").is_some_and(serde_norway::Value::is_mapping)
|
||||
}
|
||||
|
||||
pub(crate) fn core_config_declares_v8(document: &serde_norway::Value) -> bool {
|
||||
document
|
||||
.as_mapping()
|
||||
.and_then(|root| yaml_mapping_value(root, "config-version"))
|
||||
.and_then(serde_norway::Value::as_u64)
|
||||
.is_some_and(|version| version >= 8)
|
||||
}
|
||||
|
||||
pub(crate) fn set_core_yaml_path_value(
|
||||
document: &mut serde_norway::Value,
|
||||
path: &[&str],
|
||||
@@ -1219,6 +1227,156 @@ pub(crate) fn set_core_yaml_path_value(
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub(crate) fn migrate_legacy_core_config_to_v8(
|
||||
template: &str,
|
||||
legacy: &str,
|
||||
) -> Result<String, String> {
|
||||
let template_document = serde_norway::from_str::<serde_norway::Value>(template)
|
||||
.map_err(|error| format!("Failed to parse new kernel configuration template: {error}"))?;
|
||||
if !core_config_uses_v8(&template_document) {
|
||||
return Err("New kernel configuration template is not v8".to_string());
|
||||
}
|
||||
let legacy_document = serde_norway::from_str::<serde_norway::Value>(legacy)
|
||||
.map_err(|error| format!("Failed to parse existing kernel configuration: {error}"))?;
|
||||
if core_config_declares_v8(&legacy_document) {
|
||||
return Ok(legacy.to_string());
|
||||
}
|
||||
let legacy_root = legacy_document
|
||||
.as_mapping()
|
||||
.ok_or_else(|| "Existing kernel configuration root must be a YAML mapping".to_string())?;
|
||||
let mut migrated = template_document.clone();
|
||||
// Start from the new template so newly introduced defaults remain available,
|
||||
// then overlay the complete legacy document. The explicit lifts below only
|
||||
// move fields whose spelling changed between schemas; all other user-owned
|
||||
// sections must survive the upgrade as-is.
|
||||
merge_yaml_values(&mut migrated, legacy_document.clone());
|
||||
if let Some(version) = yaml_mapping_value(
|
||||
template_document
|
||||
.as_mapping()
|
||||
.ok_or_else(|| "New kernel configuration template root must be a YAML mapping".to_string())?,
|
||||
"config-version",
|
||||
).cloned() {
|
||||
set_core_yaml_path_value(&mut migrated, &["config-version"], version)?;
|
||||
} else {
|
||||
remove_core_yaml_path_value(&mut migrated, &["config-version"]);
|
||||
}
|
||||
lift_legacy_core_config_fields(
|
||||
&mut migrated,
|
||||
legacy_root,
|
||||
template_document
|
||||
.as_mapping()
|
||||
.ok_or_else(|| "New kernel configuration template root must be a YAML mapping".to_string())?,
|
||||
)?;
|
||||
render_yaml_value_changes(template, &template_document, &migrated)
|
||||
}
|
||||
|
||||
fn lift_legacy_core_config_fields(
|
||||
document: &mut serde_norway::Value,
|
||||
legacy: &serde_norway::Mapping,
|
||||
template: &serde_norway::Mapping,
|
||||
) -> Result<(), String> {
|
||||
let copy = |document: &mut serde_norway::Value, legacy_path: &[&str], v8_path: &[&str]| -> Result<(), String> {
|
||||
let Some(value) = nested_yaml_value(legacy, legacy_path).cloned() else {
|
||||
return Ok(());
|
||||
};
|
||||
// A partially migrated file can contain both spellings. Prefer the
|
||||
// canonical v8 value in that case; otherwise move the legacy value.
|
||||
if nested_yaml_value(legacy, v8_path).is_none() {
|
||||
set_core_yaml_path_value(document, v8_path, value)?;
|
||||
}
|
||||
if nested_yaml_value(template, legacy_path).is_none() {
|
||||
remove_core_yaml_path_value(document, legacy_path);
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
for (legacy_path, v8_path) in [
|
||||
(&["host"][..], &["server", "host"][..]),
|
||||
(&["port"], &["server", "port"]),
|
||||
(&["commercial-mode"], &["server", "commercial-mode"]),
|
||||
(&["auth-dir"], &["oauth", "auth-dir"]),
|
||||
(&["debug"], &["observability", "logs", "debug"]),
|
||||
(&["logging-to-file"], &["observability", "logs", "logging-to-file"]),
|
||||
(&["logs-max-total-size-mb"], &["observability", "logs", "logs-max-total-size-mb"]),
|
||||
(&["error-logs-max-files"], &["observability", "logs", "error-logs-max-files"]),
|
||||
(&["request-log"], &["observability", "logs", "request-log"]),
|
||||
(&["usage-statistics-enabled"], &["observability", "usage", "usage-statistics-enabled"]),
|
||||
(&["redis-usage-queue-retention-seconds"], &["observability", "usage", "redis-usage-queue-retention-seconds"]),
|
||||
(&["proxy-url"], &["requests", "proxy-url"]),
|
||||
(&["disable-cooling"], &["routing", "cooldown", "disable-cooling"]),
|
||||
(&["request-retry"], &["routing", "retry", "request-retry"]),
|
||||
(&["max-retry-credentials"], &["routing", "retry", "max-retry-credentials"]),
|
||||
(&["max-retry-interval"], &["routing", "retry", "max-retry-interval"]),
|
||||
(&["streaming", "bootstrap-retries"], &["requests", "streaming", "bootstrap-retries"]),
|
||||
(&["remote-management", "secret-key"], &["management", "secret-key"]),
|
||||
(&["oauth-model-alias"], &["oauth", "model-alias"]),
|
||||
(&["payload"], &["requests", "payload"]),
|
||||
] {
|
||||
copy(document, legacy_path, v8_path)?;
|
||||
}
|
||||
if let Some(tls) = yaml_mapping_value(legacy, "tls").filter(|value| value.is_mapping()).cloned() {
|
||||
if nested_yaml_value(legacy, &["server", "tls"]).is_none() {
|
||||
set_core_yaml_path_value(document, &["server", "tls"], tls)?;
|
||||
}
|
||||
if yaml_mapping_value(template, "tls").is_none() {
|
||||
remove_core_yaml_path_value(document, &["tls"]);
|
||||
}
|
||||
}
|
||||
if let Some(keys) = yaml_mapping_value(legacy, "api-keys").filter(|value| value.is_sequence()).cloned() {
|
||||
if nested_yaml_value(legacy, &["access", "api-keys"]).is_none() {
|
||||
set_core_yaml_path_value(document, &["access", "api-keys"], keys)?;
|
||||
}
|
||||
if let Some(template_upstreams) = yaml_mapping_value(template, "api-keys")
|
||||
.filter(|value| value.is_mapping())
|
||||
.cloned()
|
||||
{
|
||||
// A v8 template may already contain the upstream credential map at
|
||||
// the root. The legacy client-key sequence must not replace it.
|
||||
set_core_yaml_path_value(document, &["api-keys"], template_upstreams)?;
|
||||
} else {
|
||||
remove_core_yaml_path_value(document, &["api-keys"]);
|
||||
}
|
||||
}
|
||||
let has_canonical_upstream_mapping = yaml_mapping_value(legacy, "api-keys")
|
||||
.is_some_and(serde_norway::Value::is_mapping);
|
||||
for (legacy_name, provider) in V8_PROVIDER_FAMILIES {
|
||||
let Some(records) = yaml_mapping_value(legacy, legacy_name) else {
|
||||
continue;
|
||||
};
|
||||
if !has_canonical_upstream_mapping
|
||||
&& nested_yaml_value(legacy, &["api-keys", provider]).is_none()
|
||||
{
|
||||
let grouped = group_legacy_provider_records(provider, records)?;
|
||||
set_core_yaml_path_value(document, &["api-keys", provider], grouped)?;
|
||||
}
|
||||
if yaml_mapping_value(template, legacy_name).is_none() {
|
||||
remove_core_yaml_path_value(document, &[legacy_name]);
|
||||
}
|
||||
}
|
||||
for (legacy_path, v8_path) in legacy_extended_config_paths() {
|
||||
copy(document, &legacy_path, &v8_path)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_core_yaml_path_value(document: &mut serde_norway::Value, path: &[&str]) -> bool {
|
||||
let Some((key, parents)) = path.split_last() else {
|
||||
return false;
|
||||
};
|
||||
let Some(mut mapping) = document.as_mapping_mut() else {
|
||||
return false;
|
||||
};
|
||||
for section in parents {
|
||||
let Some(parent) = mapping.get_mut(yaml_key(section)) else {
|
||||
return false;
|
||||
};
|
||||
let Some(parent) = parent.as_mapping_mut() else {
|
||||
return false;
|
||||
};
|
||||
mapping = parent;
|
||||
}
|
||||
mapping.remove(yaml_key(key)).is_some()
|
||||
}
|
||||
|
||||
pub(crate) fn set_core_yaml_schema_value(
|
||||
document: &mut serde_norway::Value,
|
||||
legacy_path: &[&str],
|
||||
|
||||
@@ -1957,7 +1957,11 @@ pub(crate) fn migrate_core_config_for_update(
|
||||
}
|
||||
let template_document = serde_norway::from_str::<serde_norway::Value>(&template)
|
||||
.map_err(|error| format!("Failed to parse new kernel configuration template: {error}"))?;
|
||||
let migrated = if core_config_uses_v8(&template_document) {
|
||||
let migrated = if (core_config_declares_v8(&template_document) || core_config_uses_v8(&template_document))
|
||||
&& !core_config_declares_v8(&old_document)
|
||||
{
|
||||
migrate_legacy_core_config_to_v8(&template, &old_config)?
|
||||
} else if core_config_uses_v8(&template_document) {
|
||||
old_config
|
||||
} else {
|
||||
merge_core_config_fields(&template, Some(&old_config))?
|
||||
|
||||
@@ -594,20 +594,36 @@ fn replacing_a_core_migrates_old_fields_into_the_new_template() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replacing_v7_with_v8_preserves_the_legacy_config_byte_for_byte() {
|
||||
fn replacing_v7_with_v8_lifts_legacy_values_into_the_new_layout() {
|
||||
let root = agent_test_home("core-config-v8-preserve");
|
||||
let source = root.join("source");
|
||||
let target = root.join("target");
|
||||
fs::create_dir_all(&source).unwrap();
|
||||
fs::create_dir_all(&target).unwrap();
|
||||
let old_config = "# User v7 config\nhost: 127.0.0.1\nport: 9527\nremote-management:\n secret-key: user-secret\napi-keys: [client-key]\nproxy-url: direct\n";
|
||||
let v8_template = "config-version: 8\nserver: {host: '', port: 8317}\nmanagement: {secret-key: ''}\naccess: {api-keys: [template-key]}\nrequests: {proxy-url: ''}\n";
|
||||
let old_config = "# User v7 config\nhost: 127.0.0.1\nport: 9527\nremote-management:\n secret-key: user-secret\napi-keys: [client-key]\nproxy-url: direct\nrouting:\n strategy: fill-first\n session-affinity: true\n session-affinity-ttl: 2h\nplugins:\n configs:\n local:\n enabled: true\n store-auth:\n registry: stored-token\n";
|
||||
let v8_template = "config-version: 8\nserver: {host: '', port: 8317}\nmanagement: {secret-key: ''}\naccess: {api-keys: [template-key]}\napi-keys: {codex: [{name: template-upstream, keys: []}]}\nrequests: {proxy-url: ''}\nrouting: {strategy: round-robin, session-affinity: false, session-affinity-ttl: 30m}\nplugins: {configs: {}, store-auth: {}}\n";
|
||||
fs::write(source.join(CORE_CONFIG_FILE), old_config).unwrap();
|
||||
fs::write(target.join(CORE_EXAMPLE_CONFIG_FILE), v8_template).unwrap();
|
||||
|
||||
migrate_core_config_for_update(&source, &target).unwrap();
|
||||
|
||||
assert_eq!(fs::read_to_string(target.join(CORE_CONFIG_FILE)).unwrap(), old_config);
|
||||
let migrated = fs::read_to_string(target.join(CORE_CONFIG_FILE)).unwrap();
|
||||
let document = serde_norway::from_str::<serde_norway::Value>(&migrated).unwrap();
|
||||
let settings = core_config_settings_from_value(&document).unwrap();
|
||||
assert_eq!(settings.host, "127.0.0.1");
|
||||
assert_eq!(settings.port, 9527);
|
||||
assert_eq!(settings.api_keys, vec!["client-key"]);
|
||||
assert_eq!(settings.management_secret_key.as_deref(), Some("user-secret"));
|
||||
assert_eq!(settings.proxy_url, "direct");
|
||||
assert_eq!(document["routing"]["strategy"], "fill-first");
|
||||
assert_eq!(document["routing"]["session-affinity"], true);
|
||||
assert_eq!(document["routing"]["session-affinity-ttl"], "2h");
|
||||
assert_eq!(document["plugins"]["configs"]["local"]["enabled"], true);
|
||||
assert_eq!(document["plugins"]["store-auth"]["registry"], "stored-token");
|
||||
assert_eq!(document["api-keys"]["codex"][0]["name"], "template-upstream");
|
||||
assert!(migrated.starts_with("config-version:"));
|
||||
assert!(document.get("host").is_none());
|
||||
assert!(document.get("port").is_none());
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
}
|
||||
|
||||
@@ -623,16 +639,17 @@ fn v8_core_updates_preserve_partially_migrated_config_and_credentials() {
|
||||
let legacy = "# User configuration\nusage-statistics-enabled: true\nhost: 127.0.0.1\nport: 9527\nauth-dir: ../oauth\napi-keys: [client-key]\nremote-management: {secret-key: user-secret}\nproxy-url: direct\nrequest-retry: 9\ncodex-api-key: [{api-key: upstream-key, base-url: 'https://example.invalid/v1'}]\noauth-model-alias: {codex: [{name: model, alias: custom-model}]}\n";
|
||||
let template = "observability: {usage: {usage-statistics-enabled: false}}\nserver: {host: '', port: 8317}\noauth: {auth-dir: '~/.cli-proxy-api'}\naccess: {api-keys: [your-api-key-1]}\nmanagement: {secret-key: ''}\nrequests: {proxy-url: ''}\nrouting: {retry: {request-retry: 3}}\n";
|
||||
|
||||
for prefix in ["", "config-version: 8\n", "server: {host: 127.0.0.1}\n", "config-version: 8\nserver: {host: 127.0.0.1}\n"] {
|
||||
for prefix in ["", "server: {host: 127.0.0.1}\n"] {
|
||||
let original = format!("{prefix}{legacy}");
|
||||
fs::write(install_dir.join(CORE_CONFIG_FILE), &original).unwrap();
|
||||
for template_prefix in ["config-version: 8\n", ""] {
|
||||
fs::create_dir_all(&staging_dir).unwrap();
|
||||
fs::write(staging_dir.join(CORE_EXAMPLE_CONFIG_FILE), format!("{template_prefix}{template}")).unwrap();
|
||||
migrate_core_config_for_update(&install_dir, &staging_dir).unwrap();
|
||||
let migrated = fs::read_to_string(staging_dir.join(CORE_CONFIG_FILE)).unwrap();
|
||||
overlay_install_dir(&install_dir, &staging_dir).unwrap();
|
||||
fs::write(install_dir.join(CORE_CONFIG_FILE), &original).unwrap();
|
||||
|
||||
let migrated = fs::read_to_string(install_dir.join(CORE_CONFIG_FILE)).unwrap();
|
||||
let document = serde_norway::from_str::<serde_norway::Value>(&migrated).unwrap();
|
||||
let settings = core_config_settings_from_value(&document).unwrap();
|
||||
assert_eq!(settings.auth_dir, "../oauth", "credential directory changed during update");
|
||||
@@ -642,7 +659,12 @@ fn v8_core_updates_preserve_partially_migrated_config_and_credentials() {
|
||||
assert_eq!(settings.management_secret_key.as_deref(), Some("user-secret"));
|
||||
assert_eq!(settings.proxy_url, "direct");
|
||||
assert_eq!(settings.request_retry, 9);
|
||||
assert_eq!(migrated, original, "update must preserve every existing field and comment");
|
||||
assert!(migrated.contains("server:"), "legacy configuration must be lifted into the v8 layout: {migrated}");
|
||||
assert!(document.get("host").is_none(), "{migrated}");
|
||||
assert!(document.get("auth-dir").is_none());
|
||||
assert!(document.get("codex-api-key").is_none());
|
||||
assert_eq!(document["oauth"]["model-alias"]["codex"][0]["alias"], "custom-model");
|
||||
assert_eq!(document["api-keys"]["codex"][0]["keys"][0]["api-key"], "upstream-key");
|
||||
|
||||
let mut gui = GuiConfigFile::default();
|
||||
apply_core_settings_to_gui_config(&mut gui, &settings);
|
||||
@@ -652,8 +674,8 @@ fn v8_core_updates_preserve_partially_migrated_config_and_credentials() {
|
||||
let effective = core_config_settings_from_value(&startup).unwrap();
|
||||
assert_eq!(auth_dir_path_for_core(&effective.auth_dir, &install_dir).unwrap(), root.join("oauth"));
|
||||
assert!(effective.usage_statistics_enabled);
|
||||
assert_eq!(startup["codex-api-key"], document["codex-api-key"]);
|
||||
assert_eq!(startup["oauth-model-alias"], document["oauth-model-alias"]);
|
||||
assert_eq!(startup["api-keys"]["codex"], document["api-keys"]["codex"]);
|
||||
assert_eq!(startup["oauth"]["model-alias"], document["oauth"]["model-alias"]);
|
||||
assert_eq!(fs::read(&credential_path).unwrap(), b"existing-credential");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user