Codex Handbook
cloud-config/src/cache_tests.rs 206 lines
use super::*;use codex_config::AbsolutePathBuf;use codex_config::CloudConfigFragment;use codex_config::CloudConfigTomlBundle;use codex_config::CloudRequirementsFragment;use codex_config::CloudRequirementsTomlBundle;use pretty_assertions::assert_eq;use std::path::Path;use tempfile::tempdir;fn test_bundle() -> CloudConfigBundle {    CloudConfigBundle {        config_toml: CloudConfigTomlBundle {            enterprise_managed: vec![CloudConfigFragment {                id: "cfg_1".to_string(),                name: "Base config".to_string(),                contents: "model = \"gpt-5\"".to_string(),            }],        },        requirements_toml: CloudRequirementsTomlBundle {            enterprise_managed: vec![CloudRequirementsFragment {                id: "req_1".to_string(),                name: "Base requirements".to_string(),                contents: "allowed_approval_policies = [\"never\"]".to_string(),            }],        },    }}fn signed_cache_file(    signed_payload: CloudConfigBundleCacheSignedPayload,) -> CloudConfigBundleCacheFile {    let payload_bytes = cache_payload_bytes(&signed_payload).expect("payload bytes");    CloudConfigBundleCacheFile {        signature: sign_cache_payload(&payload_bytes).expect("signature"),        signed_payload,    }}fn valid_signed_payload() -> CloudConfigBundleCacheSignedPayload {    let cached_at = Utc::now();    CloudConfigBundleCacheSignedPayload {        version: CLOUD_CONFIG_BUNDLE_CACHE_VERSION,        cached_at,        expires_at: cached_at + ChronoDuration::minutes(30),        chatgpt_user_id: Some("user-12345".to_string()),        account_id: Some("account-12345".to_string()),        bundle: test_bundle(),    }}fn write_cache_file(cache: &CloudConfigBundleCache, cache_file: &CloudConfigBundleCacheFile) {    std::fs::write(        cache.path(),        serde_json::to_vec_pretty(cache_file).expect("serialize cache"),    )    .expect("write cache");}fn create_test_cache(codex_home: &Path) -> CloudConfigBundleCache {    CloudConfigBundleCache::new(AbsolutePathBuf::resolve_path_against_base(codex_home, "/"))}#[tokio::test]async fn save_writes_signed_payload_and_loads_for_matching_identity() {    let codex_home = tempdir().expect("tempdir");    let cache = create_test_cache(codex_home.path());    let bundle = test_bundle();    cache        .save(            Some("user-12345".to_string()),            Some("account-12345".to_string()),            bundle.clone(),        )        .await        .expect("save cache");    let cache_file: CloudConfigBundleCacheFile =        serde_json::from_slice(&std::fs::read(cache.path()).expect("read cache"))            .expect("parse cache");    assert!(        cache_file.signed_payload.expires_at            <= cache_file.signed_payload.cached_at + ChronoDuration::minutes(60)    );    assert!(cache_file.signed_payload.expires_at > cache_file.signed_payload.cached_at);    assert_eq!(        cache_file,        signed_cache_file(CloudConfigBundleCacheSignedPayload {            version: CLOUD_CONFIG_BUNDLE_CACHE_VERSION,            cached_at: cache_file.signed_payload.cached_at,            expires_at: cache_file.signed_payload.expires_at,            chatgpt_user_id: Some("user-12345".to_string()),            account_id: Some("account-12345".to_string()),            bundle,        })    );    assert_eq!(        cache.load(Some("user-12345"), Some("account-12345")).await,        Ok(cache_file.signed_payload)    );}#[tokio::test]async fn load_rejects_missing_request_identity_before_reading_cache_file() {    let codex_home = tempdir().expect("tempdir");    let cache = create_test_cache(codex_home.path());    assert_eq!(        cache            .load(/*chatgpt_user_id*/ None, Some("account-12345"))            .await,        Err(CacheLoadStatus::AuthIdentityIncomplete)    );    assert_eq!(        cache.load(Some("user-12345"), /*account_id*/ None).await,        Err(CacheLoadStatus::AuthIdentityIncomplete)    );}#[tokio::test]async fn load_reports_missing_and_malformed_cache_files() {    let codex_home = tempdir().expect("tempdir");    let cache = create_test_cache(codex_home.path());    assert_eq!(        cache.load(Some("user-12345"), Some("account-12345")).await,        Err(CacheLoadStatus::CacheFileNotFound)    );    std::fs::write(cache.path(), "{").expect("write malformed cache");    assert!(matches!(        cache.load(Some("user-12345"), Some("account-12345")).await,        Err(CacheLoadStatus::CacheParseFailed(_))    ));}#[tokio::test]async fn load_rejects_tampered_payload() {    let codex_home = tempdir().expect("tempdir");    let cache = create_test_cache(codex_home.path());    let mut cache_file = signed_cache_file(valid_signed_payload());    cache_file        .signed_payload        .bundle        .requirements_toml        .enterprise_managed[0]        .contents = "allowed_approval_policies = [\"on-request\"]".to_string();    write_cache_file(&cache, &cache_file);    assert_eq!(        cache.load(Some("user-12345"), Some("account-12345")).await,        Err(CacheLoadStatus::CacheSignatureInvalid)    );}#[tokio::test]async fn load_rejects_cache_for_incomplete_or_different_identity() {    let codex_home = tempdir().expect("tempdir");    let cache = create_test_cache(codex_home.path());    let cache_file = signed_cache_file(valid_signed_payload());    write_cache_file(&cache, &cache_file);    assert_eq!(        cache.load(Some("user-99999"), Some("account-12345")).await,        Err(CacheLoadStatus::CacheIdentityMismatch)    );    let mut signed_payload = valid_signed_payload();    signed_payload.chatgpt_user_id = None;    write_cache_file(&cache, &signed_cache_file(signed_payload));    assert_eq!(        cache.load(Some("user-12345"), Some("account-12345")).await,        Err(CacheLoadStatus::CacheIdentityIncomplete)    );}#[tokio::test]async fn load_rejects_expired_cache() {    let codex_home = tempdir().expect("tempdir");    let cache = create_test_cache(codex_home.path());    let mut signed_payload = valid_signed_payload();    signed_payload.expires_at = Utc::now() - ChronoDuration::seconds(1);    write_cache_file(&cache, &signed_cache_file(signed_payload));    assert_eq!(        cache.load(Some("user-12345"), Some("account-12345")).await,        Err(CacheLoadStatus::CacheExpired)    );}#[tokio::test]async fn load_rejects_unsupported_cache_version() {    let codex_home = tempdir().expect("tempdir");    let cache = create_test_cache(codex_home.path());    let mut signed_payload = valid_signed_payload();    signed_payload.version = 2;    write_cache_file(&cache, &signed_cache_file(signed_payload));    assert_eq!(        cache.load(Some("user-12345"), Some("account-12345")).await,        Err(CacheLoadStatus::CacheVersionUnsupported(2))    );}