mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-06-16 13:34:04 +08:00
Refactor Codex live-write routing and cover default auth overwrite
Collapse the two duplicated write_codex_live_atomic branches in
write_codex_live_for_provider into a single should_write_auth guard.
This is behavior-preserving: `if A {X} else if B {X} else {Y}` becomes
`if A || B {X} else {Y}`.
Adapt the Codex switch tests to the new opt-in default for
preserve_codex_official_auth_on_switch (flipped off in 3f59ab37):
add an enable_codex_official_auth_preservation() test helper for the
cases that assert the auth-preserving path, and tag the official login
provider with category="official" so it routes through the official
branch rather than relying on the global preservation flag.
Add a regression test locking the default (preservation off) behavior:
switching to a third-party provider rewrites auth.json with the new
API key and discards the existing ChatGPT OAuth login. This is the
dual of the existing preserve-and-backfill test, which only covered
the opt-in path.
This commit is contained in:
@@ -796,11 +796,11 @@ pub fn write_codex_live_for_provider(
|
||||
auth: &Value,
|
||||
config_text: Option<&str>,
|
||||
) -> Result<(), AppError> {
|
||||
if category == Some("official") && codex_auth_has_login_material(auth) {
|
||||
write_codex_live_atomic(auth, config_text)
|
||||
} else if category != Some("official")
|
||||
&& !crate::settings::preserve_codex_official_auth_on_switch()
|
||||
{
|
||||
let should_write_auth = (category == Some("official") && codex_auth_has_login_material(auth))
|
||||
|| (category != Some("official")
|
||||
&& !crate::settings::preserve_codex_official_auth_on_switch());
|
||||
|
||||
if should_write_auth {
|
||||
write_codex_live_atomic(auth, config_text)
|
||||
} else {
|
||||
let live_config = prepare_codex_provider_live_config(auth, config_text.unwrap_or(""))?;
|
||||
|
||||
@@ -10,7 +10,8 @@ use cc_switch_lib::{
|
||||
#[path = "support.rs"]
|
||||
mod support;
|
||||
use support::{
|
||||
create_test_state, create_test_state_with_config, ensure_test_home, reset_test_fs, test_mutex,
|
||||
create_test_state, create_test_state_with_config, enable_codex_official_auth_preservation,
|
||||
ensure_test_home, reset_test_fs, test_mutex,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -73,6 +74,7 @@ fn sync_claude_provider_writes_live_settings() {
|
||||
fn sync_codex_provider_writes_config_without_touching_auth() {
|
||||
let _guard = test_mutex().lock().expect("acquire test mutex");
|
||||
reset_test_fs();
|
||||
enable_codex_official_auth_preservation();
|
||||
|
||||
let mut config = MultiAppConfig::default();
|
||||
|
||||
|
||||
@@ -11,7 +11,8 @@ use cc_switch_lib::{
|
||||
mod support;
|
||||
use std::collections::HashMap;
|
||||
use support::{
|
||||
create_test_state, create_test_state_with_config, ensure_test_home, reset_test_fs, test_mutex,
|
||||
create_test_state, create_test_state_with_config, enable_codex_official_auth_preservation,
|
||||
ensure_test_home, reset_test_fs, test_mutex,
|
||||
};
|
||||
|
||||
fn settings_path(home: &Path) -> PathBuf {
|
||||
@@ -238,6 +239,7 @@ fn codex_startup_import_skips_when_only_official_seed_exists() {
|
||||
fn switch_provider_updates_codex_live_and_state() {
|
||||
let _guard = test_mutex().lock().expect("acquire test mutex");
|
||||
reset_test_fs();
|
||||
enable_codex_official_auth_preservation();
|
||||
let _home = ensure_test_home();
|
||||
|
||||
let legacy_auth = json!({"OPENAI_API_KEY": "legacy-key"});
|
||||
|
||||
@@ -8,7 +8,8 @@ use cc_switch_lib::{
|
||||
#[path = "support.rs"]
|
||||
mod support;
|
||||
use support::{
|
||||
create_test_state, create_test_state_with_config, ensure_test_home, reset_test_fs, test_mutex,
|
||||
create_test_state, create_test_state_with_config, enable_codex_official_auth_preservation,
|
||||
ensure_test_home, reset_test_fs, test_mutex,
|
||||
};
|
||||
|
||||
fn sanitize_provider_name(name: &str) -> String {
|
||||
@@ -99,6 +100,7 @@ fn migrate_legacy_common_config_usage_marks_historical_provider_enabled() {
|
||||
fn provider_service_switch_codex_updates_live_and_config() {
|
||||
let _guard = test_mutex().lock().expect("acquire test mutex");
|
||||
reset_test_fs();
|
||||
enable_codex_official_auth_preservation();
|
||||
let _home = ensure_test_home();
|
||||
|
||||
let legacy_auth = json!({ "OPENAI_API_KEY": "legacy-key" });
|
||||
@@ -355,6 +357,7 @@ requires_openai_auth = true
|
||||
fn provider_service_switch_codex_preserves_oauth_and_backfills_api_key_from_live_token() {
|
||||
let _guard = test_mutex().lock().expect("acquire test mutex");
|
||||
reset_test_fs();
|
||||
enable_codex_official_auth_preservation();
|
||||
let _home = ensure_test_home();
|
||||
|
||||
let live_auth = json!({
|
||||
@@ -520,6 +523,94 @@ requires_openai_auth = true
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_service_switch_codex_default_overwrites_official_auth_when_preservation_off() {
|
||||
let _guard = test_mutex().lock().expect("acquire test mutex");
|
||||
reset_test_fs();
|
||||
// Intentionally do NOT enable preservation: this locks the default opt-out
|
||||
// behavior where switching to a third-party provider rewrites auth.json,
|
||||
// discarding the user's ChatGPT OAuth login. It is the dual of
|
||||
// `provider_service_switch_codex_preserves_oauth_and_backfills_api_key_from_live_token`.
|
||||
let _home = ensure_test_home();
|
||||
|
||||
let live_auth = json!({
|
||||
"auth_mode": "chatgpt",
|
||||
"OPENAI_API_KEY": null,
|
||||
"tokens": {
|
||||
"access_token": "official-oauth-token",
|
||||
"account_id": "acct-1"
|
||||
}
|
||||
});
|
||||
let legacy_config = r#"model_provider = "rightcode"
|
||||
model = "gpt-5.4"
|
||||
|
||||
[model_providers.rightcode]
|
||||
name = "RightCode"
|
||||
base_url = "https://rightcode.example/v1"
|
||||
wire_api = "responses"
|
||||
requires_openai_auth = true
|
||||
"#;
|
||||
write_codex_live_atomic(&live_auth, Some(legacy_config))
|
||||
.expect("seed existing Codex OAuth live config");
|
||||
|
||||
let mut initial_config = MultiAppConfig::default();
|
||||
{
|
||||
let manager = initial_config
|
||||
.get_manager_mut(&AppType::Codex)
|
||||
.expect("codex manager");
|
||||
manager.current = "legacy-provider".to_string();
|
||||
manager.providers.insert(
|
||||
"legacy-provider".to_string(),
|
||||
Provider::with_id(
|
||||
"legacy-provider".to_string(),
|
||||
"RightCode".to_string(),
|
||||
json!({
|
||||
"auth": {"OPENAI_API_KEY": "rightcode-key"},
|
||||
"config": legacy_config
|
||||
}),
|
||||
None,
|
||||
),
|
||||
);
|
||||
manager.providers.insert(
|
||||
"third-party".to_string(),
|
||||
Provider::with_id(
|
||||
"third-party".to_string(),
|
||||
"AiHubMix".to_string(),
|
||||
json!({
|
||||
"auth": {"OPENAI_API_KEY": "third-party-key"},
|
||||
"config": r#"model_provider = "aihubmix"
|
||||
model = "gpt-5.4"
|
||||
|
||||
[model_providers.aihubmix]
|
||||
name = "AiHubMix"
|
||||
base_url = "https://aihubmix.example/v1"
|
||||
wire_api = "responses"
|
||||
requires_openai_auth = true
|
||||
"#
|
||||
}),
|
||||
None,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
let state = create_test_state_with_config(&initial_config).expect("create test state");
|
||||
|
||||
ProviderService::switch(&state, AppType::Codex, "third-party")
|
||||
.expect("switch to third-party provider should succeed");
|
||||
|
||||
let auth_value: serde_json::Value =
|
||||
read_json_file(&cc_switch_lib::get_codex_auth_path()).expect("read auth.json");
|
||||
assert_eq!(
|
||||
auth_value.get("OPENAI_API_KEY").and_then(|v| v.as_str()),
|
||||
Some("third-party-key"),
|
||||
"default (preservation off) should overwrite auth.json with the third-party API key"
|
||||
);
|
||||
assert!(
|
||||
auth_value.pointer("/tokens/access_token").is_none(),
|
||||
"default switch must clear the official ChatGPT OAuth token from live auth.json"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_service_switch_codex_supports_official_login_provider_without_auth_write() {
|
||||
let _guard = test_mutex().lock().expect("acquire test mutex");
|
||||
@@ -561,18 +652,19 @@ requires_openai_auth = true
|
||||
None,
|
||||
),
|
||||
);
|
||||
manager.providers.insert(
|
||||
let mut official_provider = Provider::with_id(
|
||||
"official-provider".to_string(),
|
||||
Provider::with_id(
|
||||
"official-provider".to_string(),
|
||||
"OpenAI Official".to_string(),
|
||||
json!({
|
||||
"auth": {},
|
||||
"config": ""
|
||||
}),
|
||||
None,
|
||||
),
|
||||
"OpenAI Official".to_string(),
|
||||
json!({
|
||||
"auth": {},
|
||||
"config": ""
|
||||
}),
|
||||
None,
|
||||
);
|
||||
official_provider.category = Some("official".to_string());
|
||||
manager
|
||||
.providers
|
||||
.insert("official-provider".to_string(), official_provider);
|
||||
}
|
||||
|
||||
let state = create_test_state_with_config(&initial_config).expect("create test state");
|
||||
|
||||
@@ -50,6 +50,15 @@ pub fn reset_test_fs() {
|
||||
let _ = update_settings(AppSettings::default());
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn enable_codex_official_auth_preservation() {
|
||||
update_settings(AppSettings {
|
||||
preserve_codex_official_auth_on_switch: true,
|
||||
..Default::default()
|
||||
})
|
||||
.expect("enable Codex official auth preservation");
|
||||
}
|
||||
|
||||
/// 全局互斥锁,避免多测试并发写入相同的 HOME 目录。
|
||||
pub fn test_mutex() -> &'static Mutex<()> {
|
||||
static MUTEX: OnceLock<Mutex<()>> = OnceLock::new();
|
||||
|
||||
Reference in New Issue
Block a user