mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-10-26 01:34:51 +08:00
Switching, adding, editing, sync, universal providers and entering or leaving local routing now write Codex through the write engine. Only routing, credential, model, reasoning, context-window and compatibility fields are replaced; MCP servers, [projects], comments and the user's own provider tables stay byte-for-byte. auth.json, config.toml, the model catalog, the managed-account marker and a device-local login stash commit as one operation, so a crash at any point rolls forward on startup. - Third-party routes are always written as [model_providers.custom] with the key in experimental_bearer_token. Rows that use another id, a top-level openai_base_url or a legacy reserved table are normalized on the way out. Switching to an official card leaves a dormant custom table (local proxy address plus placeholder credential) so older third-party sessions can still resume. - requires_openai_auth follows the route's auth and whether a login is on disk; keyring and ephemeral credential stores are handled separately. - A switch is refused before anything is written when the active profile overrides the route, the selected table is missing or built in, a keyless row would fall back to the official login, or the row has no auth. - Provider tables an older version wrote are removed only when they can be proven to be CC Switch's. Tables a profile references are kept, and reserved ids that cannot be proven are renamed. - Provider-exclusive fields, including web_search and a row's own model_catalog_json, are removed on the way out only when live still holds the previous provider's value. - No more backfill. A native ChatGPT login that CC Switch deletes or overwrites is kept in ~/.cc-switch/codex-login-stash.json (0600, not synced) and restored when switching back to an official card; logging out on an official card discards it. Login snapshots already in official rows seed the stash once. A leftover API key in auth.json is deleted only when it matches a third-party row's key. - Managed accounts fetch their token before taking the lock. If the CLI refreshes the login during a switch, the switch stops before writing anything. Once files are published, a later failure no longer rolls the row back; startup finishes the switch. - Local routing: third-party routes get a fixed local-proxy table and the official route uses cc-switch-official plus the client's own login. The contract covers every field written, so a route switch with the same contract touches no files. - The Codex editor shows what config.toml will look like after switching. On save, key fields go to the row and other edits go to live as global settings with a three-way check. Row fields that never reach live are listed read-only and can be copied. - The Codex common-config snippet is frozen like Claude's; its UI and the TOML snippet command are removed. New Codex rows keep commonConfigEnabled: true for older versions that sync them. - The write engine can delete a file as part of an operation. - Integration-test mutexes tolerate poisoning so one failure no longer cascades into unrelated tests.
87 lines
3.4 KiB
Rust
87 lines
3.4 KiB
Rust
use std::sync::Arc;
|
|
|
|
use cc_switch_lib::{import_provider_from_deeplink, parse_deeplink_url, AppState, Database};
|
|
|
|
#[path = "support.rs"]
|
|
mod support;
|
|
use support::{ensure_test_home, reset_test_fs, test_mutex};
|
|
|
|
#[test]
|
|
fn deeplink_import_claude_provider_persists_to_db() {
|
|
let _guard = test_mutex().lock().unwrap_or_else(|e| e.into_inner());
|
|
reset_test_fs();
|
|
let _home = ensure_test_home();
|
|
|
|
let url = "ccswitch://v1/import?resource=provider&app=claude&name=DeepLink%20Claude&homepage=https%3A%2F%2Fexample.com&endpoint=https%3A%2F%2Fapi.example.com%2Fv1&apiKey=sk-test-claude-key&model=claude-sonnet-4&icon=claude";
|
|
let request = parse_deeplink_url(url).expect("parse deeplink url");
|
|
|
|
let db = Arc::new(Database::memory().expect("create memory db"));
|
|
let state = AppState::new(db.clone());
|
|
|
|
let provider_id = import_provider_from_deeplink(&state, request.clone())
|
|
.expect("import provider from deeplink");
|
|
|
|
// Verify DB state
|
|
let providers = db.get_all_providers("claude").expect("get providers");
|
|
let provider = providers
|
|
.get(&provider_id)
|
|
.expect("provider created via deeplink");
|
|
|
|
assert_eq!(provider.name, request.name.clone().unwrap());
|
|
assert_eq!(provider.website_url.as_deref(), request.homepage.as_deref());
|
|
assert_eq!(provider.icon.as_deref(), Some("claude"));
|
|
let auth_token = provider
|
|
.settings_config
|
|
.pointer("/env/ANTHROPIC_AUTH_TOKEN")
|
|
.and_then(|v| v.as_str());
|
|
let base_url = provider
|
|
.settings_config
|
|
.pointer("/env/ANTHROPIC_BASE_URL")
|
|
.and_then(|v| v.as_str());
|
|
assert_eq!(auth_token, request.api_key.as_deref());
|
|
assert_eq!(base_url, request.endpoint.as_deref());
|
|
}
|
|
|
|
#[test]
|
|
fn deeplink_import_codex_provider_builds_auth_and_config() {
|
|
let _guard = test_mutex().lock().unwrap_or_else(|e| e.into_inner());
|
|
reset_test_fs();
|
|
let _home = ensure_test_home();
|
|
|
|
let url = "ccswitch://v1/import?resource=provider&app=codex&name=DeepLink%20Codex&homepage=https%3A%2F%2Fopenai.example&endpoint=https%3A%2F%2Fapi.openai.example%2Fv1&apiKey=sk-test-codex-key&model=gpt-4o&icon=openai";
|
|
let request = parse_deeplink_url(url).expect("parse deeplink url");
|
|
|
|
let db = Arc::new(Database::memory().expect("create memory db"));
|
|
let state = AppState::new(db.clone());
|
|
|
|
let provider_id = import_provider_from_deeplink(&state, request.clone())
|
|
.expect("import provider from deeplink");
|
|
|
|
let providers = db.get_all_providers("codex").expect("get providers");
|
|
let provider = providers
|
|
.get(&provider_id)
|
|
.expect("provider created via deeplink");
|
|
|
|
assert_eq!(provider.name, request.name.clone().unwrap());
|
|
assert_eq!(provider.website_url.as_deref(), request.homepage.as_deref());
|
|
assert_eq!(provider.icon.as_deref(), Some("openai"));
|
|
let auth_value = provider
|
|
.settings_config
|
|
.pointer("/auth/OPENAI_API_KEY")
|
|
.and_then(|v| v.as_str());
|
|
let config_text = provider
|
|
.settings_config
|
|
.get("config")
|
|
.and_then(|v| v.as_str())
|
|
.unwrap_or_default();
|
|
assert_eq!(auth_value, request.api_key.as_deref());
|
|
assert!(
|
|
config_text.contains(request.endpoint.as_deref().unwrap()),
|
|
"config.toml content should contain endpoint"
|
|
);
|
|
assert!(
|
|
config_text.contains("model = \"gpt-4o\""),
|
|
"config.toml content should contain model setting"
|
|
);
|
|
}
|