bzr 0.4.1

A CLI for Bugzilla, inspired by gh
Documentation
#![expect(clippy::unwrap_used)]

use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};

use crate::client::test_helpers::test_client;
use crate::error::BzrError;
use crate::field_aliases::resolve_field_alias;

#[test]
fn resolve_field_alias_maps_status() {
    assert_eq!(resolve_field_alias("status").as_ref(), "bug_status");
}

#[test]
fn resolve_field_alias_maps_severity() {
    assert_eq!(resolve_field_alias("severity").as_ref(), "bug_severity");
}

#[test]
fn resolve_field_alias_maps_id() {
    assert_eq!(resolve_field_alias("id").as_ref(), "bug_id");
}

#[test]
fn resolve_field_alias_maps_type() {
    assert_eq!(resolve_field_alias("type").as_ref(), "bug_type");
}

#[test]
fn resolve_field_alias_maps_group() {
    assert_eq!(resolve_field_alias("group").as_ref(), "bug_group");
}

#[test]
fn resolve_field_alias_maps_file_loc() {
    assert_eq!(resolve_field_alias("file_loc").as_ref(), "bug_file_loc");
}

#[test]
fn resolve_field_alias_passes_through_unknown() {
    assert_eq!(resolve_field_alias("priority").as_ref(), "priority");
}

#[test]
fn resolve_field_alias_passes_through_already_prefixed() {
    assert_eq!(resolve_field_alias("bug_status").as_ref(), "bug_status");
}

#[test]
fn resolve_field_alias_is_case_insensitive() {
    assert_eq!(resolve_field_alias("Status").as_ref(), "bug_status");
    assert_eq!(resolve_field_alias("SEVERITY").as_ref(), "bug_severity");
}

#[tokio::test]
async fn get_field_values_returns_values() {
    let mock = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/rest/field/bug/bug_status"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "fields": [{
                "values": [
                    {"name": "NEW", "sort_key": 100, "is_active": true, "can_change_to": [{"name": "ASSIGNED"}, {"name": "RESOLVED"}]},
                    {"name": "RESOLVED", "sort_key": 500, "is_active": true}
                ]
            }]
        })))
        .mount(&mock)
        .await;

    let client = test_client(&mock.uri());
    let values = client.get_field_values("status").await.unwrap();
    assert_eq!(values.len(), 2);
    assert_eq!(values[0].name, "NEW");
    let transitions = values[0].can_change_to.as_ref().unwrap();
    assert_eq!(transitions.len(), 2);
    assert_eq!(transitions[0].name, "ASSIGNED");
}

#[tokio::test]
async fn get_field_values_resolves_severity_alias() {
    let mock = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/rest/field/bug/bug_severity"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "fields": [{
                "values": [
                    {"name": "blocker", "sort_key": 100, "is_active": true},
                    {"name": "normal", "sort_key": 200, "is_active": true}
                ]
            }]
        })))
        .mount(&mock)
        .await;

    let client = test_client(&mock.uri());
    let values = client.get_field_values("severity").await.unwrap();
    assert_eq!(values.len(), 2);
    assert_eq!(values[0].name, "blocker");
}

#[tokio::test]
async fn get_field_values_unrecognized_field_returns_not_found() {
    let mock = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/rest/field/bug/nonexistent"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({"fields": []})))
        .mount(&mock)
        .await;

    let client = test_client(&mock.uri());
    let err = client.get_field_values("nonexistent").await.unwrap_err();
    assert!(
        matches!(
            err,
            BzrError::NotFound {
                resource: "field",
                ..
            }
        ),
        "expected NotFound, got: {err}"
    );
}