mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-04-18 17:33:08 +08:00
Merge feat/sqlite-migration: add database schema migration system
This merge brings the SQLite migration system from feat/sqlite-migration branch: ## New Features - Schema version control with SCHEMA_VERSION constant - Automatic migration of missing columns for providers table - Dry-run validation mode for schema compatibility checks - JSON→SQLite migration feature gate (CC_SWITCH_ENABLE_JSON_DB_MIGRATION) - Settings reload mechanism after imports ## Test Updates - Updated tests to use SQLite database instead of config.json - Removed obsolete import_config_from_path tests (replaced by db.import_sql) - Fixed MCP tests to use unified McpServer structure (v3.7.0+) - Updated provider switch tests to reflect no-backfill behavior - Adjusted error type matching for new error variants
This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
use std::{collections::HashMap, fs, sync::RwLock};
|
||||
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,
|
||||
AppState, AppType, McpApps, McpServer, McpService, MultiAppConfig,
|
||||
AppType, McpApps, McpServer, McpService, MultiAppConfig,
|
||||
};
|
||||
|
||||
#[path = "support.rs"]
|
||||
mod support;
|
||||
use support::{ensure_test_home, reset_test_fs, test_mutex};
|
||||
use support::{create_test_state_with_config, ensure_test_home, reset_test_fs, test_mutex};
|
||||
|
||||
#[test]
|
||||
fn import_default_config_claude_persists_provider() {
|
||||
@@ -35,43 +36,40 @@ fn import_default_config_claude_persists_provider() {
|
||||
|
||||
let mut config = MultiAppConfig::default();
|
||||
config.ensure_app(&AppType::Claude);
|
||||
let state = AppState {
|
||||
config: RwLock::new(config),
|
||||
};
|
||||
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 guard = state.config.read().expect("lock config");
|
||||
let manager = guard
|
||||
.get_manager(&AppType::Claude)
|
||||
.expect("claude manager present");
|
||||
assert_eq!(manager.current, "default");
|
||||
let default_provider = manager.providers.get("default").expect("default provider");
|
||||
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"
|
||||
);
|
||||
drop(guard);
|
||||
|
||||
// 验证配置已持久化
|
||||
let config_path = home.join(".cc-switch").join("config.json");
|
||||
// 验证数据已持久化到数据库(v3.7.0+ 使用 SQLite 而非 config.json)
|
||||
let db_path = home.join(".cc-switch").join("cc-switch.db");
|
||||
assert!(
|
||||
config_path.exists(),
|
||||
"importing default config should persist config.json"
|
||||
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 _home = ensure_test_home();
|
||||
|
||||
let state = AppState {
|
||||
config: RwLock::new(MultiAppConfig::default()),
|
||||
};
|
||||
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");
|
||||
@@ -87,10 +85,13 @@ fn import_default_config_without_live_file_returns_error() {
|
||||
other => panic!("unexpected error variant: {other:?}"),
|
||||
}
|
||||
|
||||
let config_path = home.join(".cc-switch").join("config.json");
|
||||
// 使用数据库架构,不再检查 config.json
|
||||
// 失败的导入不应该向数据库写入任何供应商
|
||||
let providers = state.db.get_all_providers(AppType::Claude.as_str())
|
||||
.expect("get all providers");
|
||||
assert!(
|
||||
!config_path.exists(),
|
||||
"failed import should not create config.json"
|
||||
providers.is_empty(),
|
||||
"failed import should not create any providers in database"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -115,9 +116,8 @@ fn import_mcp_from_claude_creates_config_and_enables_servers() {
|
||||
)
|
||||
.expect("seed ~/.claude.json");
|
||||
|
||||
let state = AppState {
|
||||
config: RwLock::new(MultiAppConfig::default()),
|
||||
};
|
||||
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!(
|
||||
@@ -125,13 +125,8 @@ fn import_mcp_from_claude_creates_config_and_enables_servers() {
|
||||
"import should report inserted or normalized entries"
|
||||
);
|
||||
|
||||
let guard = state.config.read().expect("lock config");
|
||||
// v3.7.0: 检查统一结构
|
||||
let servers = guard
|
||||
.mcp
|
||||
.servers
|
||||
.as_ref()
|
||||
.expect("unified servers should exist");
|
||||
let servers = state.db.get_all_mcp_servers()
|
||||
.expect("get all mcp servers");
|
||||
let entry = servers
|
||||
.get("echo")
|
||||
.expect("server imported into unified structure");
|
||||
@@ -139,28 +134,28 @@ fn import_mcp_from_claude_creates_config_and_enables_servers() {
|
||||
entry.apps.claude,
|
||||
"imported server should have Claude app enabled"
|
||||
);
|
||||
drop(guard);
|
||||
|
||||
let config_path = home.join(".cc-switch").join("config.json");
|
||||
// 验证数据已持久化到数据库
|
||||
let db_path = home.join(".cc-switch").join("cc-switch.db");
|
||||
assert!(
|
||||
config_path.exists(),
|
||||
"state.save should persist config.json when changes detected"
|
||||
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 _home = ensure_test_home();
|
||||
|
||||
let mcp_path = get_claude_mcp_path();
|
||||
fs::write(&mcp_path, "{\"mcpServers\":") // 不完整 JSON
|
||||
.expect("seed invalid ~/.claude.json");
|
||||
|
||||
let state = AppState {
|
||||
config: RwLock::new(MultiAppConfig::default()),
|
||||
};
|
||||
let state = create_test_state().expect("create test state");
|
||||
|
||||
let err =
|
||||
McpService::import_from_claude(&state).expect_err("invalid json should bubble up error");
|
||||
@@ -172,10 +167,12 @@ fn import_mcp_from_claude_invalid_json_preserves_state() {
|
||||
other => panic!("unexpected error variant: {other:?}"),
|
||||
}
|
||||
|
||||
let config_path = home.join(".cc-switch").join("config.json");
|
||||
// 使用数据库架构,检查 MCP 服务器未被写入
|
||||
let servers = state.db.get_all_mcp_servers()
|
||||
.expect("get all mcp servers");
|
||||
assert!(
|
||||
!config_path.exists(),
|
||||
"failed import should not persist config.json"
|
||||
servers.is_empty(),
|
||||
"failed import should not persist any MCP servers to database"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -221,27 +218,21 @@ fn set_mcp_enabled_for_codex_writes_live_config() {
|
||||
},
|
||||
);
|
||||
|
||||
let state = AppState {
|
||||
config: RwLock::new(config),
|
||||
};
|
||||
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 guard = state.config.read().expect("lock config");
|
||||
let entry = guard
|
||||
.mcp
|
||||
.servers
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
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"
|
||||
);
|
||||
drop(guard);
|
||||
|
||||
let toml_path = cc_switch_lib::get_codex_config_path();
|
||||
assert!(
|
||||
|
||||
Reference in New Issue
Block a user