use super::super::{
CachePreference, MAX_CUSTOM_PROVIDER_CATALOG_WORKERS, McPaths, ModelsDevLookup,
load_aggregated_catalog, load_aggregated_catalog_with_models_dev,
};
use super::{
spawn_counted_model_catalog_server, spawn_delayed_model_catalog_server,
spawn_model_catalog_server,
};
use crate::config::CustomProviderConfig;
use std::{fs, sync::Arc};
use tempfile::TempDir;
#[test]
fn aggregated_catalog_reports_settings_and_auth_read_failures() {
let temp = TempDir::new().unwrap();
let paths = McPaths::from_root(temp.path().join("mc"));
fs::create_dir_all(&paths.root).unwrap();
fs::write(&paths.settings_file, "not json").unwrap();
fs::write(&paths.auth_file, "not json").unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut permissions = fs::metadata(&paths.auth_file).unwrap().permissions();
permissions.set_mode(0o600);
fs::set_permissions(&paths.auth_file, permissions).unwrap();
}
let catalog = load_aggregated_catalog(&paths, CachePreference::AllowStale);
assert!(
catalog
.notices
.iter()
.any(|notice| notice.contains("settings.json could not be read"))
);
assert!(
catalog
.notices
.iter()
.any(|notice| notice.contains("auth.json could not be read"))
);
let notices = catalog.notices.join("\n");
assert!(!notices.contains("sk-"));
assert!(!notices.contains("access_token"));
}
#[test]
fn aggregated_catalog_reuses_seeded_models_dev_lookup_across_custom_providers() {
let temp = TempDir::new().unwrap();
let paths = McPaths::from_root(temp.path().join("mc"));
let (provider_a_url, provider_a_handle) = spawn_model_catalog_server("shared-model");
let (provider_b_url, provider_b_handle) = spawn_model_catalog_server("shared-model");
crate::config::write_settings(
&paths,
&crate::config::Settings {
custom_providers: std::collections::BTreeMap::from([
(
"provider-a".to_string(),
CustomProviderConfig {
label: "Provider A".to_string(),
base_url: provider_a_url,
fast_mode: None,
api_key_env_var: None,
models_dev_provider: Some("provider-a-meta".to_string()),
use_responses_endpoint: false,
supports_text_verbosity: false,
reasoning_protocol: crate::config::CustomReasoningProtocol::default(),
extra_models: Vec::new(),
request_headers: Default::default(),
},
),
(
"provider-b".to_string(),
CustomProviderConfig {
label: "Provider B".to_string(),
base_url: provider_b_url,
fast_mode: None,
api_key_env_var: None,
models_dev_provider: Some("provider-b-meta".to_string()),
use_responses_endpoint: false,
supports_text_verbosity: false,
reasoning_protocol: crate::config::CustomReasoningProtocol::default(),
extra_models: Vec::new(),
request_headers: Default::default(),
},
),
]),
..Default::default()
},
)
.unwrap();
let mut models_dev = ModelsDevLookup {
attempted: true,
value: Some(serde_json::json!({
"provider-a-meta": {"models": {"shared-model": {"description": "provider a metadata", "limit": {"context": 111_u64}}}},
"provider-b-meta": {"models": {"shared-model": {"description": "provider b metadata", "limit": {"context": 222_u64}}}}
})),
};
let catalog = load_aggregated_catalog_with_models_dev(
&paths,
CachePreference::AllowStale,
&mut models_dev,
);
provider_a_handle.join().unwrap();
provider_b_handle.join().unwrap();
assert_eq!(
catalog
.entries
.iter()
.filter(|entry| entry.provider == "provider-a" || entry.provider == "provider-b")
.count(),
2,
"notices={:?}",
catalog.notices
);
let provider_a = catalog
.entries
.iter()
.find(|entry| entry.provider == "provider-a")
.unwrap();
let provider_b = catalog
.entries
.iter()
.find(|entry| entry.provider == "provider-b")
.unwrap();
assert_eq!(
provider_a.description.as_deref(),
Some("provider a metadata")
);
assert_eq!(provider_a.context_window, Some(111));
assert_eq!(
provider_b.description.as_deref(),
Some("provider b metadata")
);
assert_eq!(provider_b.context_window, Some(222));
}
#[test]
fn aggregated_catalog_loads_custom_providers_in_parallel() {
let temp = TempDir::new().unwrap();
let paths = McPaths::from_root(temp.path().join("mc"));
let (seen_tx, seen_rx) = std::sync::mpsc::channel();
let (provider_a_url, provider_a_handle) = spawn_delayed_model_catalog_server(
Some("parallel-a"),
200,
std::time::Duration::from_millis(300),
Some(seen_tx.clone()),
);
let (provider_b_url, provider_b_handle) = spawn_delayed_model_catalog_server(
Some("parallel-b"),
200,
std::time::Duration::from_millis(300),
Some(seen_tx),
);
crate::config::write_settings(
&paths,
&crate::config::Settings {
custom_providers: std::collections::BTreeMap::from([
(
"provider-a".to_string(),
CustomProviderConfig {
label: "Provider A".to_string(),
base_url: provider_a_url,
fast_mode: None,
api_key_env_var: None,
models_dev_provider: Some("provider-a-meta".to_string()),
use_responses_endpoint: false,
supports_text_verbosity: false,
reasoning_protocol: crate::config::CustomReasoningProtocol::default(),
extra_models: Vec::new(),
request_headers: Default::default(),
},
),
(
"provider-b".to_string(),
CustomProviderConfig {
label: "Provider B".to_string(),
base_url: provider_b_url,
fast_mode: None,
api_key_env_var: None,
models_dev_provider: Some("provider-b-meta".to_string()),
use_responses_endpoint: false,
supports_text_verbosity: false,
reasoning_protocol: crate::config::CustomReasoningProtocol::default(),
extra_models: Vec::new(),
request_headers: Default::default(),
},
),
]),
..Default::default()
},
)
.unwrap();
let mut models_dev = ModelsDevLookup {
attempted: true,
value: Some(serde_json::json!({})),
};
let started = std::time::Instant::now();
let catalog = load_aggregated_catalog_with_models_dev(
&paths,
CachePreference::AllowStale,
&mut models_dev,
);
let elapsed = started.elapsed();
provider_a_handle.join().unwrap();
provider_b_handle.join().unwrap();
let first_seen = seen_rx.recv().unwrap();
let second_seen = seen_rx.recv().unwrap();
assert!(
elapsed < std::time::Duration::from_millis(550),
"catalog discovery was not parallel enough: elapsed={elapsed:?}, notices={:?}",
catalog.notices
);
assert!(
second_seen.duration_since(first_seen) < std::time::Duration::from_millis(200),
"provider requests did not overlap: first={first_seen:?}, second={second_seen:?}"
);
for id in ["provider-a/parallel-a", "provider-b/parallel-b"] {
assert!(
catalog.entries.iter().any(|entry| entry.id == id),
"missing {id}; notices={:?}",
catalog.notices
);
}
}
#[test]
fn aggregated_catalog_bounds_custom_provider_worker_concurrency() {
let temp = TempDir::new().unwrap();
let paths = McPaths::from_root(temp.path().join("mc"));
let active = Arc::new(std::sync::atomic::AtomicUsize::new(0));
let peak = Arc::new(std::sync::atomic::AtomicUsize::new(0));
let mut providers = std::collections::BTreeMap::new();
let mut handles = Vec::new();
for index in 0..(MAX_CUSTOM_PROVIDER_CATALOG_WORKERS + 3) {
let provider = format!("provider-{index}");
let model = format!("bounded-{index}");
let (url, handle) = spawn_counted_model_catalog_server(
&model,
std::time::Duration::from_millis(100),
Arc::clone(&active),
Arc::clone(&peak),
);
handles.push(handle);
providers.insert(
provider,
CustomProviderConfig {
label: format!("Provider {index}"),
base_url: url,
fast_mode: None,
api_key_env_var: None,
models_dev_provider: None,
use_responses_endpoint: false,
supports_text_verbosity: false,
reasoning_protocol: crate::config::CustomReasoningProtocol::default(),
extra_models: Vec::new(),
request_headers: Default::default(),
},
);
}
crate::config::write_settings(
&paths,
&crate::config::Settings {
custom_providers: providers,
..Default::default()
},
)
.unwrap();
let mut models_dev = ModelsDevLookup {
attempted: true,
value: Some(serde_json::json!({})),
};
let catalog = load_aggregated_catalog_with_models_dev(
&paths,
CachePreference::AllowStale,
&mut models_dev,
);
for handle in handles {
handle.join().unwrap();
}
assert!(
peak.load(std::sync::atomic::Ordering::SeqCst) <= MAX_CUSTOM_PROVIDER_CATALOG_WORKERS,
"peak custom provider requests exceeded cap; notices={:?}",
catalog.notices
);
for index in 0..(MAX_CUSTOM_PROVIDER_CATALOG_WORKERS + 3) {
let id = format!("provider-{index}/bounded-{index}");
assert!(
catalog.entries.iter().any(|entry| entry.id == id),
"missing {id}; notices={:?}",
catalog.notices
);
}
}
#[test]
fn aggregated_catalog_preserves_order_with_mixed_custom_success_and_failure() {
let temp = TempDir::new().unwrap();
let paths = McPaths::from_root(temp.path().join("mc"));
let (provider_a_url, provider_a_handle) = spawn_delayed_model_catalog_server(
Some("success-a"),
200,
std::time::Duration::from_millis(50),
None,
);
let (provider_b_url, provider_b_handle) =
spawn_delayed_model_catalog_server(None, 500, std::time::Duration::from_millis(50), None);
crate::config::write_settings(
&paths,
&crate::config::Settings {
custom_providers: std::collections::BTreeMap::from([
(
"provider-a".to_string(),
CustomProviderConfig {
label: "Provider A".to_string(),
base_url: provider_a_url,
fast_mode: None,
api_key_env_var: None,
models_dev_provider: None,
use_responses_endpoint: false,
supports_text_verbosity: false,
reasoning_protocol: crate::config::CustomReasoningProtocol::default(),
extra_models: Vec::new(),
request_headers: Default::default(),
},
),
(
"provider-b".to_string(),
CustomProviderConfig {
label: "Provider B".to_string(),
base_url: provider_b_url,
fast_mode: None,
api_key_env_var: None,
models_dev_provider: None,
use_responses_endpoint: false,
supports_text_verbosity: false,
reasoning_protocol: crate::config::CustomReasoningProtocol::default(),
extra_models: Vec::new(),
request_headers: Default::default(),
},
),
]),
..Default::default()
},
)
.unwrap();
let mut models_dev = ModelsDevLookup {
attempted: true,
value: Some(serde_json::json!({})),
};
let catalog = load_aggregated_catalog_with_models_dev(
&paths,
CachePreference::AllowStale,
&mut models_dev,
);
provider_a_handle.join().unwrap();
provider_b_handle.join().unwrap();
let ids = catalog
.entries
.iter()
.map(|entry| entry.id.as_str())
.collect::<Vec<_>>();
assert_eq!(&ids[..1], ["provider-a/success-a"]);
assert!(catalog.notices.iter().any(|notice| {
notice.contains("provider-b model discovery unavailable") && notice.contains("500")
}));
assert!(!ids.iter().any(|id| id.starts_with("provider-b/")));
}