mirror of
https://github.com/openai/codex.git
synced 2026-11-07 20:30:33 +00:00
## 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
84 lines
3.0 KiB
Rust
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);
|
|
}
|
|
}
|