use crate::client::BugzillaClient;
use crate::client::DetectedServerSettings;
use crate::config::Config;
use crate::error::Result;
use crate::types::ApiMode;
fn persist_detected_settings(
config: &mut Config,
server_name: &str,
settings: &DetectedServerSettings,
persist_auth: bool,
) -> Result<()> {
if let Some(srv_mut) = config.servers.get_mut(server_name) {
if persist_auth {
srv_mut.auth_method = Some(settings.auth_method);
}
if settings.server_version.is_some() {
srv_mut.api_mode = Some(settings.api_mode);
srv_mut.server_version.clone_from(&settings.server_version);
}
config.save()?;
}
Ok(())
}
pub async fn connect_and_configure(
server: Option<&str>,
api_override: Option<ApiMode>,
) -> Result<BugzillaClient> {
let mut config = Config::load()?;
let (server_name, srv) = config.resolve_server(server)?;
let (server_name, url, api_key, email, tls_insecure) = (
server_name.to_string(),
srv.url.clone(),
srv.api_key.clone(),
srv.email.clone(),
srv.tls_insecure,
);
if tls_insecure {
tracing::warn!("TLS certificate verification disabled for server '{server_name}'");
}
let (auth, resolved_mode) = match (srv.auth_method, srv.api_mode) {
(Some(method), Some(mode)) => (method, mode),
(Some(method), None) => {
tracing::debug!("auth_method cached but api_mode missing; re-detecting");
let settings = crate::client::detect_server_settings(
&url,
&api_key,
email.as_deref(),
tls_insecure,
)
.await?;
persist_detected_settings(&mut config, &server_name, &settings, false)?;
(method, settings.api_mode)
}
_ => {
let settings = crate::client::detect_server_settings(
&url,
&api_key,
email.as_deref(),
tls_insecure,
)
.await?;
persist_detected_settings(&mut config, &server_name, &settings, true)?;
(settings.auth_method, settings.api_mode)
}
};
let api_mode = api_override.unwrap_or(resolved_mode);
let client = BugzillaClient::new(
&url,
&api_key,
auth,
api_mode,
email.as_deref(),
tls_insecure,
)?;
Ok(client)
}
#[cfg(test)]
#[expect(clippy::unwrap_used)]
mod tests {
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
use crate::test_helpers::setup_test_env;
use crate::ENV_LOCK;
#[tokio::test]
async fn connect_client_returns_client() {
let (_lock, mock, _tmp) = setup_test_env().await;
Mock::given(method("GET"))
.and(path("/rest/whoami"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({"id": 1})))
.mount(&mock)
.await;
let result = super::connect_and_configure(None, None).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn connect_client_with_email_config_succeeds() {
let _lock = ENV_LOCK.lock().await;
let mock = MockServer::start().await;
let tmp = tempfile::TempDir::new().unwrap();
let config_dir = tmp.path().join("bzr");
std::fs::create_dir_all(&config_dir).unwrap();
let config_content = format!(
r#"
default_server = "test"
[servers.test]
url = "{}"
api_key = "test-key"
auth_method = "header"
api_mode = "rest"
email = "user@example.com"
"#,
mock.uri()
);
std::fs::write(config_dir.join("config.toml"), config_content).unwrap();
unsafe { std::env::set_var("XDG_CONFIG_HOME", tmp.path()) };
Mock::given(method("GET"))
.and(path("/rest/whoami"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({"id": 1})))
.mount(&mock)
.await;
let result = super::connect_and_configure(None, None).await;
assert!(
result.is_ok(),
"connect_client with email config should succeed"
);
}
#[tokio::test]
async fn connect_client_api_override_applies() {
let (_lock, mock, _tmp) = setup_test_env().await;
Mock::given(method("GET"))
.and(path("/rest/whoami"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({"id": 1})))
.mount(&mock)
.await;
let result = super::connect_and_configure(None, Some(crate::types::ApiMode::XmlRpc)).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn connect_client_missing_server_fails() {
let _lock = ENV_LOCK.lock().await;
let tmp = tempfile::TempDir::new().unwrap();
let config_dir = tmp.path().join("bzr");
std::fs::create_dir_all(&config_dir).unwrap();
std::fs::write(config_dir.join("config.toml"), "").unwrap();
unsafe { std::env::set_var("XDG_CONFIG_HOME", tmp.path()) };
let result = super::connect_and_configure(None, None).await;
assert!(result.is_err());
}
#[tokio::test]
async fn uncached_auth_detects_and_persists() {
let _lock = ENV_LOCK.lock().await;
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/rest/whoami"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({"id": 1})))
.mount(&server)
.await;
Mock::given(method("GET"))
.and(path("/rest/version"))
.respond_with(
ResponseTemplate::new(200).set_body_json(serde_json::json!({"version": "5.1.2"})),
)
.mount(&server)
.await;
let tmp = tempfile::TempDir::new().unwrap();
let config_dir = tmp.path().join("bzr");
std::fs::create_dir_all(&config_dir).unwrap();
let config_content = format!(
r#"
default_server = "test"
[servers.test]
url = "{}"
api_key = "test-key"
"#,
server.uri()
);
std::fs::write(config_dir.join("config.toml"), &config_content).unwrap();
unsafe { std::env::set_var("XDG_CONFIG_HOME", tmp.path()) };
let result = super::connect_and_configure(None, None).await;
assert!(result.is_ok(), "connect_client should succeed");
let reloaded = crate::config::Config::load().unwrap();
assert_eq!(
reloaded.servers["test"].auth_method,
Some(crate::types::AuthMethod::Header)
);
assert_eq!(
reloaded.servers["test"].api_mode,
Some(crate::types::ApiMode::Rest)
);
assert_eq!(
reloaded.servers["test"].server_version.as_deref(),
Some("5.1.2")
);
}
}