refactor(backend): phase 4 - add test hooks and extend service layer

- Extract internal functions in commands/mcp.rs and commands/provider.rs
  to enable unit testing without Tauri context
- Add test hooks: set_mcp_enabled_test_hook, import_mcp_from_claude_test_hook,
  import_mcp_from_codex_test_hook, import_default_config_test_hook
- Migrate error types from String to AppError for precise error matching in tests
- Extend ProviderService with delete() method to unify Codex/Claude cleanup logic
- Add comprehensive test coverage:
  - tests/mcp_commands.rs: command-level tests for MCP operations
  - tests/provider_service.rs: service-level tests for switch/delete operations
- Run cargo fmt to fix formatting issues (EOF newlines)
- Update BACKEND_REFACTOR_PLAN.md to mark phase 3 complete
This commit is contained in:
Jason
2025-10-28 11:58:57 +08:00
parent c2e8855a0f
commit 7e27f88154
20 changed files with 1005 additions and 415 deletions

View File

@@ -7,6 +7,7 @@ use tauri::State;
use crate::app_config::AppType;
use crate::claude_mcp;
use crate::error::AppError;
use crate::mcp;
use crate::store::AppState;
@@ -159,15 +160,8 @@ pub async fn set_mcp_enabled(
id: String,
enabled: bool,
) -> Result<bool, String> {
let mut cfg = state
.config
.lock()
.map_err(|e| format!("获取锁失败: {}", e))?;
let app_ty = AppType::from(app.as_deref().unwrap_or("claude"));
let changed = mcp::set_enabled_and_sync_for(&mut cfg, &app_ty, &id, enabled)?;
drop(cfg);
state.save()?;
Ok(changed)
set_mcp_enabled_internal(&*state, app_ty, &id, enabled).map_err(Into::into)
}
/// 手动同步:将启用的 MCP 投影到 ~/.claude.json
@@ -207,10 +201,40 @@ pub async fn sync_enabled_mcp_to_codex(state: State<'_, AppState>) -> Result<boo
/// 从 ~/.claude.json 导入 MCP 定义到 config.json
#[tauri::command]
pub async fn import_mcp_from_claude(state: State<'_, AppState>) -> Result<usize, String> {
let mut cfg = state
.config
.lock()
.map_err(|e| format!("获取锁失败: {}", e))?;
import_mcp_from_claude_internal(&*state).map_err(Into::into)
}
/// 从 ~/.codex/config.toml 导入 MCP 定义到 config.json
#[tauri::command]
pub async fn import_mcp_from_codex(state: State<'_, AppState>) -> Result<usize, String> {
import_mcp_from_codex_internal(&*state).map_err(Into::into)
}
fn set_mcp_enabled_internal(
state: &AppState,
app_ty: AppType,
id: &str,
enabled: bool,
) -> Result<bool, AppError> {
let mut cfg = state.config.lock()?;
let changed = mcp::set_enabled_and_sync_for(&mut cfg, &app_ty, id, enabled)?;
drop(cfg);
state.save()?;
Ok(changed)
}
#[doc(hidden)]
pub fn set_mcp_enabled_test_hook(
state: &AppState,
app_ty: AppType,
id: &str,
enabled: bool,
) -> Result<bool, AppError> {
set_mcp_enabled_internal(state, app_ty, id, enabled)
}
fn import_mcp_from_claude_internal(state: &AppState) -> Result<usize, AppError> {
let mut cfg = state.config.lock()?;
let changed = mcp::import_from_claude(&mut cfg)?;
drop(cfg);
if changed > 0 {
@@ -219,13 +243,13 @@ pub async fn import_mcp_from_claude(state: State<'_, AppState>) -> Result<usize,
Ok(changed)
}
/// 从 ~/.codex/config.toml 导入 MCP 定义到 config.json
#[tauri::command]
pub async fn import_mcp_from_codex(state: State<'_, AppState>) -> Result<usize, String> {
let mut cfg = state
.config
.lock()
.map_err(|e| format!("获取锁失败: {}", e))?;
#[doc(hidden)]
pub fn import_mcp_from_claude_test_hook(state: &AppState) -> Result<usize, AppError> {
import_mcp_from_claude_internal(state)
}
fn import_mcp_from_codex_internal(state: &AppState) -> Result<usize, AppError> {
let mut cfg = state.config.lock()?;
let changed = mcp::import_from_codex(&mut cfg)?;
drop(cfg);
if changed > 0 {
@@ -233,3 +257,8 @@ pub async fn import_mcp_from_codex(state: State<'_, AppState>) -> Result<usize,
}
Ok(changed)
}
#[doc(hidden)]
pub fn import_mcp_from_codex_test_hook(state: &AppState) -> Result<usize, AppError> {
import_mcp_from_codex_internal(state)
}