From 7898096de31e642b897008dd168405f6d009c317 Mon Sep 17 00:00:00 2001 From: Jason Date: Sun, 22 Feb 2026 12:50:05 +0800 Subject: [PATCH] refactor(cleanup): remove dead code and redundant MCP sync after partial-merge refactor - Remove ConfigService legacy full-overwrite sync methods (~150 lines) - Remove redundant McpService::sync_all_enabled from switch_normal - Switch proxy fallback recovery from write_live_snapshot to write_live_partial - Remove dead ProviderService::write_gemini_live wrapper - Update tests to reflect partial-merge behavior (MCP preserved, not re-synced) --- src-tauri/src/services/config.rs | 150 -------------- src-tauri/src/services/provider/mod.rs | 11 +- src-tauri/src/services/proxy.rs | 4 +- src-tauri/tests/import_export_sync.rs | 272 +------------------------ src-tauri/tests/provider_commands.rs | 7 +- src-tauri/tests/provider_service.rs | 12 +- 6 files changed, 15 insertions(+), 441 deletions(-) diff --git a/src-tauri/src/services/config.rs b/src-tauri/src/services/config.rs index d354cccb..d2c8da0c 100644 --- a/src-tauri/src/services/config.rs +++ b/src-tauri/src/services/config.rs @@ -1,9 +1,5 @@ -use super::provider::{sanitize_claude_settings_for_live, ProviderService}; -use crate::app_config::{AppType, MultiAppConfig}; use crate::error::AppError; -use crate::provider::Provider; use chrono::Utc; -use serde_json::Value; use std::fs; use std::path::Path; @@ -82,150 +78,4 @@ impl ConfigService { Ok(()) } - - /// 同步当前供应商到对应的 live 配置。 - pub fn sync_current_providers_to_live(config: &mut MultiAppConfig) -> Result<(), AppError> { - Self::sync_current_provider_for_app(config, &AppType::Claude)?; - Self::sync_current_provider_for_app(config, &AppType::Codex)?; - Self::sync_current_provider_for_app(config, &AppType::Gemini)?; - Ok(()) - } - - fn sync_current_provider_for_app( - config: &mut MultiAppConfig, - app_type: &AppType, - ) -> Result<(), AppError> { - let (current_id, provider) = { - let manager = match config.get_manager(app_type) { - Some(manager) => manager, - None => return Ok(()), - }; - - if manager.current.is_empty() { - return Ok(()); - } - - let current_id = manager.current.clone(); - let provider = match manager.providers.get(¤t_id) { - Some(provider) => provider.clone(), - None => { - log::warn!( - "当前应用 {app_type:?} 的供应商 {current_id} 不存在,跳过 live 同步" - ); - return Ok(()); - } - }; - (current_id, provider) - }; - - match app_type { - AppType::Codex => Self::sync_codex_live(config, ¤t_id, &provider)?, - AppType::Claude => Self::sync_claude_live(config, ¤t_id, &provider)?, - AppType::Gemini => Self::sync_gemini_live(config, ¤t_id, &provider)?, - AppType::OpenCode => { - // OpenCode uses additive mode, no live sync needed - // OpenCode providers are managed directly in the config file - } - AppType::OpenClaw => { - // OpenClaw uses additive mode, no live sync needed - // OpenClaw providers are managed directly in the config file - } - } - - Ok(()) - } - - fn sync_codex_live( - config: &mut MultiAppConfig, - provider_id: &str, - provider: &Provider, - ) -> Result<(), AppError> { - let settings = provider.settings_config.as_object().ok_or_else(|| { - AppError::Config(format!("供应商 {provider_id} 的 Codex 配置必须是对象")) - })?; - let auth = settings.get("auth").ok_or_else(|| { - AppError::Config(format!("供应商 {provider_id} 的 Codex 配置缺少 auth 字段")) - })?; - if !auth.is_object() { - return Err(AppError::Config(format!( - "供应商 {provider_id} 的 Codex auth 配置必须是 JSON 对象" - ))); - } - let cfg_text = settings.get("config").and_then(Value::as_str); - - crate::codex_config::write_codex_live_atomic(auth, cfg_text)?; - // 注意:MCP 同步在 v3.7.0 中已通过 McpService 进行,不再在此调用 - // sync_enabled_to_codex 使用旧的 config.mcp.codex 结构,在新架构中为空 - // MCP 的启用/禁用应通过 McpService::toggle_app 进行 - - let cfg_text_after = crate::codex_config::read_and_validate_codex_config_text()?; - if let Some(manager) = config.get_manager_mut(&AppType::Codex) { - if let Some(target) = manager.providers.get_mut(provider_id) { - if let Some(obj) = target.settings_config.as_object_mut() { - obj.insert( - "config".to_string(), - serde_json::Value::String(cfg_text_after), - ); - } - } - } - - Ok(()) - } - - fn sync_claude_live( - config: &mut MultiAppConfig, - provider_id: &str, - provider: &Provider, - ) -> Result<(), AppError> { - use crate::config::{read_json_file, write_json_file}; - - let settings_path = crate::config::get_claude_settings_path(); - if let Some(parent) = settings_path.parent() { - fs::create_dir_all(parent).map_err(|e| AppError::io(parent, e))?; - } - - let settings = sanitize_claude_settings_for_live(&provider.settings_config); - write_json_file(&settings_path, &settings)?; - - let live_after = read_json_file::(&settings_path)?; - if let Some(manager) = config.get_manager_mut(&AppType::Claude) { - if let Some(target) = manager.providers.get_mut(provider_id) { - target.settings_config = live_after; - } - } - - Ok(()) - } - - fn sync_gemini_live( - config: &mut MultiAppConfig, - provider_id: &str, - provider: &Provider, - ) -> Result<(), AppError> { - use crate::gemini_config::{env_to_json, read_gemini_env}; - - ProviderService::write_gemini_live(provider)?; - - // 读回实际写入的内容并更新到配置中(包含 settings.json) - let live_after_env = read_gemini_env()?; - let settings_path = crate::gemini_config::get_gemini_settings_path(); - let live_after_config = if settings_path.exists() { - crate::config::read_json_file(&settings_path)? - } else { - serde_json::json!({}) - }; - let mut live_after = env_to_json(&live_after_env); - if let Some(obj) = live_after.as_object_mut() { - obj.insert("config".to_string(), live_after_config); - } - - if let Some(manager) = config.get_manager_mut(&AppType::Gemini) { - if let Some(target) = manager.providers.get_mut(provider_id) { - target.settings_config = live_after; - } - } - - Ok(()) - } } diff --git a/src-tauri/src/services/provider/mod.rs b/src-tauri/src/services/provider/mod.rs index 448d896f..0b50bc0d 100644 --- a/src-tauri/src/services/provider/mod.rs +++ b/src-tauri/src/services/provider/mod.rs @@ -27,12 +27,12 @@ pub use live::{ // Internal re-exports (pub(crate)) pub(crate) use live::sanitize_claude_settings_for_live; -pub(crate) use live::write_live_snapshot; +pub(crate) use live::write_live_partial; // Internal re-exports use live::{ backfill_key_fields, remove_openclaw_provider_from_live, remove_opencode_provider_from_live, - write_gemini_live, write_live_partial, + write_live_snapshot, }; use usage::validate_usage_script; @@ -567,9 +567,6 @@ impl ProviderService { // Sync to live (partial merge: only key fields, preserving user settings) write_live_partial(&app_type, provider)?; - // Sync MCP - McpService::sync_all_enabled(state)?; - Ok(result) } @@ -685,10 +682,6 @@ impl ProviderService { .await } - pub(crate) fn write_gemini_live(provider: &Provider) -> Result<(), AppError> { - write_gemini_live(provider) - } - fn validate_provider_settings(app_type: &AppType, provider: &Provider) -> Result<(), AppError> { match app_type { AppType::Claude => { diff --git a/src-tauri/src/services/proxy.rs b/src-tauri/src/services/proxy.rs index 5325f4af..3dc0412d 100644 --- a/src-tauri/src/services/proxy.rs +++ b/src-tauri/src/services/proxy.rs @@ -8,7 +8,7 @@ use crate::database::Database; use crate::provider::Provider; use crate::proxy::server::ProxyServer; use crate::proxy::types::*; -use crate::services::provider::write_live_snapshot; +use crate::services::provider::write_live_partial; use serde_json::{json, Value}; use std::str::FromStr; use std::sync::Arc; @@ -1266,7 +1266,7 @@ impl ProxyService { return Ok(false); }; - write_live_snapshot(app_type, provider) + write_live_partial(app_type, provider) .map_err(|e| format!("写入 {app_type:?} Live 配置失败: {e}"))?; Ok(true) diff --git a/src-tauri/tests/import_export_sync.rs b/src-tauri/tests/import_export_sync.rs index 24a1667f..c9738c7f 100644 --- a/src-tauri/tests/import_export_sync.rs +++ b/src-tauri/tests/import_export_sync.rs @@ -2,10 +2,7 @@ use serde_json::json; use std::fs; use std::path::PathBuf; -use cc_switch_lib::{ - get_claude_settings_path, read_json_file, AppError, AppType, ConfigService, MultiAppConfig, - Provider, ProviderMeta, -}; +use cc_switch_lib::{AppError, AppType, ConfigService, MultiAppConfig, Provider}; #[path = "support.rs"] mod support; @@ -13,132 +10,6 @@ use support::{ create_test_state, create_test_state_with_config, ensure_test_home, reset_test_fs, test_mutex, }; -#[test] -fn sync_claude_provider_writes_live_settings() { - let _guard = test_mutex().lock().expect("acquire test mutex"); - reset_test_fs(); - let home = ensure_test_home(); - - let mut config = MultiAppConfig::default(); - let provider_config = json!({ - "env": { - "ANTHROPIC_AUTH_TOKEN": "test-key", - "ANTHROPIC_BASE_URL": "https://api.test" - }, - "ui": { - "displayName": "Test Provider" - } - }); - - let provider = Provider::with_id( - "prov-1".to_string(), - "Test Claude".to_string(), - provider_config.clone(), - None, - ); - - let manager = config - .get_manager_mut(&AppType::Claude) - .expect("claude manager"); - manager.providers.insert("prov-1".to_string(), provider); - manager.current = "prov-1".to_string(); - - ConfigService::sync_current_providers_to_live(&mut config).expect("sync live settings"); - - let settings_path = get_claude_settings_path(); - assert!( - settings_path.exists(), - "live settings should be written to {}", - settings_path.display() - ); - - let live_value: serde_json::Value = read_json_file(&settings_path).expect("read live file"); - assert_eq!(live_value, provider_config); - - // 确认 SSOT 中的供应商也同步了最新内容 - let updated = config - .get_manager(&AppType::Claude) - .and_then(|m| m.providers.get("prov-1")) - .expect("provider in config"); - assert_eq!(updated.settings_config, provider_config); - - // 额外确认写入位置位于测试 HOME 下 - assert!( - settings_path.starts_with(home), - "settings path {settings_path:?} should reside under test HOME {home:?}" - ); -} - -#[test] -fn sync_codex_provider_writes_auth_and_config() { - let _guard = test_mutex().lock().expect("acquire test mutex"); - reset_test_fs(); - - let mut config = MultiAppConfig::default(); - - // 注意:v3.7.0 后 MCP 同步由 McpService 独立处理,不再通过 provider 切换触发 - // 此测试仅验证 auth.json 和 config.toml 基础配置的写入 - - let provider_config = json!({ - "auth": { - "OPENAI_API_KEY": "codex-key" - }, - "config": r#"base_url = "https://codex.test""# - }); - - let provider = Provider::with_id( - "codex-1".to_string(), - "Codex Test".to_string(), - provider_config.clone(), - None, - ); - - let manager = config - .get_manager_mut(&AppType::Codex) - .expect("codex manager"); - manager.providers.insert("codex-1".to_string(), provider); - manager.current = "codex-1".to_string(); - - ConfigService::sync_current_providers_to_live(&mut config).expect("sync codex live"); - - let auth_path = cc_switch_lib::get_codex_auth_path(); - let config_path = cc_switch_lib::get_codex_config_path(); - - assert!( - auth_path.exists(), - "auth.json should exist at {}", - auth_path.display() - ); - assert!( - config_path.exists(), - "config.toml should exist at {}", - config_path.display() - ); - - let auth_value: serde_json::Value = read_json_file(&auth_path).expect("read auth"); - assert_eq!( - auth_value, - provider_config.get("auth").cloned().expect("auth object") - ); - - let toml_text = fs::read_to_string(&config_path).expect("read config.toml"); - // 验证基础配置正确写入 - assert!( - toml_text.contains("base_url"), - "config.toml should contain base_url from provider config" - ); - - // 当前供应商应同步最新 config 文本 - let manager = config.get_manager(&AppType::Codex).expect("codex manager"); - let synced = manager.providers.get("codex-1").expect("codex provider"); - let synced_cfg = synced - .settings_config - .get("config") - .and_then(|v| v.as_str()) - .expect("config string"); - assert_eq!(synced_cfg, toml_text); -} - #[test] fn sync_enabled_to_codex_writes_enabled_servers() { let _guard = test_mutex().lock().expect("acquire test mutex"); @@ -338,46 +209,6 @@ fn sync_enabled_to_codex_returns_error_on_invalid_toml() { } } -#[test] -fn sync_codex_provider_missing_auth_returns_error() { - let _guard = test_mutex().lock().expect("acquire test mutex"); - reset_test_fs(); - - let mut config = MultiAppConfig::default(); - let provider = Provider::with_id( - "codex-missing-auth".to_string(), - "No Auth".to_string(), - json!({ - "config": "model = \"test\"" - }), - None, - ); - let manager = config - .get_manager_mut(&AppType::Codex) - .expect("codex manager"); - manager.providers.insert(provider.id.clone(), provider); - manager.current = "codex-missing-auth".to_string(); - - let err = ConfigService::sync_current_providers_to_live(&mut config) - .expect_err("sync should fail when auth missing"); - match err { - cc_switch_lib::AppError::Config(msg) => { - assert!(msg.contains("auth"), "error message should mention auth"); - } - other => panic!("unexpected error variant: {other:?}"), - } - - // 确认未产生任何 live 配置文件 - assert!( - !cc_switch_lib::get_codex_auth_path().exists(), - "auth.json should not be created on failure" - ); - assert!( - !cc_switch_lib::get_codex_config_path().exists(), - "config.toml should not be created on failure" - ); -} - #[test] fn write_codex_live_atomic_persists_auth_and_config() { let _guard = test_mutex().lock().expect("acquire test mutex"); @@ -816,107 +647,6 @@ fn create_backup_retains_only_latest_entries() { ); } -#[test] -fn sync_gemini_packycode_sets_security_selected_type() { - let _guard = test_mutex().lock().expect("acquire test mutex"); - reset_test_fs(); - let home = ensure_test_home(); - - let mut config = MultiAppConfig::default(); - { - let manager = config - .get_manager_mut(&AppType::Gemini) - .expect("gemini manager"); - manager.current = "packy-1".to_string(); - manager.providers.insert( - "packy-1".to_string(), - Provider::with_id( - "packy-1".to_string(), - "PackyCode".to_string(), - json!({ - "env": { - "GEMINI_API_KEY": "pk-key", - "GOOGLE_GEMINI_BASE_URL": "https://api-slb.packyapi.com" - } - }), - Some("https://www.packyapi.com".to_string()), - ), - ); - } - - ConfigService::sync_current_providers_to_live(&mut config) - .expect("syncing gemini live should succeed"); - - // security field is written to ~/.gemini/settings.json, not ~/.cc-switch/settings.json - let gemini_settings = home.join(".gemini").join("settings.json"); - assert!( - gemini_settings.exists(), - "Gemini settings.json should exist at {}", - gemini_settings.display() - ); - - let raw = std::fs::read_to_string(&gemini_settings).expect("read gemini settings.json"); - let value: serde_json::Value = serde_json::from_str(&raw).expect("parse gemini settings.json"); - assert_eq!( - value - .pointer("/security/auth/selectedType") - .and_then(|v| v.as_str()), - Some("gemini-api-key"), - "syncing PackyCode Gemini should enforce security.auth.selectedType in Gemini settings" - ); -} - -#[test] -fn sync_gemini_google_official_sets_oauth_security() { - let _guard = test_mutex().lock().expect("acquire test mutex"); - reset_test_fs(); - let home = ensure_test_home(); - - let mut config = MultiAppConfig::default(); - { - let manager = config - .get_manager_mut(&AppType::Gemini) - .expect("gemini manager"); - manager.current = "google-official".to_string(); - let mut provider = Provider::with_id( - "google-official".to_string(), - "Google".to_string(), - json!({ - "env": {} - }), - Some("https://ai.google.dev".to_string()), - ); - provider.meta = Some(ProviderMeta { - partner_promotion_key: Some("google-official".to_string()), - ..ProviderMeta::default() - }); - manager - .providers - .insert("google-official".to_string(), provider); - } - - ConfigService::sync_current_providers_to_live(&mut config) - .expect("syncing google official gemini should succeed"); - - // security field is written to ~/.gemini/settings.json, not ~/.cc-switch/settings.json - let gemini_settings = home.join(".gemini").join("settings.json"); - assert!( - gemini_settings.exists(), - "Gemini settings should exist at {}", - gemini_settings.display() - ); - let gemini_raw = std::fs::read_to_string(&gemini_settings).expect("read gemini settings"); - let gemini_value: serde_json::Value = - serde_json::from_str(&gemini_raw).expect("parse gemini settings json"); - assert_eq!( - gemini_value - .pointer("/security/auth/selectedType") - .and_then(|v| v.as_str()), - Some("oauth-personal"), - "Gemini settings should record oauth-personal for Google Official" - ); -} - #[test] fn export_sql_writes_to_target_path() { let _guard = test_mutex().lock().expect("acquire test mutex"); diff --git a/src-tauri/tests/provider_commands.rs b/src-tauri/tests/provider_commands.rs index 46fcb60c..148c00db 100644 --- a/src-tauri/tests/provider_commands.rs +++ b/src-tauri/tests/provider_commands.rs @@ -100,9 +100,12 @@ command = "say" ); let config_text = std::fs::read_to_string(get_codex_config_path()).expect("read config.toml"); + // With partial merge, only key fields (model, provider, model_providers) are + // merged into config.toml. The existing MCP section should be preserved. + // MCP sync from DB is handled separately (at startup or explicit sync). assert!( - config_text.contains("mcp_servers.echo-server"), - "config.toml should contain synced MCP servers" + config_text.contains("mcp_servers.legacy"), + "config.toml should preserve existing MCP servers after partial merge" ); let current_id = app_state diff --git a/src-tauri/tests/provider_service.rs b/src-tauri/tests/provider_service.rs index 4d02615d..ad664253 100644 --- a/src-tauri/tests/provider_service.rs +++ b/src-tauri/tests/provider_service.rs @@ -112,9 +112,12 @@ command = "say" let config_text = std::fs::read_to_string(cc_switch_lib::get_codex_config_path()).expect("read config.toml"); + // With partial merge, only key fields (model, provider, model_providers) are + // merged into config.toml. The existing MCP section should be preserved. + // MCP sync from DB is handled separately (at startup or explicit sync). assert!( - config_text.contains("mcp_servers.echo-server"), - "config.toml should contain synced MCP servers" + config_text.contains("mcp_servers.legacy"), + "config.toml should preserve existing MCP servers after partial merge" ); let current_id = state @@ -143,11 +146,6 @@ command = "say" new_config_text.contains("mcp_servers.latest"), "provider config should contain original MCP servers" ); - // live 文件额外包含同步的 MCP 服务器 - assert!( - config_text.contains("mcp_servers.echo-server"), - "live config should include synced MCP servers" - ); let legacy = providers .get("old-provider")