Codex Handbook
cloud-config/src/service_tests.rs 1027 lines
use super::*;use crate::backend::BundleClient;use crate::backend::BundleRequestError;use crate::backend::RetryableFailureKind;use crate::backend::bundle_from_response;use crate::cache::CLOUD_CONFIG_BUNDLE_CACHE_FILENAME;use crate::cache::CloudConfigBundleCache;use crate::metrics::bundle_shape_tag;use base64::Engine;use base64::engine::general_purpose::URL_SAFE_NO_PAD;use codex_backend_client::ConfigBundleResponse;use codex_backend_client::DeliveredTomlFragment;use codex_config::AbsolutePathBuf;use codex_config::CloudConfigFragment;use codex_config::CloudConfigTomlBundle;use codex_config::CloudRequirementsFragment;use codex_config::CloudRequirementsTomlBundle;use codex_config::types::AuthCredentialsStoreMode;use codex_login::AuthKeyringBackendKind;use pretty_assertions::assert_eq;use serde_json::json;use std::collections::VecDeque;use std::future::pending;use std::path::Path;use std::sync::atomic::AtomicUsize;use std::sync::atomic::Ordering;use tempfile::tempdir;fn write_auth_json(codex_home: &Path, value: serde_json::Value) -> std::io::Result<()> {    std::fs::write(codex_home.join("auth.json"), serde_json::to_string(&value)?)?;    Ok(())}fn create_test_cache(codex_home: &Path) -> CloudConfigBundleCache {    CloudConfigBundleCache::new(AbsolutePathBuf::resolve_path_against_base(codex_home, "/"))}async fn auth_manager_with_api_key() -> Arc<AuthManager> {    let tmp = tempdir().expect("tempdir");    let auth_json = json!({        "OPENAI_API_KEY": "sk-test-key",        "tokens": null,        "last_refresh": null,    });    write_auth_json(tmp.path(), auth_json).expect("write auth");    Arc::new(        AuthManager::new(            tmp.path().to_path_buf(),            /*enable_codex_api_key_env*/ false,            AuthCredentialsStoreMode::File,            /*chatgpt_base_url*/ None,            AuthKeyringBackendKind::default(),        )        .await,    )}async fn auth_manager_with_plan_and_identity(    plan_type: &str,    chatgpt_user_id: Option<&str>,    account_id: Option<&str>,) -> Arc<AuthManager> {    let tmp = tempdir().expect("tempdir");    write_auth_json(        tmp.path(),        chatgpt_auth_json(            plan_type,            chatgpt_user_id,            account_id,            "test-access-token",            "test-refresh-token",        ),    )    .expect("write auth");    Arc::new(        AuthManager::new(            tmp.path().to_path_buf(),            /*enable_codex_api_key_env*/ false,            AuthCredentialsStoreMode::File,            /*chatgpt_base_url*/ None,            AuthKeyringBackendKind::default(),        )        .await,    )}async fn auth_manager_with_plan(plan_type: &str) -> Arc<AuthManager> {    auth_manager_with_plan_and_identity(plan_type, Some("user-12345"), Some("account-12345")).await}fn chatgpt_auth_json(    plan_type: &str,    chatgpt_user_id: Option<&str>,    account_id: Option<&str>,    access_token: &str,    refresh_token: &str,) -> serde_json::Value {    chatgpt_auth_json_with_last_refresh(        plan_type,        chatgpt_user_id,        account_id,        access_token,        refresh_token,        "2025-01-01T00:00:00Z",    )}fn chatgpt_auth_json_with_last_refresh(    plan_type: &str,    chatgpt_user_id: Option<&str>,    account_id: Option<&str>,    access_token: &str,    refresh_token: &str,    last_refresh: &str,) -> serde_json::Value {    chatgpt_auth_json_with_mode(        plan_type,        chatgpt_user_id,        account_id,        access_token,        refresh_token,        last_refresh,        /*auth_mode*/ None,    )}fn chatgpt_auth_json_with_mode(    plan_type: &str,    chatgpt_user_id: Option<&str>,    account_id: Option<&str>,    access_token: &str,    refresh_token: &str,    last_refresh: &str,    auth_mode: Option<&str>,) -> serde_json::Value {    let header = json!({ "alg": "none", "typ": "JWT" });    let auth_payload = json!({        "chatgpt_plan_type": plan_type,        "chatgpt_user_id": chatgpt_user_id,        "user_id": chatgpt_user_id,    });    let payload = json!({        "email": "user@example.com",        "https://api.openai.com/auth": auth_payload,    });    let header_b64 = URL_SAFE_NO_PAD.encode(serde_json::to_vec(&header).expect("header"));    let payload_b64 = URL_SAFE_NO_PAD.encode(serde_json::to_vec(&payload).expect("payload"));    let signature_b64 = URL_SAFE_NO_PAD.encode(b"sig");    let fake_jwt = format!("{header_b64}.{payload_b64}.{signature_b64}");    let mut auth_json = json!({        "OPENAI_API_KEY": null,        "tokens": {            "id_token": fake_jwt,            "access_token": access_token,            "refresh_token": refresh_token,            "account_id": account_id,        },        "last_refresh": last_refresh,    });    if let Some(auth_mode) = auth_mode {        auth_json["auth_mode"] = serde_json::Value::String(auth_mode.to_string());    }    auth_json}fn test_bundle() -> CloudConfigBundle {    CloudConfigBundle {        config_toml: CloudConfigTomlBundle {            enterprise_managed: vec![test_config_fragment()],        },        requirements_toml: CloudRequirementsTomlBundle {            enterprise_managed: vec![test_requirements_fragment()],        },    }}fn test_config_fragment() -> CloudConfigFragment {    CloudConfigFragment {        id: "cfg_1".to_string(),        name: "Base config".to_string(),        contents: "model = \"gpt-5\"".to_string(),    }}fn test_requirements_fragment() -> CloudRequirementsFragment {    CloudRequirementsFragment {        id: "req_1".to_string(),        name: "Base requirements".to_string(),        contents: "allowed_approval_policies = [\"never\"]".to_string(),    }}fn invalid_config_bundle() -> CloudConfigBundle {    CloudConfigBundle {        config_toml: CloudConfigTomlBundle {            enterprise_managed: vec![CloudConfigFragment {                id: "cfg_invalid".to_string(),                name: "Invalid config".to_string(),                contents: "model = [".to_string(),            }],        },        requirements_toml: CloudRequirementsTomlBundle::default(),    }}fn request_error() -> BundleRequestError {    BundleRequestError::Retryable(RetryableFailureKind::Request { status_code: None })}struct StaticBundleClient {    bundle: CloudConfigBundle,    request_count: AtomicUsize,}impl StaticBundleClient {    fn new(bundle: CloudConfigBundle) -> Self {        Self {            bundle,            request_count: AtomicUsize::new(0),        }    }}impl BundleClient for StaticBundleClient {    async fn get_bundle(&self, _auth: &CodexAuth) -> Result<CloudConfigBundle, BundleRequestError> {        self.request_count.fetch_add(1, Ordering::SeqCst);        Ok(self.bundle.clone())    }}struct PendingBundleClient;impl BundleClient for PendingBundleClient {    async fn get_bundle(&self, _auth: &CodexAuth) -> Result<CloudConfigBundle, BundleRequestError> {        pending::<()>().await;        Ok(CloudConfigBundle::default())    }}struct SequenceBundleClient {    responses: tokio::sync::Mutex<VecDeque<Result<CloudConfigBundle, BundleRequestError>>>,    request_count: AtomicUsize,}impl SequenceBundleClient {    fn new(responses: Vec<Result<CloudConfigBundle, BundleRequestError>>) -> Self {        Self {            responses: tokio::sync::Mutex::new(VecDeque::from(responses)),            request_count: AtomicUsize::new(0),        }    }}impl BundleClient for SequenceBundleClient {    async fn get_bundle(&self, _auth: &CodexAuth) -> Result<CloudConfigBundle, BundleRequestError> {        self.request_count.fetch_add(1, Ordering::SeqCst);        let mut responses = self.responses.lock().await;        responses            .pop_front()            .unwrap_or_else(|| Ok(CloudConfigBundle::default()))    }}struct TokenBundleClient {    expected_token: String,    bundle: CloudConfigBundle,    request_count: AtomicUsize,}impl BundleClient for TokenBundleClient {    async fn get_bundle(&self, auth: &CodexAuth) -> Result<CloudConfigBundle, BundleRequestError> {        self.request_count.fetch_add(1, Ordering::SeqCst);        if matches!(            auth.get_token().as_deref(),            Ok(token) if token == self.expected_token.as_str()        ) {            Ok(self.bundle.clone())        } else {            Err(BundleRequestError::Unauthorized {                status_code: Some(401),                message: "GET /config/bundle failed: 401".to_string(),            })        }    }}struct UnauthorizedBundleClient {    message: String,    request_count: AtomicUsize,}impl BundleClient for UnauthorizedBundleClient {    async fn get_bundle(&self, _auth: &CodexAuth) -> Result<CloudConfigBundle, BundleRequestError> {        self.request_count.fetch_add(1, Ordering::SeqCst);        Err(BundleRequestError::Unauthorized {            status_code: Some(401),            message: self.message.clone(),        })    }}#[test]fn bundle_shape_tag_describes_sorted_enterprise_sources() {    assert_eq!(bundle_shape_tag(/*bundle*/ None), "none");    assert_eq!(        bundle_shape_tag(Some(&CloudConfigBundle::default())),        "empty"    );    assert_eq!(        bundle_shape_tag(Some(&CloudConfigBundle {            config_toml: CloudConfigTomlBundle {                enterprise_managed: vec![test_config_fragment()],            },            requirements_toml: CloudRequirementsTomlBundle::default(),        })),        "enterprise_config"    );    assert_eq!(        bundle_shape_tag(Some(&CloudConfigBundle {            config_toml: CloudConfigTomlBundle::default(),            requirements_toml: CloudRequirementsTomlBundle {                enterprise_managed: vec![test_requirements_fragment()],            },        })),        "enterprise_requirements"    );    assert_eq!(        bundle_shape_tag(Some(&CloudConfigBundle {            config_toml: CloudConfigTomlBundle {                enterprise_managed: vec![test_config_fragment()],            },            requirements_toml: CloudRequirementsTomlBundle {                enterprise_managed: vec![test_requirements_fragment()],            },        })),        "enterprise_config,enterprise_requirements"    );}#[tokio::test]async fn get_bundle_skips_non_chatgpt_auth() {    let fetcher = Arc::new(StaticBundleClient::new(test_bundle()));    let codex_home = tempdir().expect("tempdir");    let service = CloudConfigBundleService::new(        auth_manager_with_api_key().await,        fetcher.clone(),        codex_home.path().to_path_buf(),        CLOUD_CONFIG_BUNDLE_TIMEOUT,    );    assert_eq!(service.load_startup_bundle().await, Ok(None));    assert_eq!(fetcher.request_count.load(Ordering::SeqCst), 0);}#[tokio::test]async fn get_bundle_skips_individual_plan() {    let fetcher = Arc::new(StaticBundleClient::new(test_bundle()));    let codex_home = tempdir().expect("tempdir");    let service = CloudConfigBundleService::new(        auth_manager_with_plan("pro").await,        fetcher.clone(),        codex_home.path().to_path_buf(),        CLOUD_CONFIG_BUNDLE_TIMEOUT,    );    assert_eq!(service.load_startup_bundle().await, Ok(None));    assert_eq!(fetcher.request_count.load(Ordering::SeqCst), 0);}#[tokio::test]async fn get_bundle_allows_eligible_workspace_plans_and_writes_cache() {    for plan_type in [        "business",        "enterprise_cbp_usage_based",        "enterprise",        "hc",        "edu",        "education",    ] {        let bundle = test_bundle();        let fetcher = Arc::new(StaticBundleClient::new(bundle.clone()));        let codex_home = tempdir().expect("tempdir");        let service = CloudConfigBundleService::new(            auth_manager_with_plan(plan_type).await,            fetcher.clone(),            codex_home.path().to_path_buf(),            CLOUD_CONFIG_BUNDLE_TIMEOUT,        );        assert_eq!(            service.load_startup_bundle().await,            Ok(Some(bundle)),            "plan_type: {plan_type}"        );        assert_eq!(            fetcher.request_count.load(Ordering::SeqCst),            1,            "plan_type: {plan_type}"        );        assert!(            codex_home                .path()                .join(CLOUD_CONFIG_BUNDLE_CACHE_FILENAME)                .exists(),            "plan_type: {plan_type}"        );    }}#[tokio::test]async fn get_bundle_skips_team_like_usage_based_plan() {    let fetcher = Arc::new(StaticBundleClient::new(test_bundle()));    let codex_home = tempdir().expect("tempdir");    let service = CloudConfigBundleService::new(        auth_manager_with_plan("self_serve_business_usage_based").await,        fetcher.clone(),        codex_home.path().to_path_buf(),        CLOUD_CONFIG_BUNDLE_TIMEOUT,    );    assert_eq!(service.load_startup_bundle().await, Ok(None));    assert_eq!(fetcher.request_count.load(Ordering::SeqCst), 0);}#[tokio::test]async fn get_bundle_rejects_invalid_remote_bundle_before_cache_write() {    let codex_home = tempdir().expect("tempdir");    let fetcher = Arc::new(StaticBundleClient::new(invalid_config_bundle()));    let service = CloudConfigBundleService::new(        auth_manager_with_plan("business").await,        fetcher.clone(),        codex_home.path().to_path_buf(),        CLOUD_CONFIG_BUNDLE_TIMEOUT,    );    let err = service        .load_startup_bundle()        .await        .expect_err("invalid remote bundle should fail closed");    assert_eq!(err.code(), CloudConfigBundleLoadErrorCode::InvalidBundle);    assert!(err.to_string().contains("invalid cloud config bundle"));    assert_eq!(fetcher.request_count.load(Ordering::SeqCst), 1);    assert!(        !codex_home            .path()            .join(CLOUD_CONFIG_BUNDLE_CACHE_FILENAME)            .exists()    );}#[tokio::test]async fn get_bundle_ignores_invalid_cache_and_refetches() {    let codex_home = tempdir().expect("tempdir");    let cache = create_test_cache(codex_home.path());    cache        .save(            Some("user-12345".to_string()),            Some("account-12345".to_string()),            invalid_config_bundle(),        )        .await        .expect("write invalid cache");    let replacement_bundle = test_bundle();    let fetcher = Arc::new(StaticBundleClient::new(replacement_bundle.clone()));    let service = CloudConfigBundleService::new(        auth_manager_with_plan("business").await,        fetcher.clone(),        codex_home.path().to_path_buf(),        CLOUD_CONFIG_BUNDLE_TIMEOUT,    );    assert_eq!(        service.load_startup_bundle().await,        Ok(Some(replacement_bundle.clone()))    );    assert_eq!(fetcher.request_count.load(Ordering::SeqCst), 1);    assert_eq!(        cache            .load(Some("user-12345"), Some("account-12345"))            .await            .expect("load refreshed cache")            .bundle,        replacement_bundle    );}#[tokio::test]async fn get_bundle_empty_response_is_success_and_cached() {    let codex_home = tempdir().expect("tempdir");    let fetcher = Arc::new(StaticBundleClient::new(CloudConfigBundle::default()));    let service = CloudConfigBundleService::new(        auth_manager_with_plan("enterprise").await,        fetcher.clone(),        codex_home.path().to_path_buf(),        CLOUD_CONFIG_BUNDLE_TIMEOUT,    );    assert_eq!(service.load_startup_bundle().await, Ok(None));    assert_eq!(fetcher.request_count.load(Ordering::SeqCst), 1);    assert!(        codex_home            .path()            .join(CLOUD_CONFIG_BUNDLE_CACHE_FILENAME)            .exists()    );}#[tokio::test]async fn get_bundle_uses_cache_when_valid() {    let bundle = test_bundle();    let codex_home = tempdir().expect("tempdir");    let prime_service = CloudConfigBundleService::new(        auth_manager_with_plan("business").await,        Arc::new(StaticBundleClient::new(bundle.clone())),        codex_home.path().to_path_buf(),        CLOUD_CONFIG_BUNDLE_TIMEOUT,    );    let _ = prime_service.load_startup_bundle().await;    let fetcher = Arc::new(SequenceBundleClient::new(vec![Err(request_error())]));    let service = CloudConfigBundleService::new(        auth_manager_with_plan("business").await,        fetcher.clone(),        codex_home.path().to_path_buf(),        CLOUD_CONFIG_BUNDLE_TIMEOUT,    );    assert_eq!(service.load_startup_bundle().await, Ok(Some(bundle)));    assert_eq!(fetcher.request_count.load(Ordering::SeqCst), 0);}#[tokio::test]async fn get_bundle_ignores_cache_for_different_auth_identity() {    let codex_home = tempdir().expect("tempdir");    let prime_service = CloudConfigBundleService::new(        auth_manager_with_plan_and_identity("business", Some("user-12345"), Some("account-12345"))            .await,        Arc::new(StaticBundleClient::new(test_bundle())),        codex_home.path().to_path_buf(),        CLOUD_CONFIG_BUNDLE_TIMEOUT,    );    let _ = prime_service.load_startup_bundle().await;    let replacement_bundle = CloudConfigBundle {        config_toml: CloudConfigTomlBundle::default(),        requirements_toml: CloudRequirementsTomlBundle {            enterprise_managed: vec![CloudRequirementsFragment {                id: "req_2".to_string(),                name: "Replacement requirements".to_string(),                contents: "allowed_approval_policies = [\"on-request\"]".to_string(),            }],        },    };    let fetcher = Arc::new(SequenceBundleClient::new(vec![Ok(        replacement_bundle.clone()    )]));    let service = CloudConfigBundleService::new(        auth_manager_with_plan_and_identity("business", Some("user-99999"), Some("account-12345"))            .await,        fetcher.clone(),        codex_home.path().to_path_buf(),        CLOUD_CONFIG_BUNDLE_TIMEOUT,    );    assert_eq!(        service.load_startup_bundle().await,        Ok(Some(replacement_bundle))    );    assert_eq!(fetcher.request_count.load(Ordering::SeqCst), 1);}#[tokio::test(start_paused = true)]async fn get_bundle_times_out() {    let codex_home = tempdir().expect("tempdir");    let service = CloudConfigBundleService::new(        auth_manager_with_plan("enterprise").await,        Arc::new(PendingBundleClient),        codex_home.path().to_path_buf(),        CLOUD_CONFIG_BUNDLE_TIMEOUT,    );    let handle = tokio::spawn(async move { service.load_startup_bundle_with_timeout().await });    tokio::time::advance(CLOUD_CONFIG_BUNDLE_TIMEOUT + Duration::from_millis(1)).await;    let result = handle.await.expect("cloud config bundle task");    let err = result.expect_err("cloud config bundle timeout should fail closed");    assert!(        err.to_string()            .contains("timed out waiting for cloud config bundle")    );}#[tokio::test(start_paused = true)]async fn get_bundle_retries_until_success() {    let fetcher = Arc::new(SequenceBundleClient::new(vec![        Err(request_error()),        Ok(test_bundle()),    ]));    let codex_home = tempdir().expect("tempdir");    let service = CloudConfigBundleService::new(        auth_manager_with_plan("business").await,        fetcher.clone(),        codex_home.path().to_path_buf(),        CLOUD_CONFIG_BUNDLE_TIMEOUT,    );    let handle = tokio::spawn(async move { service.load_startup_bundle().await });    tokio::task::yield_now().await;    tokio::time::advance(Duration::from_secs(1)).await;    assert_eq!(handle.await.expect("bundle task"), Ok(Some(test_bundle())));    assert_eq!(fetcher.request_count.load(Ordering::SeqCst), 2);}#[tokio::test]async fn get_bundle_recovers_after_unauthorized_reload() {    let auth_home = tempdir().expect("tempdir");    write_auth_json(        auth_home.path(),        chatgpt_auth_json_with_last_refresh(            "business",            Some("user-12345"),            Some("account-12345"),            "stale-access-token",            "test-refresh-token",            // Keep auth "fresh" so the first request hits unauthorized recovery            // instead of AuthManager::auth() proactively reloading from disk.            "3025-01-01T00:00:00Z",        ),    )    .expect("write initial auth");    let auth_manager = Arc::new(        AuthManager::new(            auth_home.path().to_path_buf(),            /*enable_codex_api_key_env*/ false,            AuthCredentialsStoreMode::File,            /*chatgpt_base_url*/ None,            AuthKeyringBackendKind::default(),        )        .await,    );    write_auth_json(        auth_home.path(),        chatgpt_auth_json_with_last_refresh(            "business",            Some("user-12345"),            Some("account-12345"),            "fresh-access-token",            "test-refresh-token",            "3025-01-01T00:00:00Z",        ),    )    .expect("write refreshed auth");    let fetcher = Arc::new(TokenBundleClient {        expected_token: "fresh-access-token".to_string(),        bundle: test_bundle(),        request_count: AtomicUsize::new(0),    });    let codex_home = tempdir().expect("tempdir");    let service = CloudConfigBundleService::new(        auth_manager,        fetcher.clone(),        codex_home.path().to_path_buf(),        CLOUD_CONFIG_BUNDLE_TIMEOUT,    );    assert_eq!(service.load_startup_bundle().await, Ok(Some(test_bundle())));    assert_eq!(fetcher.request_count.load(Ordering::SeqCst), 2);}#[tokio::test]async fn get_bundle_recovers_after_unauthorized_reload_updates_cache_identity() {    let auth_home = tempdir().expect("tempdir");    write_auth_json(        auth_home.path(),        chatgpt_auth_json_with_last_refresh(            "business",            Some("user-12345"),            Some("account-12345"),            "stale-access-token",            "test-refresh-token",            "3025-01-01T00:00:00Z",        ),    )    .expect("write initial auth");    let auth_manager = Arc::new(        AuthManager::new(            auth_home.path().to_path_buf(),            /*enable_codex_api_key_env*/ false,            AuthCredentialsStoreMode::File,            /*chatgpt_base_url*/ None,            AuthKeyringBackendKind::default(),        )        .await,    );    write_auth_json(        auth_home.path(),        chatgpt_auth_json_with_last_refresh(            "business",            Some("user-99999"),            Some("account-12345"),            "fresh-access-token",            "test-refresh-token",            "3025-01-01T00:00:00Z",        ),    )    .expect("write refreshed auth");    let fetcher = Arc::new(TokenBundleClient {        expected_token: "fresh-access-token".to_string(),        bundle: test_bundle(),        request_count: AtomicUsize::new(0),    });    let codex_home = tempdir().expect("tempdir");    let service = CloudConfigBundleService::new(        auth_manager,        fetcher.clone(),        codex_home.path().to_path_buf(),        CLOUD_CONFIG_BUNDLE_TIMEOUT,    );    assert_eq!(service.load_startup_bundle().await, Ok(Some(test_bundle())));    let cache = create_test_cache(codex_home.path());    assert_eq!(        cache            .load(Some("user-99999"), Some("account-12345"))            .await            .expect("load cache")            .bundle,        test_bundle()    );    assert_eq!(fetcher.request_count.load(Ordering::SeqCst), 2);}#[tokio::test]async fn get_bundle_surfaces_auth_recovery_message() {    let auth_home = tempdir().expect("tempdir");    write_auth_json(        auth_home.path(),        chatgpt_auth_json(            "enterprise",            Some("user-12345"),            Some("account-12345"),            "stale-access-token",            "test-refresh-token",        ),    )    .expect("write auth");    let auth_manager = Arc::new(        AuthManager::new(            auth_home.path().to_path_buf(),            /*enable_codex_api_key_env*/ false,            AuthCredentialsStoreMode::File,            /*chatgpt_base_url*/ None,            AuthKeyringBackendKind::default(),        )        .await,    );    write_auth_json(        auth_home.path(),        chatgpt_auth_json(            "enterprise",            Some("user-12345"),            Some("account-99999"),            "fresh-access-token",            "test-refresh-token",        ),    )    .expect("write mismatched auth");    let fetcher = Arc::new(UnauthorizedBundleClient {        message: "GET /config/bundle failed: 401".to_string(),        request_count: AtomicUsize::new(0),    });    let codex_home = tempdir().expect("tempdir");    let service = CloudConfigBundleService::new(        auth_manager,        fetcher.clone(),        codex_home.path().to_path_buf(),        CLOUD_CONFIG_BUNDLE_TIMEOUT,    );    let err = service        .load_startup_bundle()        .await        .expect_err("cloud config bundle should surface auth recovery errors");    assert_eq!(        err,        CloudConfigBundleLoadError::new(            CloudConfigBundleLoadErrorCode::Auth,            Some(401),            "Your access token could not be refreshed because you have since logged out or signed in to another account. Please sign in again.",        )    );    assert_eq!(fetcher.request_count.load(Ordering::SeqCst), 1);}#[tokio::test]async fn get_bundle_unauthorized_without_recovery_uses_generic_message() {    let auth_home = tempdir().expect("tempdir");    write_auth_json(        auth_home.path(),        chatgpt_auth_json_with_mode(            "enterprise",            Some("user-12345"),            Some("account-12345"),            "test-access-token",            "test-refresh-token",            "2025-01-01T00:00:00Z",            Some("chatgptAuthTokens"),        ),    )    .expect("write auth");    let auth_manager = Arc::new(        AuthManager::new(            auth_home.path().to_path_buf(),            /*enable_codex_api_key_env*/ false,            AuthCredentialsStoreMode::File,            /*chatgpt_base_url*/ None,            AuthKeyringBackendKind::default(),        )        .await,    );    let fetcher = Arc::new(UnauthorizedBundleClient {        message:            "GET https://chatgpt.com/backend-api/wham/config/bundle failed: 401; content-type=text/html; body=<html>nope</html>"                .to_string(),        request_count: AtomicUsize::new(0),    });    let codex_home = tempdir().expect("tempdir");    let service = CloudConfigBundleService::new(        auth_manager,        fetcher.clone(),        codex_home.path().to_path_buf(),        CLOUD_CONFIG_BUNDLE_TIMEOUT,    );    let err = service        .load_startup_bundle()        .await        .expect_err("cloud config bundle should fail closed");    assert_eq!(        err,        CloudConfigBundleLoadError::new(            CloudConfigBundleLoadErrorCode::Auth,            Some(401),            CLOUD_CONFIG_BUNDLE_AUTH_RECOVERY_FAILED_MESSAGE,        )    );    assert_eq!(fetcher.request_count.load(Ordering::SeqCst), 1);}#[tokio::test]async fn get_bundle_does_not_use_cache_when_auth_identity_is_incomplete() {    let codex_home = tempdir().expect("tempdir");    let prime_service = CloudConfigBundleService::new(        auth_manager_with_plan("business").await,        Arc::new(StaticBundleClient::new(test_bundle())),        codex_home.path().to_path_buf(),        CLOUD_CONFIG_BUNDLE_TIMEOUT,    );    let _ = prime_service.load_startup_bundle().await;    let replacement_bundle = CloudConfigBundle {        config_toml: CloudConfigTomlBundle::default(),        requirements_toml: CloudRequirementsTomlBundle {            enterprise_managed: vec![CloudRequirementsFragment {                id: "req_2".to_string(),                name: "Replacement requirements".to_string(),                contents: "allowed_approval_policies = [\"on-request\"]".to_string(),            }],        },    };    let fetcher = Arc::new(SequenceBundleClient::new(vec![Ok(        replacement_bundle.clone()    )]));    let service = CloudConfigBundleService::new(        auth_manager_with_plan_and_identity(            "business",            /*chatgpt_user_id*/ None,            Some("account-12345"),        )        .await,        fetcher.clone(),        codex_home.path().to_path_buf(),        CLOUD_CONFIG_BUNDLE_TIMEOUT,    );    assert_eq!(        service.load_startup_bundle().await,        Ok(Some(replacement_bundle))    );    assert_eq!(fetcher.request_count.load(Ordering::SeqCst), 1);}#[tokio::test(start_paused = true)]async fn get_bundle_stops_after_max_retries() {    let fetcher = Arc::new(SequenceBundleClient::new(vec![        Err(request_error());        CLOUD_CONFIG_BUNDLE_MAX_ATTEMPTS    ]));    let codex_home = tempdir().expect("tempdir");    let service = CloudConfigBundleService::new(        auth_manager_with_plan("enterprise").await,        fetcher.clone(),        codex_home.path().to_path_buf(),        CLOUD_CONFIG_BUNDLE_TIMEOUT,    );    let handle = tokio::spawn(async move { service.load_startup_bundle().await });    tokio::task::yield_now().await;    tokio::time::advance(Duration::from_secs(5)).await;    tokio::task::yield_now().await;    let err = handle        .await        .expect("cloud config bundle task")        .expect_err("cloud config bundle retry exhaustion should fail closed");    assert_eq!(err.to_string(), CLOUD_CONFIG_BUNDLE_LOAD_FAILED_MESSAGE);    assert_eq!(err.code(), CloudConfigBundleLoadErrorCode::RequestFailed);    assert_eq!(        fetcher.request_count.load(Ordering::SeqCst),        CLOUD_CONFIG_BUNDLE_MAX_ATTEMPTS    );}#[tokio::test]async fn refresh_from_remote_updates_cached_bundle() {    let replacement_bundle = CloudConfigBundle {        config_toml: CloudConfigTomlBundle::default(),        requirements_toml: CloudRequirementsTomlBundle {            enterprise_managed: vec![CloudRequirementsFragment {                id: "req_2".to_string(),                name: "Replacement requirements".to_string(),                contents: "allowed_approval_policies = [\"on-request\"]".to_string(),            }],        },    };    let codex_home = tempdir().expect("tempdir");    let fetcher = Arc::new(SequenceBundleClient::new(vec![        Ok(test_bundle()),        Ok(replacement_bundle.clone()),    ]));    let service = CloudConfigBundleService::new(        auth_manager_with_plan("business").await,        fetcher,        codex_home.path().to_path_buf(),        CLOUD_CONFIG_BUNDLE_TIMEOUT,    );    assert_eq!(service.load_startup_bundle().await, Ok(Some(test_bundle())));    assert!(service.refresh_cache_once().await);    let cache = create_test_cache(codex_home.path());    let signed_payload = cache        .load(Some("user-12345"), Some("account-12345"))        .await        .expect("load cache");    assert_eq!(signed_payload.bundle, replacement_bundle);}#[test]fn bundle_response_conversion_preserves_fragment_order() {    let response = ConfigBundleResponse {        config_toml: Some(Some(Box::new(codex_backend_client::DeliveredConfigToml {            enterprise_managed: Some(Some(vec![                DeliveredTomlFragment::new(                    "cfg_high".to_string(),                    "High config".to_string(),                    "model = \"high\"".to_string(),                ),                DeliveredTomlFragment::new(                    "cfg_low".to_string(),                    "Low config".to_string(),                    "model = \"low\"".to_string(),                ),            ])),        }))),        requirements_toml: Some(Some(Box::new(            codex_backend_client::DeliveredRequirementsToml {                enterprise_managed: Some(Some(vec![DeliveredTomlFragment::new(                    "req_high".to_string(),                    "High requirements".to_string(),                    "allowed_approval_policies = [\"never\"]".to_string(),                )])),            },        ))),    };    assert_eq!(        bundle_from_response(response),        CloudConfigBundle {            config_toml: CloudConfigTomlBundle {                enterprise_managed: vec![                    CloudConfigFragment {                        id: "cfg_high".to_string(),                        name: "High config".to_string(),                        contents: "model = \"high\"".to_string(),                    },                    CloudConfigFragment {                        id: "cfg_low".to_string(),                        name: "Low config".to_string(),                        contents: "model = \"low\"".to_string(),                    },                ],            },            requirements_toml: CloudRequirementsTomlBundle {                enterprise_managed: vec![CloudRequirementsFragment {                    id: "req_high".to_string(),                    name: "High requirements".to_string(),                    contents: "allowed_approval_policies = [\"never\"]".to_string(),                }],            },        }    );}#[test]fn bundle_response_conversion_treats_missing_sections_as_empty() {    assert_eq!(        bundle_from_response(ConfigBundleResponse::new()),        CloudConfigBundle::default()    );}