#![expect(clippy::unwrap_used)]
use wiremock::matchers::{method, path, query_param};
use wiremock::{Mock, ResponseTemplate};
use crate::cli::BugAction;
use crate::test_helpers::{capture_stdout, extract_json, setup_test_env};
use crate::types::OutputFormat;
fn from_url_action(url: String, save_as: Option<String>) -> BugAction {
BugAction::Search {
query: None,
from_url: Some(url),
save_as,
limit: None,
fields: None,
exclude_fields: None,
}
}
#[tokio::test]
async fn handle_search_from_url_executes() {
let (_lock, mock, _tmp) = setup_test_env().await;
Mock::given(method("GET"))
.and(path("/rest/bug"))
.and(query_param("product", "TestProduct"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"bugs": [{"id": 1, "summary": "Test bug", "status": "NEW",
"product": "TestProduct", "component": "General"}]
})))
.expect(1)
.mount(&mock)
.await;
let server_url = mock.uri();
let url = format!("{server_url}/buglist.cgi?product=TestProduct&limit=10");
let action = from_url_action(url, None);
let (result, output) = capture_stdout(crate::commands::bug::execute(
&action,
None,
OutputFormat::Json,
None,
))
.await;
assert!(result.is_ok(), "from-url search failed: {result:?}");
let parsed: serde_json::Value = extract_json(&output);
assert_eq!(parsed[0]["id"], 1);
}
#[tokio::test]
async fn handle_search_from_url_preserves_url_limit_when_cli_unset() {
let (_lock, mock, _tmp) = setup_test_env().await;
Mock::given(method("GET"))
.and(path("/rest/bug"))
.and(query_param("product", "TestProduct"))
.and(query_param("limit", "10"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({"bugs": []})))
.expect(1)
.mount(&mock)
.await;
let url = format!("{}/buglist.cgi?product=TestProduct&limit=10", mock.uri());
let action = from_url_action(url, None);
let (result, _) = capture_stdout(crate::commands::bug::execute(
&action,
None,
OutputFormat::Json,
None,
))
.await;
assert!(
result.is_ok(),
"from-url with explicit limit failed: {result:?}"
);
}
#[tokio::test]
async fn handle_search_quicksearch_passes_limit_and_field_filters() {
let (_lock, mock, _tmp) = setup_test_env().await;
Mock::given(method("GET"))
.and(path("/rest/bug"))
.and(query_param("quicksearch", "crash"))
.and(query_param("limit", "5"))
.and(query_param("include_fields", "id,summary"))
.and(query_param("exclude_fields", "comments"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({"bugs": []})))
.expect(1)
.mount(&mock)
.await;
let action = BugAction::Search {
query: Some("crash".into()),
from_url: None,
save_as: None,
limit: Some(5),
fields: Some("id,summary".into()),
exclude_fields: Some("comments".into()),
};
let (result, _) = capture_stdout(crate::commands::bug::execute(
&action,
None,
OutputFormat::Json,
None,
))
.await;
assert!(
result.is_ok(),
"quicksearch with filters failed: {result:?}"
);
}
#[tokio::test]
async fn handle_search_from_url_passes_raw_params() {
let (_lock, mock, _tmp) = setup_test_env().await;
Mock::given(method("GET"))
.and(path("/rest/bug"))
.and(query_param("product", "TestProduct"))
.and(query_param("f1", "qa_contact"))
.and(query_param("o1", "changedfrom"))
.and(query_param("v1", "user@example.com"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({"bugs": []})))
.expect(1)
.mount(&mock)
.await;
let server_url = mock.uri();
let url = format!(
"{server_url}/buglist.cgi?product=TestProduct&f1=qa_contact&o1=changedfrom&v1=user%40example.com"
);
let action = from_url_action(url, None);
let (result, _) = capture_stdout(crate::commands::bug::execute(
&action,
None,
OutputFormat::Json,
None,
))
.await;
assert!(
result.is_ok(),
"from-url with raw params failed: {result:?}"
);
}
#[tokio::test]
async fn handle_search_from_url_saves_query() {
let (_lock, mock, _tmp) = setup_test_env().await;
Mock::given(method("GET"))
.and(path("/rest/bug"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({"bugs": []})))
.mount(&mock)
.await;
let server_url = mock.uri();
let url = format!("{server_url}/buglist.cgi?product=TestProduct&known_name=my-query");
let action = from_url_action(url, Some("my-query".into()));
let (result, _output) = capture_stdout(crate::commands::bug::execute(
&action,
None,
OutputFormat::Json,
None,
))
.await;
assert!(result.is_ok(), "from-url save failed: {result:?}");
let config = crate::config::Config::load().unwrap();
let saved = config.queries.get("my-query").unwrap();
assert_eq!(saved.kind, crate::types::QueryKind::Url);
assert_eq!(saved.product, vec!["TestProduct"]);
assert!(saved.source_url.is_some());
}
#[tokio::test]
async fn handle_search_from_url_auto_names_from_known_name() {
let (_lock, mock, _tmp) = setup_test_env().await;
Mock::given(method("GET"))
.and(path("/rest/bug"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({"bugs": []})))
.mount(&mock)
.await;
let server_url = mock.uri();
let url =
format!("{server_url}/buglist.cgi?product=TestProduct&known_name=my%20saved%20search");
let action = from_url_action(url, Some(String::new()));
let (result, _output) = capture_stdout(crate::commands::bug::execute(
&action,
None,
OutputFormat::Json,
None,
))
.await;
assert!(
result.is_ok(),
"auto-name from known_name failed: {result:?}"
);
let config = crate::config::Config::load().unwrap();
assert!(
config.queries.contains_key("my saved search"),
"query should be saved as 'my saved search'"
);
}
#[tokio::test]
async fn handle_search_save_as_no_name_no_known_name_errors() {
let (_lock, _mock, _tmp) = setup_test_env().await;
let action = from_url_action(
"https://bugzilla.example.com/buglist.cgi?product=Firefox".into(),
Some(String::new()),
);
let (result, _output) = capture_stdout(crate::commands::bug::execute(
&action,
None,
OutputFormat::Json,
None,
))
.await;
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(
err.contains("no name provided for --save-as"),
"unexpected error: {err}"
);
}