fix: refine oauth auto-removal behavior

This commit is contained in:
beilo
2026-05-19 13:41:18 +08:00
parent 2ca4b486ec
commit 75aa3dc0cc
13 changed files with 388 additions and 40 deletions
+79 -10
View File
@@ -26,6 +26,25 @@ pub fn should_auto_remove_structured_reason(reason: Option<&str>) -> bool {
))
}
pub fn should_auto_remove_oauth_refresh_failed_key(
key: &StoredProviderCatalogKey,
now_unix_secs: u64,
) -> bool {
if !key.auth_type.trim().eq_ignore_ascii_case("oauth") {
return false;
}
let Some(reason) = key.oauth_invalid_reason.as_deref().map(str::trim) else {
return false;
};
reason
.lines()
.map(str::trim)
.any(|line| line.starts_with(OAUTH_REFRESH_FAILED_PREFIX))
&& key
.expires_at_unix_secs
.is_none_or(|value| value == 0 || value <= now_unix_secs)
}
pub fn normalize_string_id_list(values: Option<Vec<String>>) -> Option<Vec<String>> {
let mut out = Vec::new();
let mut seen = std::collections::BTreeSet::new();
@@ -116,7 +135,7 @@ pub fn quota_refresh_success_invalid_state(
.as_deref()
.map(str::trim)
.unwrap_or_default();
if current_reason.starts_with(OAUTH_REFRESH_FAILED_PREFIX) {
if current_reason.starts_with(OAUTH_ACCOUNT_BLOCK_PREFIX) {
return (
key.oauth_invalid_at_unix_secs,
(!current_reason.is_empty()).then_some(current_reason.to_string()),
@@ -435,8 +454,7 @@ fn codex_merge_invalid_reason(current: &str, candidate_reason: &str) -> String {
return current.to_string();
}
if current.starts_with(OAUTH_EXPIRED_PREFIX)
&& (candidate_reason.starts_with(OAUTH_REQUEST_FAILED_PREFIX)
|| candidate_reason.starts_with(OAUTH_REFRESH_FAILED_PREFIX))
&& candidate_reason.starts_with(OAUTH_REQUEST_FAILED_PREFIX)
{
return current.to_string();
}
@@ -914,8 +932,9 @@ mod tests {
use super::{
codex_build_invalid_state, codex_runtime_invalid_reason,
parse_chatgpt_web_conversation_init_response, parse_codex_wham_usage_response,
should_auto_remove_structured_reason, OAUTH_ACCOUNT_BLOCK_PREFIX, OAUTH_EXPIRED_PREFIX,
OAUTH_REFRESH_FAILED_PREFIX, OAUTH_REQUEST_FAILED_PREFIX,
quota_refresh_success_invalid_state, should_auto_remove_structured_reason,
should_auto_remove_oauth_refresh_failed_key, OAUTH_ACCOUNT_BLOCK_PREFIX,
OAUTH_EXPIRED_PREFIX, OAUTH_REFRESH_FAILED_PREFIX, OAUTH_REQUEST_FAILED_PREFIX,
};
use aether_data_contracts::repository::provider_catalog::StoredProviderCatalogKey;
use serde_json::json;
@@ -944,7 +963,7 @@ mod tests {
}
#[test]
fn codex_invalid_state_keeps_oauth_expired_over_refresh_failure() {
fn codex_invalid_state_replaces_oauth_expired_with_refresh_failure() {
let mut key = StoredProviderCatalogKey::new(
"key-1".to_string(),
"provider-1".to_string(),
@@ -964,10 +983,26 @@ mod tests {
200,
),
(
Some(100),
Some(format!("{OAUTH_EXPIRED_PREFIX}session expired"))
Some(200),
Some(format!("{OAUTH_REFRESH_FAILED_PREFIX}Token 续期失败"))
)
);
}
#[test]
fn codex_invalid_state_keeps_oauth_expired_over_request_failure() {
let mut key = StoredProviderCatalogKey::new(
"key-1".to_string(),
"provider-1".to_string(),
"key-1".to_string(),
"oauth".to_string(),
None,
true,
)
.expect("key should build");
key.oauth_invalid_at_unix_secs = Some(100);
key.oauth_invalid_reason = Some(format!("{OAUTH_EXPIRED_PREFIX}session expired"));
assert_eq!(
codex_build_invalid_state(
&key,
@@ -1011,12 +1046,46 @@ mod tests {
}
#[test]
fn auto_remove_structured_reason_removes_oauth_expired_token_invalid() {
assert!(should_auto_remove_structured_reason(Some(
fn auto_remove_structured_reason_keeps_oauth_expired_token_invalid() {
assert!(!should_auto_remove_structured_reason(Some(
"[OAUTH_EXPIRED] token invalidated"
)));
}
#[test]
fn auto_remove_oauth_refresh_failed_requires_expired_access_token() {
let mut key = StoredProviderCatalogKey::new(
"key-1".to_string(),
"provider-1".to_string(),
"key-1".to_string(),
"oauth".to_string(),
None,
true,
)
.expect("key should build");
key.oauth_invalid_reason = Some(format!("{OAUTH_REFRESH_FAILED_PREFIX}Token 续期失败"));
key.expires_at_unix_secs = Some(2_000);
assert!(!should_auto_remove_oauth_refresh_failed_key(&key, 1_000));
assert!(should_auto_remove_oauth_refresh_failed_key(&key, 2_000));
}
#[test]
fn quota_refresh_success_clears_refresh_failed_marker() {
let mut key = StoredProviderCatalogKey::new(
"key-1".to_string(),
"provider-1".to_string(),
"key-1".to_string(),
"oauth".to_string(),
None,
true,
)
.expect("key should build");
key.oauth_invalid_reason = Some("[REFRESH_FAILED] Token 续期失败".to_string());
assert_eq!(quota_refresh_success_invalid_state(&key), (None, None));
}
#[test]
fn auto_remove_structured_reason_keeps_request_and_refresh_failures() {
assert!(!should_auto_remove_structured_reason(Some(
+2 -3
View File
@@ -42,7 +42,6 @@ const AUTO_REMOVABLE_ACCOUNT_STATE_CODES: &[&str] = &[
"account_disabled",
"workspace_deactivated",
"account_forbidden",
"oauth_token_invalid",
];
#[derive(Debug, Clone, Default, PartialEq, Eq)]
@@ -604,7 +603,7 @@ mod tests {
}
#[test]
fn oauth_expired_state_is_auto_removed() {
fn oauth_expired_state_is_not_auto_removed() {
let state = resolve_pool_account_state(
Some("codex"),
None,
@@ -613,7 +612,7 @@ mod tests {
assert!(state.blocked);
assert_eq!(state.code.as_deref(), Some("oauth_token_invalid"));
assert!(should_auto_remove_account_state(&state));
assert!(!should_auto_remove_account_state(&state));
}
#[test]