mirror of
https://github.com/openai/codex.git
synced 2026-09-11 20:36:49 +00:00
## Why Switching providers or authentication could reuse a previous identity's model catalog, including its default service tier. An in-flight refresh could also overwrite a newer account's catalog after cache storage completed. ## What changed - Require a matching provider and auth identity for disk and in-memory catalog reuse. Treat legacy entries without an identity as cache misses and fall back to bundled metadata when the in-memory identity no longer matches. - Recheck identity after fetching and storing a catalog before publishing it. - Renew cache freshness only when the client version, identity, and ETag match the stored entry. - Update cache fixtures to include the configured provider and auth identity. ## Testing Add regression coverage for provider and auth switches, credential rotation, legacy and mismatched cache entries, conditional TTL renewal, and an account switch during cache storage. Verify that API-key requests do not inherit a ChatGPT catalog's default `service_tier`, while explicit tier selection still works. GitOrigin-RevId: e15d7ffff238d951d7a1c6a68381cbb479994146
84 lines
2.9 KiB
Rust
84 lines
2.9 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(),
|
|
)),
|
|
);
|
|
assert_eq!(
|
|
manager
|
|
.raw_model_catalog(
|
|
RefreshStrategy::OnlineIfUncached,
|
|
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);
|
|
}
|
|
}
|