Files
codex/codex-rs/models-manager/src/cache_identity_tests.rs
T
evanz-oai 44ccf39457 Preserve model catalogs when starting child agents (#60049)
## Why

Child startup can lose remote model capabilities such as `tool_search` when the selected catalog is uninitialized or disk data is older than the parent's in-memory snapshot.

## What changed

- Initialize the final provider, auth, and originator catalog before resolving a child's startup model, then read it from memory.
- Preserve loaded catalogs, including empty responses and updates that failed to persist, without overwriting concurrent refreshes.
- Reuse catalogs across retry and WebSocket transport overrides while retaining routing and header distinctions.

## Testing

Add regression coverage for child tool search with warm, cold, and disabled caches; failed cache writes; concurrent refreshes; empty catalogs; and Guardian reviewer catalog reuse.

GitOrigin-RevId: 01d995a65b623cc7d6f26e2dc9b6e8cf380de782
2026-11-07 03:16:52 +00:00

84 lines
3.0 KiB
Rust

//! Coverage for cache identity validation and conditional freshness updates.
use super::*;
use pretty_assertions::assert_eq;
#[tokio::test]
async fn mismatched_and_legacy_cache_entries_fetch_the_current_catalog() {
for identity in [None, Some("other-provider-or-account".to_string())] {
let stale = remote_model("stale", "Stale", /*priority*/ 0);
let current = remote_model("current", "Current", /*priority*/ 0);
let cache = TestModelsCache::with_entry(ModelsCacheEntry {
fetched_at: Utc::now(),
etag: Some("old-etag".into()),
client_version: Some(crate::client_version_to_whole()),
identity,
models: vec![stale],
});
let endpoint = TestModelsEndpoint::new(vec![vec![current.clone()]]);
let manager = OpenAiModelsManager::new_with_cache(
cache.clone(),
endpoint.clone(),
Some(AuthManager::from_auth_for_testing(
CodexAuth::create_dummy_chatgpt_auth_for_testing(),
)),
);
manager
.ensure_initialized(DEFAULT_HTTP_CLIENT_FACTORY)
.await;
assert_eq!(
manager
.raw_model_catalog(RefreshStrategy::InMemory, DEFAULT_HTTP_CLIENT_FACTORY)
.await
.models,
vec![current.clone()]
);
assert_eq!(endpoint.fetch_count(), 1);
}
}
#[tokio::test]
async fn matching_etag_cannot_renew_a_different_identity_or_catalog() {
let home = tempdir().unwrap();
let cache = FileModelsCache::new(home.path().join(MODEL_CACHE_FILE), DEFAULT_MODEL_CACHE_TTL);
let expected = ModelsCacheEntry {
fetched_at: Utc::now() - chrono::Duration::hours(1),
etag: Some("etag".into()),
client_version: Some(crate::client_version_to_whole()),
identity: Some("first-account".into()),
models: vec![remote_model("model", "Model", /*priority*/ 0)],
};
for (key, etag, version) in [
("other-account", "etag", crate::client_version_to_whole()),
(
"first-account",
"new-etag",
crate::client_version_to_whole(),
),
("first-account", "etag", "other-version".into()),
] {
let stored = ModelsCacheEntry {
identity: Some(key.into()),
etag: Some(etag.into()),
client_version: Some(version),
..expected.clone()
};
cache.store(&stored).await.unwrap();
cache
.refresh_ttl(
expected.client_version.as_deref().unwrap(),
expected.identity.as_deref().unwrap(),
expected.etag.as_deref().unwrap(),
)
.await
.unwrap();
let actual: ModelsCacheEntry = serde_json::from_slice(
&tokio::fs::read(home.path().join(MODEL_CACHE_FILE))
.await
.unwrap(),
)
.unwrap();
assert_eq!(actual, stored);
}
}