mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-04 19:45:34 +08:00
refactor(provider): establish typed write ownership
Replace the generic provider upsert surface with strict typed create, update, rename, endpoint, and compensation operations. This app-independent prerequisite owns provider writes without depending on Pi runtime or canonical restore. The v17 migration also reserves dormant device-local ledger tables and the Pi skill bit alongside endpoint normalization. Keeping that reservation here prevents a later stacked feature from rewriting a published migration; no Pi runtime behavior is activated by this commit. Old save_provider callsite classification ========================================== Inventory authority: abandoned 5a385fc8 tree. The old definition at src-tauri/src/database/dao/providers.rs:180 is deleted and is not a callsite. Production callsites: - src-tauri/src/commands/provider.rs:253 [create] Claude Desktop import creates one absent aggregate; it now strict-inserts the row and initial endpoints in one transaction. - src-tauri/src/database/dao/providers.rs:638 [create] official seed first proves absence, then strict-creates; a racing insert is a conflict. - src-tauri/src/database/dao/providers.rs:704 [create] on-demand seed first proves absence, then strict-creates; it cannot overwrite an existing row. - src-tauri/src/services/omo.rs:291 [create] OMO import constructs a new aggregate and strict-creates it; OMO is not eligible for rename. - src-tauri/src/services/provider/endpoints.rs:85 [update] endpoint last-used is not a Provider-row save; it now calls the exact touch endpoint operation. - src-tauri/src/services/provider/live.rs:1567 [create/update] default live import is reconciliation: read first, then strict create or strict update. - src-tauri/src/services/provider/live.rs:1743 [update] an existing OpenCode live provider follows the strict row-update branch. - src-tauri/src/services/provider/live.rs:1770 [create] a new OpenCode live provider follows the strict aggregate-create branch. - src-tauri/src/services/provider/live.rs:1825 [update] an existing OpenClaw live provider follows the strict row-update branch. - src-tauri/src/services/provider/live.rs:1858 [create] a new OpenClaw live provider follows the strict aggregate-create branch. - src-tauri/src/services/provider/live.rs:1900 [update] an existing Hermes live provider follows the strict row-update branch. - src-tauri/src/services/provider/live.rs:1926 [create] a new Hermes live provider follows the strict aggregate-create branch. - src-tauri/src/services/provider/mod.rs:2568 [create] ProviderService::add owns strict aggregate creation and all initial endpoints. - src-tauri/src/services/provider/mod.rs:2680 [rename] an additive DB-only key change now uses the dedicated transactional rename after eligibility checks. - src-tauri/src/services/provider/mod.rs:2711 [update] OMO edit updates exactly the existing main row after its live-file coordination. - src-tauri/src/services/provider/mod.rs:2740 [update] additive-provider edit updates exactly the existing main row after resolving live ownership. - src-tauri/src/services/provider/mod.rs:2750 [update] switch-mode edit updates exactly the existing main row and never inserts. - src-tauri/src/services/provider/mod.rs:2948 [update] remove-from-live changes only the existing provider's live-managed marker. - src-tauri/src/services/provider/mod.rs:3120 [update] switch backfill updates only the existing current provider row. - src-tauri/src/services/provider/mod.rs:3174 [update] successful additive switch changes only the existing live-managed marker. - src-tauri/src/services/provider/mod.rs:3315 [update] common-config migration updates only each already-read existing row. - src-tauri/src/services/provider/mod.rs:3895 [update] Gemini credential scrub updates only each already-read existing row. - src-tauri/src/services/provider/mod.rs:4082 [update] sort ordering is routed to the dedicated sort-index state operation, not row replacement. - src-tauri/src/services/provider/mod.rs:4636 [create/update] universal-to- Claude reconciliation reads the target and selects strict create or update. - src-tauri/src/services/provider/mod.rs:4651 [create/update] universal-to- Codex reconciliation reads the target and selects strict create or update. - src-tauri/src/services/provider/mod.rs:4665 [create/update] universal-to- Gemini reconciliation reads the target and selects strict create or update. Required indirect ownership paths: - src-tauri/src/deeplink/provider.rs [create] the old indirect flow called ProviderService::add and then appended endpoints one by one. It now supplies every non-primary endpoint to one strict aggregate create, so hydration is complete atomically and a duplicate is zero-side-effect. - [restore] no old generic-save callsite is reclassified as restore. Exact aggregate replacement exists only as the sealed restore_provider_aggregate_on_tx compensation primitive. Test-only callsites: Every item below is classified [test]. Each is fixture setup, not a production write authority, and is migrated to a real ProviderService entry where the behavior is under test or to the cfg(test)-only typed fixture reconciler where the test merely needs pre-existing rows. - src-tauri/src/codex_history_migration.rs:1442 [test] migration fixture setup. - src-tauri/src/codex_history_migration.rs:1452 [test] migration fixture setup. - src-tauri/src/codex_history_migration.rs:2174 [test] migration fixture setup. - src-tauri/src/codex_history_migration.rs:2176 [test] migration fixture setup. - src-tauri/src/codex_history_migration.rs:2199 [test] migration fixture setup. - src-tauri/src/codex_history_migration.rs:2219 [test] migration fixture setup. - src-tauri/src/codex_history_migration.rs:2247 [test] migration fixture setup. - src-tauri/src/codex_history_migration.rs:2267 [test] migration fixture setup. - src-tauri/src/codex_history_migration.rs:2288 [test] migration fixture setup. - src-tauri/src/codex_history_migration.rs:2320 [test] migration fixture setup. - src-tauri/src/codex_history_migration.rs:2393 [test] migration fixture setup. - src-tauri/src/codex_history_migration.rs:2449 [test] migration fixture setup. - src-tauri/src/codex_history_migration.rs:2498 [test] migration fixture setup. - src-tauri/src/codex_history_migration.rs:2555 [test] migration fixture setup. - src-tauri/src/codex_history_migration.rs:2604 [test] migration fixture setup. - src-tauri/src/codex_history_migration.rs:2625 [test] migration fixture setup. - src-tauri/src/database/dao/providers.rs:754 [test] DAO fixture setup. - src-tauri/src/proxy/provider_router.rs:351 [test] router fixture setup. - src-tauri/src/proxy/provider_router.rs:352 [test] router fixture setup. - src-tauri/src/proxy/provider_router.rs:377 [test] router fixture setup. - src-tauri/src/proxy/provider_router.rs:378 [test] router fixture setup. - src-tauri/src/proxy/provider_router.rs:410 [test] router fixture setup. - src-tauri/src/proxy/provider_router.rs:411 [test] router fixture setup. - src-tauri/src/proxy/provider_router.rs:447 [test] router fixture setup. - src-tauri/src/proxy/provider_router.rs:448 [test] router fixture setup. - src-tauri/src/proxy/provider_router.rs:488 [test] router fixture setup. - src-tauri/src/services/provider/mod.rs:485 [test] service fixture setup. - src-tauri/src/services/provider/mod.rs:586 [test] service fixture setup. - src-tauri/src/services/provider/mod.rs:813 [test] service fixture setup. - src-tauri/src/services/provider/mod.rs:825 [test] service fixture setup. - src-tauri/src/services/provider/mod.rs:1472 [test] service fixture setup. - src-tauri/src/services/provider/mod.rs:1607 [test] service fixture setup. - src-tauri/src/services/provider/mod.rs:1737 [test] service fixture setup. - src-tauri/src/services/provider/mod.rs:1945 [test] service fixture setup. - src-tauri/src/services/provider/mod.rs:1952 [test] service fixture setup. - src-tauri/src/services/provider/mod.rs:1978 [test] service fixture setup. - src-tauri/src/services/provider/mod.rs:2006 [test] service fixture setup. - src-tauri/src/services/provider/mod.rs:2056 [test] service fixture setup. - src-tauri/src/services/provider/mod.rs:2130 [test] service fixture setup. - src-tauri/src/services/provider/mod.rs:2167 [test] service fixture setup. - src-tauri/src/services/provider/mod.rs:2207 [test] service fixture setup. - src-tauri/src/services/provider/mod.rs:2235 [test] service fixture setup. - src-tauri/src/services/provider/mod.rs:2270 [test] service fixture setup. - src-tauri/src/services/provider/mod.rs:2320 [test] service fixture setup. - src-tauri/src/services/provider/mod.rs:2362 [test] service fixture setup. - src-tauri/src/services/proxy.rs:3762 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:3948 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:4034 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:4095 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:4114 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:4263 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:4341 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:4421 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:4533 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:4651 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:4787 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:5264 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:5320 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:5385 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:5387 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:5460 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:5462 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:5611 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:5613 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:5615 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:5698 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:5700 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:5998 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:6000 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:6173 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:6175 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:6417 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:6419 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:6553 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:6555 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:6635 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:6637 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:6919 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:7173 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:7175 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:7240 [test] proxy fixture setup. - src-tauri/src/services/proxy.rs:7242 [test] proxy fixture setup. - src-tauri/tests/profile_roundtrip.rs:112 [test] profile fixture create. - src-tauri/tests/profile_roundtrip.rs:116 [test] profile fixture create. - src-tauri/tests/profile_roundtrip.rs:126 [test] profile fixture create. - src-tauri/tests/profile_roundtrip.rs:133 [test] profile fixture create. - src-tauri/tests/profile_roundtrip.rs:292 [test] profile fixture create. - src-tauri/tests/profile_roundtrip.rs:501 [test] profile fixture create. - src-tauri/tests/profile_roundtrip.rs:505 [test] profile fixture create. - src-tauri/tests/profile_roundtrip.rs:670 [test] profile fixture create. - src-tauri/tests/profile_roundtrip.rs:677 [test] profile fixture create. - src-tauri/tests/profile_roundtrip.rs:762 [test] Linux Desktop fixture create. - src-tauri/tests/profile_roundtrip.rs:769 [test] Linux Desktop fixture create. - src-tauri/tests/provider_commands.rs:69 [test] command fixture create. - src-tauri/tests/provider_service.rs:2927 [test] service fixture create.
This commit is contained in:
@@ -7,13 +7,14 @@ use std::fs;
|
||||
use serde_json::json;
|
||||
|
||||
use cc_switch_lib::{
|
||||
AppType, InstalledSkill, McpServer, McpService, ProfilePayload, ProfileScope, ProfileService,
|
||||
Prompt, PromptService, Provider, ProviderService, SkillApps, SkillService,
|
||||
AppType, InstalledSkill, McpServer, McpService, NewProviderAggregate, ProfilePayload,
|
||||
ProfileScope, ProfileService, Prompt, PromptService, Provider, ProviderService, SkillApps,
|
||||
SkillService,
|
||||
};
|
||||
|
||||
#[path = "support.rs"]
|
||||
mod support;
|
||||
use support::{create_test_state, ensure_test_home, reset_test_fs, test_mutex};
|
||||
use support::{create_test_state, ensure_test_home, new_provider_input, reset_test_fs, test_mutex};
|
||||
|
||||
fn claude_provider(id: &str, token: &str) -> Provider {
|
||||
Provider::with_id(
|
||||
@@ -107,34 +108,41 @@ fn profile_snapshot_apply_roundtrip_restores_configuration() {
|
||||
let state = create_test_state().expect("create test state");
|
||||
|
||||
// ---- 种子数据:2 个 Claude 供应商(p1 为当前)+ 2 个 MCP + 1 个 Skill + 2 个 Prompt ----
|
||||
state
|
||||
.db
|
||||
.save_provider(AppType::Claude.as_str(), &claude_provider("p1", "key-1"))
|
||||
.expect("save provider p1");
|
||||
state
|
||||
.db
|
||||
.save_provider(AppType::Claude.as_str(), &claude_provider("p2", "key-2"))
|
||||
.expect("save provider p2");
|
||||
ProviderService::add(
|
||||
&state,
|
||||
AppType::Claude,
|
||||
new_provider_input(claude_provider("p1", "key-1")),
|
||||
false,
|
||||
)
|
||||
.expect("create provider p1");
|
||||
ProviderService::add(
|
||||
&state,
|
||||
AppType::Claude,
|
||||
new_provider_input(claude_provider("p2", "key-2")),
|
||||
false,
|
||||
)
|
||||
.expect("create provider p2");
|
||||
state
|
||||
.db
|
||||
.set_current_provider(AppType::Claude.as_str(), "p1")
|
||||
.expect("set current provider p1");
|
||||
|
||||
// Claude Desktop 只有供应商一个活跃维度(MCP/Skills/Prompt 对它不适用)
|
||||
state
|
||||
.db
|
||||
.save_provider(
|
||||
AppType::ClaudeDesktop.as_str(),
|
||||
&desktop_provider("d1", "dk-1"),
|
||||
)
|
||||
.expect("save desktop provider d1");
|
||||
state
|
||||
.db
|
||||
.save_provider(
|
||||
AppType::ClaudeDesktop.as_str(),
|
||||
&desktop_provider("d2", "dk-2"),
|
||||
)
|
||||
.expect("save desktop provider d2");
|
||||
for provider in [
|
||||
desktop_provider("d1", "dk-1"),
|
||||
desktop_provider("d2", "dk-2"),
|
||||
] {
|
||||
state
|
||||
.db
|
||||
.create_provider(
|
||||
NewProviderAggregate::from_input(
|
||||
AppType::ClaudeDesktop.as_str(),
|
||||
new_provider_input(provider),
|
||||
)
|
||||
.expect("build typed desktop create"),
|
||||
)
|
||||
.expect("create desktop provider");
|
||||
}
|
||||
state
|
||||
.db
|
||||
.set_current_provider(AppType::ClaudeDesktop.as_str(), "d1")
|
||||
@@ -287,10 +295,13 @@ fn shared_profile_sides_are_isolated_and_mergeable() {
|
||||
let state = create_test_state().expect("create test state");
|
||||
|
||||
// 种子:Claude 侧有当前供应商 + 启用的 MCP
|
||||
state
|
||||
.db
|
||||
.save_provider(AppType::Claude.as_str(), &claude_provider("p1", "key-1"))
|
||||
.expect("save provider p1");
|
||||
ProviderService::add(
|
||||
&state,
|
||||
AppType::Claude,
|
||||
new_provider_input(claude_provider("p1", "key-1")),
|
||||
false,
|
||||
)
|
||||
.expect("create provider p1");
|
||||
state
|
||||
.db
|
||||
.set_current_provider(AppType::Claude.as_str(), "p1")
|
||||
@@ -496,14 +507,20 @@ fn switching_profile_autosaves_previous_profile_state() {
|
||||
let state = create_test_state().expect("create test state");
|
||||
|
||||
// ---- 种子:Claude 侧两套供应商 / MCP / Prompt ----
|
||||
state
|
||||
.db
|
||||
.save_provider(AppType::Claude.as_str(), &claude_provider("p1", "key-1"))
|
||||
.expect("save provider p1");
|
||||
state
|
||||
.db
|
||||
.save_provider(AppType::Claude.as_str(), &claude_provider("p2", "key-2"))
|
||||
.expect("save provider p2");
|
||||
ProviderService::add(
|
||||
&state,
|
||||
AppType::Claude,
|
||||
new_provider_input(claude_provider("p1", "key-1")),
|
||||
false,
|
||||
)
|
||||
.expect("create provider p1");
|
||||
ProviderService::add(
|
||||
&state,
|
||||
AppType::Claude,
|
||||
new_provider_input(claude_provider("p2", "key-2")),
|
||||
false,
|
||||
)
|
||||
.expect("create provider p2");
|
||||
state
|
||||
.db
|
||||
.set_current_provider(AppType::Claude.as_str(), "p1")
|
||||
@@ -665,17 +682,13 @@ fn profile_switch_auto_disables_takeover_before_apply() {
|
||||
// ---- 两个 Claude 供应商:custom1 与 custom2 ----
|
||||
let mut custom1 = claude_provider("custom1", "custom-key-1");
|
||||
custom1.category = Some("custom".to_string());
|
||||
state
|
||||
.db
|
||||
.save_provider(AppType::Claude.as_str(), &custom1)
|
||||
.expect("save custom1 provider");
|
||||
ProviderService::add(&state, AppType::Claude, new_provider_input(custom1), false)
|
||||
.expect("create custom1 provider");
|
||||
|
||||
let mut custom2 = claude_provider("custom2", "custom-key-2");
|
||||
custom2.category = Some("custom".to_string());
|
||||
state
|
||||
.db
|
||||
.save_provider(AppType::Claude.as_str(), &custom2)
|
||||
.expect("save custom2 provider");
|
||||
ProviderService::add(&state, AppType::Claude, new_provider_input(custom2), false)
|
||||
.expect("create custom2 provider");
|
||||
|
||||
// 初始状态:custom1 + 代理接管
|
||||
ProviderService::switch(&state, AppType::Claude, "custom1").expect("switch to custom1");
|
||||
@@ -757,20 +770,20 @@ fn claude_desktop_profile_scope_is_independent() {
|
||||
|
||||
let state = create_test_state().expect("create test state");
|
||||
|
||||
state
|
||||
.db
|
||||
.save_provider(
|
||||
AppType::ClaudeDesktop.as_str(),
|
||||
&desktop_provider("d1", "dk-1"),
|
||||
)
|
||||
.expect("save desktop provider d1");
|
||||
state
|
||||
.db
|
||||
.save_provider(
|
||||
AppType::ClaudeDesktop.as_str(),
|
||||
&desktop_provider("d2", "dk-2"),
|
||||
)
|
||||
.expect("save desktop provider d2");
|
||||
ProviderService::add(
|
||||
&state,
|
||||
AppType::ClaudeDesktop,
|
||||
new_provider_input(desktop_provider("d1", "dk-1")),
|
||||
false,
|
||||
)
|
||||
.expect("create desktop provider d1");
|
||||
ProviderService::add(
|
||||
&state,
|
||||
AppType::ClaudeDesktop,
|
||||
new_provider_input(desktop_provider("d2", "dk-2")),
|
||||
false,
|
||||
)
|
||||
.expect("create desktop provider d2");
|
||||
state
|
||||
.db
|
||||
.set_current_provider(AppType::ClaudeDesktop.as_str(), "d1")
|
||||
|
||||
@@ -12,7 +12,7 @@ mod support;
|
||||
use std::collections::HashMap;
|
||||
use support::{
|
||||
create_test_state, create_test_state_with_config, enable_codex_official_auth_preservation,
|
||||
ensure_test_home, reset_test_fs, test_mutex,
|
||||
ensure_test_home, new_provider_input, reset_test_fs, test_mutex,
|
||||
};
|
||||
|
||||
fn settings_path(home: &Path) -> PathBuf {
|
||||
@@ -64,18 +64,18 @@ fn grokbuild_import_and_switch_write_live_config() {
|
||||
);
|
||||
|
||||
let next_config = grokbuild_config("Relay", "https://new.example/v1", "new-key");
|
||||
state
|
||||
.db
|
||||
.save_provider(
|
||||
AppType::GrokBuild.as_str(),
|
||||
&Provider::with_id(
|
||||
"relay".to_string(),
|
||||
"Relay".to_string(),
|
||||
json!({ "config": next_config }),
|
||||
None,
|
||||
),
|
||||
)
|
||||
.expect("save second Grok Build provider");
|
||||
ProviderService::add(
|
||||
&state,
|
||||
AppType::GrokBuild,
|
||||
new_provider_input(Provider::with_id(
|
||||
"relay".to_string(),
|
||||
"Relay".to_string(),
|
||||
json!({ "config": next_config }),
|
||||
None,
|
||||
)),
|
||||
false,
|
||||
)
|
||||
.expect("create second Grok Build provider");
|
||||
|
||||
switch_provider_test_hook(&state, AppType::GrokBuild, "relay")
|
||||
.expect("switch Grok Build provider");
|
||||
|
||||
@@ -9,7 +9,7 @@ use cc_switch_lib::{
|
||||
mod support;
|
||||
use support::{
|
||||
create_test_state, create_test_state_with_config, enable_codex_official_auth_preservation,
|
||||
ensure_test_home, reset_test_fs, test_mutex,
|
||||
ensure_test_home, new_provider_input, reset_test_fs, test_mutex,
|
||||
};
|
||||
|
||||
fn sanitize_provider_name(name: &str) -> String {
|
||||
@@ -3084,10 +3084,8 @@ fn recover_from_crash_without_backup_cleans_placeholder_instead_of_writing_it_ba
|
||||
taken_over_live.clone(),
|
||||
None,
|
||||
);
|
||||
state
|
||||
.db
|
||||
.save_provider(AppType::Claude.as_str(), &provider)
|
||||
.expect("save placeholder provider");
|
||||
ProviderService::add(&state, AppType::Claude, new_provider_input(provider), false)
|
||||
.expect("create placeholder provider");
|
||||
state
|
||||
.db
|
||||
.set_current_provider(AppType::Claude.as_str(), "default")
|
||||
|
||||
@@ -1,7 +1,31 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
|
||||
use cc_switch_lib::{update_settings, AppSettings, AppState, Database, MultiAppConfig};
|
||||
use cc_switch_lib::{
|
||||
update_settings, AppSettings, AppState, Database, MultiAppConfig, Provider,
|
||||
ProviderMutationInput,
|
||||
};
|
||||
|
||||
/// Build the public write DTO explicitly for integration tests. Keeping this
|
||||
/// conversion test-only avoids reintroducing a production `From<Provider>`
|
||||
/// path from hydrated read projections to provider mutations.
|
||||
#[allow(dead_code)]
|
||||
pub fn new_provider_input(provider: Provider) -> ProviderMutationInput {
|
||||
ProviderMutationInput {
|
||||
id: provider.id,
|
||||
name: provider.name,
|
||||
settings_config: provider.settings_config,
|
||||
website_url: provider.website_url,
|
||||
category: provider.category,
|
||||
created_at: provider.created_at,
|
||||
sort_index: provider.sort_index,
|
||||
notes: provider.notes,
|
||||
meta: provider.meta,
|
||||
icon: provider.icon,
|
||||
icon_color: provider.icon_color,
|
||||
in_failover_queue: provider.in_failover_queue,
|
||||
}
|
||||
}
|
||||
|
||||
/// 为测试设置隔离的 HOME 目录,避免污染真实用户数据。
|
||||
pub fn ensure_test_home() -> &'static Path {
|
||||
|
||||
Reference in New Issue
Block a user