#![cfg(all(feature = "backend-http", feature = "keyring"))]
use std::collections::HashMap;
use proserpina::backend::credentials::{resolve_configs_with_keyring, Credentials};
use proserpina::backend::roster::Provider;
fn env(keys: &[(&str, &str)]) -> HashMap<String, String> {
keys.iter()
.copied()
.map(|(k, v)| (k.to_owned(), v.to_owned()))
.collect()
}
fn registry() -> Vec<Provider> {
vec![
Provider::new("deepseek")
.with_base_url("https://api.deepseek.com/v1")
.with_model("deepseek-chat")
.with_key_env_var("DEEPSEEK_API_KEY"),
Provider::new("zai")
.with_base_url("https://api.z.ai/api/coding/paas/v4")
.with_model("glm-5.2")
.with_key_env_var("ZAI_API_KEY"),
]
}
#[test]
fn keyring_key_wins_over_env_and_config() {
let keyring = env(&[("DEEPSEEK_API_KEY", "sk-keychain")]);
let env_keys = env(&[("DEEPSEEK_API_KEY", "sk-env")]);
let creds = Credentials::from_toml(
r#"[deepseek]
api_key = "sk-config""#,
)
.unwrap();
let configs =
resolve_configs_with_keyring(®istry(), &creds, &env_keys, &keyring).expect("ok");
assert_eq!(configs.len(), 1);
assert_eq!(configs[0].api_key, "sk-keychain", "keyring must win");
}
#[test]
fn env_used_when_keyring_entry_absent() {
let keyring = HashMap::new();
let env_keys = env(&[("DEEPSEEK_API_KEY", "sk-env")]);
let creds = Credentials::default();
let configs =
resolve_configs_with_keyring(®istry(), &creds, &env_keys, &keyring).expect("ok");
assert_eq!(configs[0].api_key, "sk-env");
}
#[test]
fn keyring_entry_under_wrong_name_is_ignored() {
let keyring = env(&[("WRONG_NAME", "sk-x")]);
let env_keys = HashMap::new();
let creds = Credentials::default();
let configs =
resolve_configs_with_keyring(®istry(), &creds, &env_keys, &keyring).expect("ok");
assert!(
configs.is_empty(),
"no matching keyring/env/config -> not authed"
);
}
#[test]
fn keyring_and_env_can_auth_different_providers_simultaneously() {
let keyring = env(&[("DEEPSEEK_API_KEY", "sk-keychain")]);
let env_keys = env(&[("ZAI_API_KEY", "sk-env")]);
let creds = Credentials::default();
let configs =
resolve_configs_with_keyring(®istry(), &creds, &env_keys, &keyring).expect("ok");
assert_eq!(configs.len(), 2);
let by_model: HashMap<&str, &str> = configs
.iter()
.map(|c| (c.model.as_str(), c.api_key.as_str()))
.collect();
assert_eq!(by_model["deepseek-chat"], "sk-keychain");
assert_eq!(by_model["glm-5.2"], "sk-env");
}
#[test]
#[ignore]
fn live_keyring_roundtrip() {
use proserpina::backend::credentials::{read_keyring, KEYRING_SERVICE};
let var = "DEEPSEEK_API_KEY";
let sentinel = "proserpina-test-sentinel-key";
let _ = keyring::Entry::new(KEYRING_SERVICE, var).and_then(|e| e.delete_credential());
let entry = keyring::Entry::new(KEYRING_SERVICE, var).expect("create entry");
entry.set_password(sentinel).expect("write sentinel");
let read = read_keyring(var).expect("read_keyring should succeed");
assert_eq!(read.as_deref(), Some(sentinel));
entry.delete_credential().expect("cleanup");
}