[codex-core] add revoked auth integration coverage [ci changed_files]

This commit is contained in:
Cooper Gamble
2026-05-19 23:16:49 +00:00
parent 96e9af9096
commit 053f6633b1
3 changed files with 119 additions and 1 deletions

View File

@@ -716,6 +716,35 @@ async fn token_invalidated_error_type_401_clears_auth_and_requires_relogin() {
);
}
#[tokio::test]
async fn token_revoked_error_code_401_clears_auth_and_requires_relogin() {
let (_codex_home, manager) = managed_chatgpt_auth_manager("revoked-access-token").await;
let mut recovery = Some(manager.unauthorized_recovery());
let err = handle_unauthorized(
TransportError::Http {
status: StatusCode::UNAUTHORIZED,
url: Some("https://chatgpt.com/backend-api/codex/responses".to_string()),
headers: None,
body: Some(r#"{"error":{"code":"token_revoked"}}"#.to_string()),
},
&mut recovery,
&test_session_telemetry(),
)
.await
.expect_err("revoked access tokens should force relogin");
let CodexErr::RefreshTokenFailed(failed) = err else {
panic!("expected revoked access token to force relogin, got {err:?}");
};
assert_eq!(failed.reason, RefreshTokenFailedReason::Revoked);
assert_eq!(
failed.message,
"Your ChatGPT session is no longer valid. Please sign in again."
);
assert!(manager.auth_cached().is_none());
}
#[tokio::test]
async fn token_invalidated_401_retries_when_persisted_auth_changed() {
let (codex_home, manager) = managed_chatgpt_auth_manager("revoked-access-token").await;

View File

@@ -339,7 +339,7 @@ impl ThreadManager {
state_db: Option<StateDbHandle>,
) -> Self {
set_thread_manager_test_mode_for_tests(/*enabled*/ true);
let auth_manager = AuthManager::from_auth_for_testing(auth);
let auth_manager = AuthManager::from_auth_for_testing_with_home(auth, codex_home.clone());
let installation_id = uuid::Uuid::new_v4().to_string();
let skills_codex_home = match AbsolutePathBuf::from_absolute_path_checked(&codex_home) {
Ok(codex_home) => codex_home,

View File

@@ -1066,6 +1066,95 @@ async fn chatgpt_auth_sends_correct_request() {
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn revoked_chatgpt_auth_user_turn_clears_auth_and_requests_relogin() -> anyhow::Result<()> {
skip_if_no_network!(Ok(()));
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/api/codex/responses"))
.respond_with(ResponseTemplate::new(401).set_body_json(json!({
"error": {
"code": "token_revoked",
"message": "revoked",
}
})))
.expect(1)
.mount(&server)
.await;
let codex_home = Arc::new(TempDir::new()?);
let _jwt = write_auth_json(
codex_home.as_ref(),
/*openai_api_key*/ None,
"pro",
"revoked-access-token",
Some("account_id"),
);
let auth = CodexAuth::from_auth_storage(
codex_home.path(),
AuthCredentialsStoreMode::File,
/*chatgpt_base_url*/ None,
)
.await?
.expect("managed ChatGPT auth should load");
let mut model_provider = built_in_model_providers(/* openai_base_url */ None)["openai"].clone();
model_provider.base_url = Some(format!("{}/api/codex", server.uri()));
model_provider.supports_websockets = false;
let mut builder = test_codex()
.with_home(codex_home.clone())
.with_auth(auth)
.with_config(move |config| {
config.model_provider = model_provider;
});
let test = builder.build(&server).await?;
let codex = test.codex.clone();
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
responsesapi_client_metadata: None,
})
.await?;
let error_event = wait_for_event(&codex, |ev| matches!(ev, EventMsg::Error(_))).await;
assert!(
matches!(
error_event,
EventMsg::Error(ref err)
if err.message.contains(
"Your ChatGPT session is no longer valid. Please sign in again."
)
),
"expected invalidated-session relogin error; got {error_event:?}"
);
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
assert!(
!test.codex_home_path().join("auth.json").exists(),
"revoked managed ChatGPT auth should be removed"
);
let response_attempts = server
.received_requests()
.await
.expect("mock server should capture requests")
.into_iter()
.filter(|request| request.url.path() == "/api/codex/responses")
.count();
assert_eq!(
response_attempts, 1,
"revoked managed auth should fail without retrying /responses"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn prefers_apikey_when_config_prefers_apikey_even_with_chatgpt_tokens() {
skip_if_no_network!();