Files
cc-switch/src-tauri/tests/mcp_commands.rs
Jason 67aa275599 test: migrate tests to SQLite database architecture
This commit refactors all tests to work with the new database-based
architecture, replacing the previous JSON config approach.

Key changes:
- Add Database export to lib.rs for test access
- Create test helper functions in support.rs:
  - create_test_state(): Creates empty test state with fresh DB
  - create_test_state_with_config(): Migrates JSON config to DB
- Fix environment isolation in provider_service tests:
  - provider_service_switch_missing_provider_returns_error
  - provider_service_switch_codex_missing_auth_returns_error
- Replace ignored export tests with working alternatives:
  - export_sql_writes_to_target_path (tests Database::export_sql)
  - export_sql_returns_error_for_invalid_path (tests error handling)
- Update error type matching to align with current implementation

All tests now:
- Use isolated test environments (test_mutex + reset_test_fs)
- Access data via Database API instead of RwLock<MultiAppConfig>
- Work with SQLite persistence layer
- Pass without environment pollution or race conditions

Fixes test compilation errors after database migration.
2025-11-24 12:24:41 +08:00

242 lines
7.6 KiB
Rust

use std::collections::HashMap;
use std::fs;
use serde_json::json;
use cc_switch_lib::{
get_claude_mcp_path, get_claude_settings_path, import_default_config_test_hook, AppError,
AppType, McpApps, McpServer, McpService, MultiAppConfig,
};
#[path = "support.rs"]
mod support;
use support::{create_test_state_with_config, ensure_test_home, reset_test_fs, test_mutex};
#[test]
fn import_default_config_claude_persists_provider() {
let _guard = test_mutex().lock().expect("acquire test mutex");
reset_test_fs();
let home = ensure_test_home();
let settings_path = get_claude_settings_path();
if let Some(parent) = settings_path.parent() {
fs::create_dir_all(parent).expect("create claude settings dir");
}
let settings = json!({
"env": {
"ANTHROPIC_AUTH_TOKEN": "test-key",
"ANTHROPIC_BASE_URL": "https://api.test"
}
});
fs::write(
&settings_path,
serde_json::to_string_pretty(&settings).expect("serialize settings"),
)
.expect("seed claude settings.json");
let mut config = MultiAppConfig::default();
config.ensure_app(&AppType::Claude);
let state = create_test_state_with_config(&config).expect("create test state");
import_default_config_test_hook(&state, AppType::Claude)
.expect("import default config succeeds");
// 验证内存状态
let providers = state.db.get_all_providers(AppType::Claude.as_str())
.expect("get all providers");
let current_id = state.db.get_current_provider(AppType::Claude.as_str())
.expect("get current provider");
assert_eq!(current_id.as_deref(), Some("default"));
let default_provider = providers.get("default").expect("default provider");
assert_eq!(
default_provider.settings_config, settings,
"default provider should capture live settings"
);
// 验证配置已持久化
let config_path = home.join(".cc-switch").join("config.json");
assert!(
config_path.exists(),
"importing default config should persist config.json"
);
}
#[test]
fn import_default_config_without_live_file_returns_error() {
use support::create_test_state;
let _guard = test_mutex().lock().expect("acquire test mutex");
reset_test_fs();
let home = ensure_test_home();
let state = create_test_state().expect("create test state");
let err = import_default_config_test_hook(&state, AppType::Claude)
.expect_err("missing live file should error");
match err {
AppError::Localized { zh, .. } => assert!(
zh.contains("Claude Code 配置文件不存在"),
"unexpected error message: {zh}"
),
AppError::Message(msg) => assert!(
msg.contains("Claude Code 配置文件不存在"),
"unexpected error message: {msg}"
),
other => panic!("unexpected error variant: {other:?}"),
}
let config_path = home.join(".cc-switch").join("config.json");
assert!(
!config_path.exists(),
"failed import should not create config.json"
);
}
#[test]
fn import_mcp_from_claude_creates_config_and_enables_servers() {
let _guard = test_mutex().lock().expect("acquire test mutex");
reset_test_fs();
let home = ensure_test_home();
let mcp_path = get_claude_mcp_path();
let claude_json = json!({
"mcpServers": {
"echo": {
"type": "stdio",
"command": "echo"
}
}
});
fs::write(
&mcp_path,
serde_json::to_string_pretty(&claude_json).expect("serialize claude mcp"),
)
.expect("seed ~/.claude.json");
let config = MultiAppConfig::default();
let state = create_test_state_with_config(&config).expect("create test state");
let changed = McpService::import_from_claude(&state).expect("import mcp from claude succeeds");
assert!(
changed > 0,
"import should report inserted or normalized entries"
);
let servers = state.db.get_all_mcp_servers()
.expect("get all mcp servers");
let entry = servers
.get("echo")
.expect("server imported into unified structure");
assert!(
entry.apps.claude,
"imported server should have Claude app enabled"
);
let config_path = home.join(".cc-switch").join("config.json");
assert!(
config_path.exists(),
"state.save should persist config.json when changes detected"
);
}
#[test]
fn import_mcp_from_claude_invalid_json_preserves_state() {
use support::create_test_state;
let _guard = test_mutex().lock().expect("acquire test mutex");
reset_test_fs();
let home = ensure_test_home();
let mcp_path = get_claude_mcp_path();
fs::write(&mcp_path, "{\"mcpServers\":") // 不完整 JSON
.expect("seed invalid ~/.claude.json");
let state = create_test_state().expect("create test state");
let err =
McpService::import_from_claude(&state).expect_err("invalid json should bubble up error");
match err {
AppError::McpValidation(msg) => assert!(
msg.contains("解析 ~/.claude.json 失败"),
"unexpected error message: {msg}"
),
other => panic!("unexpected error variant: {other:?}"),
}
let config_path = home.join(".cc-switch").join("config.json");
assert!(
!config_path.exists(),
"failed import should not persist config.json"
);
}
#[test]
fn set_mcp_enabled_for_codex_writes_live_config() {
let _guard = test_mutex().lock().expect("acquire test mutex");
reset_test_fs();
let home = ensure_test_home();
// 创建 Codex 配置目录和文件
let codex_dir = home.join(".codex");
fs::create_dir_all(&codex_dir).expect("create codex dir");
fs::write(
codex_dir.join("auth.json"),
r#"{"OPENAI_API_KEY":"test-key"}"#,
)
.expect("create auth.json");
fs::write(codex_dir.join("config.toml"), "").expect("create empty config.toml");
let mut config = MultiAppConfig::default();
config.ensure_app(&AppType::Codex);
// v3.7.0: 使用统一结构
config.mcp.servers = Some(HashMap::new());
config.mcp.servers.as_mut().unwrap().insert(
"codex-server".into(),
McpServer {
id: "codex-server".to_string(),
name: "Codex Server".to_string(),
server: json!({
"type": "stdio",
"command": "echo"
}),
apps: McpApps {
claude: false,
codex: false, // 初始未启用
gemini: false,
},
description: None,
homepage: None,
docs: None,
tags: Vec::new(),
},
);
let state = create_test_state_with_config(&config).expect("create test state");
// v3.7.0: 使用 toggle_app 替代 set_enabled
McpService::toggle_app(&state, "codex-server", AppType::Codex, true)
.expect("toggle_app should succeed");
let servers = state.db.get_all_mcp_servers()
.expect("get all mcp servers");
let entry = servers
.get("codex-server")
.expect("codex server exists");
assert!(
entry.apps.codex,
"server should have Codex app enabled after toggle"
);
let toml_path = cc_switch_lib::get_codex_config_path();
assert!(
toml_path.exists(),
"enabling server should trigger sync to ~/.codex/config.toml"
);
let toml_text = fs::read_to_string(&toml_path).expect("read codex config");
assert!(
toml_text.contains("codex-server"),
"codex config should include the enabled server definition"
);
}