Dev (#164)
* chore(release): stop publishing legacy Windows update packages * feat(agents): support OpenCode Desktop across platforms Detect CLI and desktop installations on Windows, macOS, and Linux, and add separate launch and restart controls. Closes #156 * fix(codex): align model display names and show total session count - preserve runtime model identity in generated catalogs - count distinct sessions across database and rollout sources - update session count labels in all supported languages * fix(codex): keep config and auth files in sync - generate auth.json for API key configuration - preserve and validate OAuth credentials - include auth.json in transactional updates and rollback - retain compatibility with legacy Codex configuration states * fix(cpa-gui): update version to 0.2.59 in Cargo files * fix(codex): preserve OAuth mode after closing configuration * feat(config): add TLS management and protocol-aware local URLs - add TLS controls to advanced settings - persist certificate and private key paths - restart the core after TLS changes - switch WebUI, API and management URLs between HTTP and HTTPS - synchronize agent configurations and detection with the active protocol * refactor(config): reorganize network, routing, and TLS settings - split network settings and session routing into separate tabs - move TLS configuration into network settings - preserve TLS restart and HTTPS URL synchronization behavior * feat(config): add independent network settings and custom listen IP * fix(navigation): allow access to usage records while core is stopped * refactor(home): show a single API URL based on the configured listen IP * feat(config): add file pickers for TLS certificate and private key
This commit is contained in:
1 parent
c3cd57bd8e
commit
2b9ceb11fb
21 files changed
+1355
-261
No files matched your search
@@ -507,6 +507,51 @@ fn confirmed_network_routing_patch_updates_all_fields_together() {
|
||||
assert!(updated.contains("# unrelated"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn independent_network_modules_only_patch_their_own_fields() {
|
||||
let config = GuiConfigFile {
|
||||
host: "192.168.1.20".to_string(),
|
||||
port: 9527,
|
||||
proxy_url: "socks5://127.0.0.1:7890".to_string(),
|
||||
routing_session_affinity: true,
|
||||
routing_session_affinity_ttl: "2h".to_string(),
|
||||
request_retry: 7,
|
||||
max_retry_credentials: 8,
|
||||
max_retry_interval: 9,
|
||||
streaming_bootstrap_retries: 10,
|
||||
..GuiConfigFile::default()
|
||||
};
|
||||
let input = "host: 127.0.0.1\nport: 8317\nproxy-url: \"\"\nrouting:\n session-affinity: false\n session-affinity-ttl: 1h\nrequest-retry: 1\nmax-retry-credentials: 2\nmax-retry-interval: 3\nstreaming:\n bootstrap-retries: 4\n";
|
||||
|
||||
let network = patch_core_network_endpoint_yaml(input, &config)
|
||||
.unwrap()
|
||||
.expect("network endpoint should change");
|
||||
let network = serde_norway::from_str::<serde_norway::Value>(&network).unwrap();
|
||||
assert_eq!(network["host"], "192.168.1.20");
|
||||
assert_eq!(network["port"], 9527);
|
||||
assert_eq!(network["proxy-url"], "socks5://127.0.0.1:7890");
|
||||
assert_eq!(network["request-retry"], 1);
|
||||
assert_eq!(network["routing"]["session-affinity"], false);
|
||||
|
||||
let retry = patch_core_retry_yaml(input, &config)
|
||||
.unwrap()
|
||||
.expect("retry settings should change");
|
||||
let retry = serde_norway::from_str::<serde_norway::Value>(&retry).unwrap();
|
||||
assert_eq!(retry["request-retry"], 7);
|
||||
assert_eq!(retry["max-retry-credentials"], 8);
|
||||
assert_eq!(retry["max-retry-interval"], 9);
|
||||
assert_eq!(retry["streaming"]["bootstrap-retries"], 10);
|
||||
assert_eq!(retry["host"], "127.0.0.1");
|
||||
|
||||
let routing = patch_core_session_routing_yaml(input, &config)
|
||||
.unwrap()
|
||||
.expect("session routing should change");
|
||||
let routing = serde_norway::from_str::<serde_norway::Value>(&routing).unwrap();
|
||||
assert_eq!(routing["routing"]["session-affinity"], true);
|
||||
assert_eq!(routing["routing"]["session-affinity-ttl"], "2h");
|
||||
assert_eq!(routing["request-retry"], 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn core_config_controls_preserve_comments_and_unrelated_values() {
|
||||
let input = "# Client authentication\napi-keys:\n - old-key\n\n# Plugin runtime\nplugins:\n enabled: false # global switch\n dir: plugins\n\n# Credential routing\nrouting:\n strategy: round-robin # current strategy\n session-affinity: true\n\ndebug: true # untouched\n";
|
||||
@@ -846,6 +891,86 @@ fn optional_core_strings_are_trimmed_and_reject_control_characters() {
|
||||
assert!(normalize_optional_config_string("bad\nvalue".to_string(), "代理 URL").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn core_tls_settings_read_defaults_and_configured_values() {
|
||||
let defaults = serde_norway::from_str::<serde_norway::Value>("host: 127.0.0.1\n").unwrap();
|
||||
let configured = serde_norway::from_str::<serde_norway::Value>(
|
||||
"tls:\n enable: true\n cert: C:/certs/server.crt\n key: C:/certs/server.key\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let defaults = core_tls_settings_from_value(&defaults).unwrap();
|
||||
assert!(!defaults.enabled);
|
||||
assert!(defaults.cert.is_empty());
|
||||
assert!(defaults.key.is_empty());
|
||||
|
||||
let configured = core_tls_settings_from_value(&configured).unwrap();
|
||||
assert!(configured.enabled);
|
||||
assert_eq!(configured.cert, "C:/certs/server.crt");
|
||||
assert_eq!(configured.key, "C:/certs/server.key");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tls_patch_preserves_unrelated_yaml_and_paths_when_disabled() {
|
||||
let input = "# server\nhost: 127.0.0.1\ntls:\n enable: true\n cert: old.crt\n key: old.key\ncustom:\n keep: true\n";
|
||||
let settings = CoreTlsSettings {
|
||||
enabled: false,
|
||||
cert: "new.crt".to_string(),
|
||||
key: "new.key".to_string(),
|
||||
};
|
||||
let patched = patch_core_tls_settings_yaml(input, &settings)
|
||||
.unwrap()
|
||||
.expect("TLS settings should change");
|
||||
let document = serde_norway::from_str::<serde_norway::Value>(&patched).unwrap();
|
||||
|
||||
assert!(patched.contains("# server"));
|
||||
assert_eq!(document["tls"]["enable"], false);
|
||||
assert_eq!(document["tls"]["cert"], "new.crt");
|
||||
assert_eq!(document["tls"]["key"], "new.key");
|
||||
assert_eq!(document["custom"]["keep"], true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enabled_tls_requires_both_paths() {
|
||||
let missing_key = CoreTlsSettings {
|
||||
enabled: true,
|
||||
cert: "server.crt".to_string(),
|
||||
key: String::new(),
|
||||
};
|
||||
assert!(normalize_core_tls_settings(missing_key).is_err());
|
||||
|
||||
let disabled = CoreTlsSettings {
|
||||
enabled: false,
|
||||
cert: " server.crt ".to_string(),
|
||||
key: String::new(),
|
||||
};
|
||||
let disabled = normalize_core_tls_settings(disabled).unwrap();
|
||||
assert_eq!(disabled.cert, "server.crt");
|
||||
assert!(disabled.key.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn core_loopback_origin_uses_the_selected_transport() {
|
||||
assert_eq!(core_loopback_origin(8317, false), "http://127.0.0.1:8317");
|
||||
assert_eq!(core_loopback_origin(9527, true), "https://127.0.0.1:9527");
|
||||
assert!(is_managed_agent_base_url("https://127.0.0.1:9527/v1"));
|
||||
assert!(is_managed_agent_base_url("https://localhost:9527"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn core_origin_uses_connectable_custom_and_ipv6_hosts() {
|
||||
assert_eq!(
|
||||
core_origin("192.168.1.20", 9527, true),
|
||||
"https://192.168.1.20:9527"
|
||||
);
|
||||
assert_eq!(core_origin("0.0.0.0", 8317, false), "http://127.0.0.1:8317");
|
||||
assert_eq!(core_origin("::", 8317, false), "http://[::1]:8317");
|
||||
assert_eq!(
|
||||
core_origin("2001:db8::1", 8317, true),
|
||||
"https://[2001:db8::1]:8317"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn configured_proxy_supports_http_and_socks5_urls() {
|
||||
for proxy_url in ["http://127.0.0.1:8080", "socks5://127.0.0.1:7890"] {
|
||||
|
||||
Reference in new issue
Block a user