Files
cc-switch/src-tauri/tests/hermes_roundtrip.rs
T
Jason 0be75668cc feat(hermes): align provider schema with Hermes Agent 0.10.0
Hermes 0.10.0 tightened custom_providers validation (commit 2cdae233):
invalid base_urls are rejected, unknown fields produce warnings, and
new fields (rate_limit_delay, bedrock_converse, key_env) landed.

- Add bedrock_converse to the api_mode selector (and i18n labels)
- Expose rate_limit_delay in a provider-level advanced panel
- Validate base_url client-side (URL shape, template-token friendly)
- Drop per-model max_tokens — not in _VALID_CUSTOM_PROVIDER_FIELDS
- Round-trip test asserts set_provider preserves rate_limit_delay /
  key_env / any unknown forward-compat field
2026-04-21 11:57:07 +08:00

125 lines
4.0 KiB
Rust

mod support;
use cc_switch_lib::{hermes_config, update_settings, AppSettings};
/// 读取并回写 Hermes provider 时,Hermes v12+ 新增或未来才会出现的字段
/// (例如 `rate_limit_delay`、`key_env`)必须透传,不能因为 UI 不感知就静默丢弃。
/// 否则用户在 Hermes Web UI 配置的高级字段会在 CC Switch 编辑后消失。
fn with_temp_hermes_dir<F: FnOnce(&std::path::Path)>(f: F) {
let guard = support::test_mutex().lock().expect("test mutex poisoned");
let home = support::ensure_test_home();
support::reset_test_fs();
let hermes_dir = home.join(".hermes-roundtrip");
let _ = std::fs::remove_dir_all(&hermes_dir);
std::fs::create_dir_all(&hermes_dir).expect("create temp hermes dir");
update_settings(AppSettings {
hermes_config_dir: Some(hermes_dir.to_string_lossy().into_owned()),
..AppSettings::default()
})
.expect("set hermes_config_dir override");
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(&hermes_dir)));
// Always restore settings and drop fixture dir, even on test failure.
let _ = update_settings(AppSettings::default());
let _ = std::fs::remove_dir_all(&hermes_dir);
drop(guard);
if let Err(err) = result {
std::panic::resume_unwind(err);
}
}
#[test]
fn set_provider_preserves_unknown_and_future_fields() {
with_temp_hermes_dir(|dir| {
let yaml = r#"custom_providers:
- name: myhost
base_url: https://api.example.com/v1
api_key: sk-old
api_mode: chat_completions
rate_limit_delay: 0.5
key_env: MY_API_KEY
foo_bar: keep-me-around
models:
gpt-4:
context_length: 8192
"#;
let config_path = dir.join("config.yaml");
std::fs::write(&config_path, yaml).expect("seed config.yaml");
// Simulate the UI sending back only the fields it knows about.
let patch = serde_json::json!({
"name": "myhost",
"base_url": "https://api.example.com/v1",
"api_key": "sk-new",
"api_mode": "chat_completions",
"models": [
{ "id": "gpt-4", "context_length": 8192 }
]
});
hermes_config::set_provider("myhost", patch).expect("set_provider");
let written = std::fs::read_to_string(&config_path).expect("read written config");
assert!(
written.contains("rate_limit_delay"),
"rate_limit_delay stripped:\n{written}"
);
assert!(
written.contains("key_env"),
"key_env key stripped:\n{written}"
);
assert!(
written.contains("MY_API_KEY"),
"key_env value stripped:\n{written}"
);
assert!(
written.contains("foo_bar"),
"unknown forward-compat field stripped:\n{written}"
);
assert!(
written.contains("sk-new"),
"api_key was not updated to sk-new:\n{written}"
);
assert!(
!written.contains("sk-old"),
"old api_key still present:\n{written}"
);
});
}
#[test]
fn get_providers_surfaces_rate_limit_delay_and_key_env() {
with_temp_hermes_dir(|dir| {
let yaml = r#"custom_providers:
- name: myhost
base_url: https://api.example.com/v1
api_key: sk-xxx
api_mode: chat_completions
rate_limit_delay: 2.5
key_env: FOO_KEY
models:
m1: {}
"#;
std::fs::write(dir.join("config.yaml"), yaml).expect("seed config.yaml");
let providers = hermes_config::get_providers().expect("get_providers");
let entry = providers.get("myhost").expect("myhost missing");
assert_eq!(
entry.get("rate_limit_delay").and_then(|v| v.as_f64()),
Some(2.5),
"rate_limit_delay not surfaced to DAO payload"
);
assert_eq!(
entry.get("key_env").and_then(|v| v.as_str()),
Some("FOO_KEY"),
"key_env not surfaced to DAO payload"
);
});
}