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" ); // 验证数据已持久化到数据库(v3.7.0+ 使用 SQLite 而非 config.json) let db_path = home.join(".cc-switch").join("cc-switch.db"); assert!( db_path.exists(), "importing default config should persist to cc-switch.db" ); } #[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:?}"), } // 使用数据库架构,不再检查 config.json // 失败的导入不应该向数据库写入任何供应商 let providers = state .db .get_all_providers(AppType::Claude.as_str()) .expect("get all providers"); assert!( providers.is_empty(), "failed import should not create any providers in database" ); } #[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 db_path = home.join(".cc-switch").join("cc-switch.db"); assert!( db_path.exists(), "state.save should persist to cc-switch.db 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:?}"), } // 使用数据库架构,检查 MCP 服务器未被写入 let servers = state.db.get_all_mcp_servers().expect("get all mcp servers"); assert!( servers.is_empty(), "failed import should not persist any MCP servers to database" ); } #[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" ); }