diff --git a/codex-rs/model-provider/src/shared_state_test_support.rs b/codex-rs/model-provider/src/shared_state_test_support.rs index 38bc88b709..5a6d1fdafa 100644 --- a/codex-rs/model-provider/src/shared_state_test_support.rs +++ b/codex-rs/model-provider/src/shared_state_test_support.rs @@ -1,4 +1,4 @@ -//! Seeds encrypted gateway credentials for provider and agent integration tests. +//! Seeds gateway credentials in the configured store for provider and agent integration tests. #![allow(clippy::expect_used)] // Fixture setup failures should fail the calling test immediately. @@ -11,13 +11,12 @@ use codex_login::AuthManager; use codex_login::GatewayAuthConfig; use codex_login::GatewayAuthManager; use codex_model_provider_info::ModelProviderInfo; +use codex_model_provider_info::OAuthCredentialsStore; use codex_secrets::LocalSecretsNamespace; use codex_secrets::SecretName; use codex_secrets::SecretScope; use codex_secrets::SecretsBackendKind; use codex_secrets::SecretsManager; -use sha2::Digest; -use sha2::Sha256; use super::process_shared_state; @@ -59,7 +58,7 @@ impl KeyringStore for TestKeyring { } } -/// Seeds a provider's encrypted gateway store with a test-only keyring passphrase. +/// Seeds a provider's configured gateway store, using a test passphrase for the keyring backend. /// Keep the returned manager alive while requests use this fixture. pub fn seed_gateway_auth( info: &ModelProviderInfo, @@ -85,43 +84,51 @@ pub fn seed_gateway_auth( let keyring = Arc::new(TestKeyring(Mutex::new(Some( "gateway-test-passphrase".to_string(), )))); - let secrets = SecretsManager::new_with_keyring_store_and_namespace( - runtime.codex_home.clone(), - SecretsBackendKind::Local, - keyring.clone(), - LocalSecretsNamespace::GatewayOAuth, - ); - // Match the credential identity used by GatewayAuthManager's encrypted store. - let mut digest = Sha256::new(); - digest.update(runtime.codex_home.to_string_lossy().as_bytes()); - digest.update([0]); - for value in [ - oauth.authorization_url.as_str(), - oauth.token_url.as_str(), - oauth.client_id.as_str(), - oauth.resource.as_deref().unwrap_or_default(), - ] { - digest.update(value.as_bytes()); - digest.update([0]); - } - for scope in &oauth.scopes { - digest.update(scope.as_bytes()); - digest.update([0]); - } - let name = SecretName::new(&format!("PROVIDER_OAUTH_{:X}", digest.finalize())) - .expect("gateway test credential name"); - secrets - .set(&SecretScope::Global, &name, &token.to_string()) - .expect("seed encrypted gateway credentials"); let manager = Arc::new( GatewayAuthManager::new( oauth.clone(), runtime.codex_home.clone(), runtime.auth_route_config.http_client_factory(), - keyring, + keyring.clone(), ) .expect("gateway test HTTP client"), ); + let credential_id = manager.credential_id(); + match oauth.credentials_store { + OAuthCredentialsStore::Keyring => { + let secrets = SecretsManager::new_with_keyring_store_and_namespace( + runtime.codex_home.clone(), + SecretsBackendKind::Local, + keyring, + LocalSecretsNamespace::GatewayOAuth, + ); + let digest = credential_id + .strip_prefix("provider-oauth|") + .expect("gateway credential ID"); + let name = SecretName::new(&format!("PROVIDER_OAUTH_{}", digest.to_ascii_uppercase())) + .expect("gateway test credential name"); + secrets + .set(&SecretScope::Global, &name, &token.to_string()) + .expect("seed encrypted gateway credentials"); + } + OAuthCredentialsStore::File => { + let dir = runtime.codex_home.join("secrets"); + std::fs::create_dir_all(&dir).expect("create test credentials directory"); + let file = dir.join("gateway_oauth.json"); + let mut tokens: std::collections::BTreeMap = + match std::fs::read(&file) { + Ok(bytes) => serde_json::from_slice(&bytes).expect("parse test gateway tokens"), + Err(err) if err.kind() == std::io::ErrorKind::NotFound => Default::default(), + Err(err) => panic!("read test gateway file: {err}"), + }; + tokens.insert(credential_id, token); + std::fs::write( + file, + serde_json::to_vec(&tokens).expect("serialize test gateway tokens"), + ) + .expect("seed test gateway file"); + } + } process_shared_state() .gateway_managers .lock() diff --git a/codex-rs/model-provider/src/shared_state_tests.rs b/codex-rs/model-provider/src/shared_state_tests.rs index 7d06dfea34..3979a9988f 100644 --- a/codex-rs/model-provider/src/shared_state_tests.rs +++ b/codex-rs/model-provider/src/shared_state_tests.rs @@ -20,6 +20,7 @@ use codex_login::default_client::ClientRedirectPolicy; use codex_login::default_client::create_client_for_route; use codex_model_provider_info::GatewayOAuthDelivery; use codex_model_provider_info::ModelProviderInfo; +use codex_model_provider_info::OAuthCredentialsStore; use codex_models_manager::manager::RefreshStrategy; use codex_protocol::error::CodexErrorDetails; use codex_protocol::protocol::CodexErrorInfo; @@ -408,7 +409,8 @@ async fn models_cache_is_reused_only_for_matching_gateway_configuration() { resource: None, scopes: vec![], redirect_port: None, - credentials_store: Default::default(), + // Keyring latency is outside the cache behavior under test. + credentials_store: OAuthCredentialsStore::File, delivery: GatewayOAuthDelivery::Header { name: "x-gateway-auth".into(), scheme: "Bearer".into(), @@ -672,7 +674,7 @@ async fn gateway_login_required_invalidates_the_last_successful_catalog() { resource: None, scopes: vec![], redirect_port: None, - credentials_store: Default::default(), + credentials_store: OAuthCredentialsStore::File, delivery: GatewayOAuthDelivery::Header { name: "x-gateway-auth".into(), scheme: "Bearer".into(),