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:
@@ -150,10 +150,7 @@ pub fn get_provider_config_path(provider_id: &str, provider_name: Option<&str>)
|
||||
/// 读取 JSON 配置文件
|
||||
pub fn read_json_file<T: for<'a> Deserialize<'a>>(path: &Path) -> Result<T, AppError> {
|
||||
if !path.exists() {
|
||||
return Err(AppError::Config(format!(
|
||||
"文件不存在: {}",
|
||||
path.display()
|
||||
)));
|
||||
return Err(AppError::Config(format!("文件不存在: {}", path.display())));
|
||||
}
|
||||
|
||||
let content = fs::read_to_string(path).map_err(|e| AppError::io(path, e))?;
|
||||
@@ -168,8 +165,8 @@ pub fn write_json_file<T: Serialize>(path: &Path, data: &T) -> Result<(), AppErr
|
||||
fs::create_dir_all(parent).map_err(|e| AppError::io(parent, e))?;
|
||||
}
|
||||
|
||||
let json = serde_json::to_string_pretty(data)
|
||||
.map_err(|e| AppError::JsonSerialize { source: e })?;
|
||||
let json =
|
||||
serde_json::to_string_pretty(data).map_err(|e| AppError::JsonSerialize { source: e })?;
|
||||
|
||||
atomic_write(path, json.as_bytes())
|
||||
}
|
||||
@@ -204,8 +201,7 @@ pub fn atomic_write(path: &Path, data: &[u8]) -> Result<(), AppError> {
|
||||
tmp.push(format!("{}.tmp.{}", file_name, ts));
|
||||
|
||||
{
|
||||
let mut f =
|
||||
fs::File::create(&tmp).map_err(|e| AppError::io(&tmp, e))?;
|
||||
let mut f = fs::File::create(&tmp).map_err(|e| AppError::io(&tmp, e))?;
|
||||
f.write_all(data).map_err(|e| AppError::io(&tmp, e))?;
|
||||
f.flush().map_err(|e| AppError::io(&tmp, e))?;
|
||||
}
|
||||
@@ -226,11 +222,7 @@ pub fn atomic_write(path: &Path, data: &[u8]) -> Result<(), AppError> {
|
||||
let _ = fs::remove_file(path);
|
||||
}
|
||||
fs::rename(&tmp, path).map_err(|e| AppError::IoContext {
|
||||
context: format!(
|
||||
"原子替换失败: {} -> {}",
|
||||
tmp.display(),
|
||||
path.display()
|
||||
),
|
||||
context: format!("原子替换失败: {} -> {}", tmp.display(), path.display()),
|
||||
source: e,
|
||||
})?;
|
||||
}
|
||||
@@ -238,11 +230,7 @@ pub fn atomic_write(path: &Path, data: &[u8]) -> Result<(), AppError> {
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
fs::rename(&tmp, path).map_err(|e| AppError::IoContext {
|
||||
context: format!(
|
||||
"原子替换失败: {} -> {}",
|
||||
tmp.display(),
|
||||
path.display()
|
||||
),
|
||||
context: format!("原子替换失败: {} -> {}", tmp.display(), path.display()),
|
||||
source: e,
|
||||
})?;
|
||||
}
|
||||
@@ -287,11 +275,7 @@ mod tests {
|
||||
/// 复制文件
|
||||
pub fn copy_file(from: &Path, to: &Path) -> Result<(), AppError> {
|
||||
fs::copy(from, to).map_err(|e| AppError::IoContext {
|
||||
context: format!(
|
||||
"复制文件失败 ({} -> {})",
|
||||
from.display(),
|
||||
to.display()
|
||||
),
|
||||
context: format!("复制文件失败 ({} -> {})", from.display(), to.display()),
|
||||
source: e,
|
||||
})?;
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user