fix: sync gateway pairing persistence and proxy null clears

This commit is contained in:
Chummy 2026-02-19 17:51:08 +08:00
parent f1ca73d3d2
commit 916c0c823b
4 changed files with 80 additions and 14 deletions

View file

@ -224,6 +224,7 @@ fn has_supervised_channels(config: &Config) -> bool {
irc, irc,
lark, lark,
dingtalk, dingtalk,
linq,
qq, qq,
.. ..
} = &config.channels_config; } = &config.channels_config;
@ -240,6 +241,7 @@ fn has_supervised_channels(config: &Config) -> bool {
|| irc.is_some() || irc.is_some()
|| lark.is_some() || lark.is_some()
|| dingtalk.is_some() || dingtalk.is_some()
|| linq.is_some()
|| qq.is_some() || qq.is_some()
} }

View file

@ -653,11 +653,16 @@ async fn persist_pairing_tokens(config: Arc<Mutex<Config>>, pairing: &PairingGua
let paired_tokens = pairing.tokens(); let paired_tokens = pairing.tokens();
// This is needed because parking_lot's guard is not Send so we clone the inner // This is needed because parking_lot's guard is not Send so we clone the inner
// this should be removed once async mutexes are used everywhere // this should be removed once async mutexes are used everywhere
let mut cfg = { config.lock().clone() }; let mut updated_cfg = { config.lock().clone() };
cfg.gateway.paired_tokens = paired_tokens; updated_cfg.gateway.paired_tokens = paired_tokens;
cfg.save() updated_cfg
.save()
.await .await
.context("Failed to persist paired tokens to config.toml") .context("Failed to persist paired tokens to config.toml")?;
// Keep shared runtime config in sync with persisted tokens.
*config.lock() = updated_cfg;
Ok(())
} }
/// Webhook request body /// Webhook request body
@ -1410,7 +1415,9 @@ mod tests {
assert!(guard.is_authenticated(&token)); assert!(guard.is_authenticated(&token));
let shared_config = Arc::new(Mutex::new(config)); let shared_config = Arc::new(Mutex::new(config));
persist_pairing_tokens(shared_config, &guard).await.unwrap(); persist_pairing_tokens(shared_config.clone(), &guard)
.await
.unwrap();
let saved = tokio::fs::read_to_string(config_path).await.unwrap(); let saved = tokio::fs::read_to_string(config_path).await.unwrap();
let parsed: Config = toml::from_str(&saved).unwrap(); let parsed: Config = toml::from_str(&saved).unwrap();
@ -1418,6 +1425,10 @@ mod tests {
let persisted = &parsed.gateway.paired_tokens[0]; let persisted = &parsed.gateway.paired_tokens[0];
assert_eq!(persisted.len(), 64); assert_eq!(persisted.len(), 64);
assert!(persisted.chars().all(|c| c.is_ascii_hexdigit())); assert!(persisted.chars().all(|c| c.is_ascii_hexdigit()));
let in_memory = shared_config.lock();
assert_eq!(in_memory.gateway.paired_tokens.len(), 1);
assert_eq!(&in_memory.gateway.paired_tokens[0], persisted);
} }
#[test] #[test]

View file

@ -74,6 +74,7 @@ fn has_launchable_channels(channels: &ChannelsConfig) -> bool {
irc, irc,
lark, lark,
dingtalk, dingtalk,
linq,
qq, qq,
.. ..
} = channels; } = channels;
@ -90,6 +91,7 @@ fn has_launchable_channels(channels: &ChannelsConfig) -> bool {
|| irc.is_some() || irc.is_some()
|| lark.is_some() || lark.is_some()
|| dingtalk.is_some() || dingtalk.is_some()
|| linq.is_some()
|| qq.is_some() || qq.is_some()
} }

View file

@ -189,20 +189,41 @@ impl ProxyConfigTool {
})?; })?;
} }
if let MaybeSet::Set(update) = Self::parse_optional_string_update(args, "http_proxy")? { match Self::parse_optional_string_update(args, "http_proxy")? {
MaybeSet::Set(update) => {
proxy.http_proxy = Some(update); proxy.http_proxy = Some(update);
touched_proxy_url = true; touched_proxy_url = true;
} }
MaybeSet::Null => {
proxy.http_proxy = None;
touched_proxy_url = true;
}
MaybeSet::Unset => {}
}
if let MaybeSet::Set(update) = Self::parse_optional_string_update(args, "https_proxy")? { match Self::parse_optional_string_update(args, "https_proxy")? {
MaybeSet::Set(update) => {
proxy.https_proxy = Some(update); proxy.https_proxy = Some(update);
touched_proxy_url = true; touched_proxy_url = true;
} }
MaybeSet::Null => {
proxy.https_proxy = None;
touched_proxy_url = true;
}
MaybeSet::Unset => {}
}
if let MaybeSet::Set(update) = Self::parse_optional_string_update(args, "all_proxy")? { match Self::parse_optional_string_update(args, "all_proxy")? {
MaybeSet::Set(update) => {
proxy.all_proxy = Some(update); proxy.all_proxy = Some(update);
touched_proxy_url = true; touched_proxy_url = true;
} }
MaybeSet::Null => {
proxy.all_proxy = None;
touched_proxy_url = true;
}
MaybeSet::Unset => {}
}
if let Some(no_proxy_raw) = args.get("no_proxy") { if let Some(no_proxy_raw) = args.get("no_proxy") {
proxy.no_proxy = Self::parse_string_list(no_proxy_raw, "no_proxy")?; proxy.no_proxy = Self::parse_string_list(no_proxy_raw, "no_proxy")?;
@ -494,4 +515,34 @@ mod tests {
assert!(get_result.output.contains("provider.openai")); assert!(get_result.output.contains("provider.openai"));
assert!(get_result.output.contains("services")); assert!(get_result.output.contains("services"));
} }
#[tokio::test]
async fn set_null_proxy_url_clears_existing_value() {
let tmp = TempDir::new().unwrap();
let tool = ProxyConfigTool::new(test_config(&tmp).await, test_security());
let set_result = tool
.execute(json!({
"action": "set",
"http_proxy": "http://127.0.0.1:7890"
}))
.await
.unwrap();
assert!(set_result.success, "{:?}", set_result.error);
let clear_result = tool
.execute(json!({
"action": "set",
"http_proxy": null
}))
.await
.unwrap();
assert!(clear_result.success, "{:?}", clear_result.error);
let get_result = tool.execute(json!({"action": "get"})).await.unwrap();
assert!(get_result.success);
let parsed: Value = serde_json::from_str(&get_result.output).unwrap();
assert!(parsed["proxy"]["http_proxy"].is_null());
assert!(parsed["runtime_proxy"]["http_proxy"].is_null());
}
} }