bzr 0.4.2

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

use std::collections::HashMap;

use super::*;
use crate::types::{BugTemplate, OutputFormat};

fn make_template() -> BugTemplate {
    BugTemplate {
        product: Some("Widget".into()),
        component: Some("Backend".into()),
        version: None,
        priority: Some("P1".into()),
        severity: Some("major".into()),
        assignee: None,
        op_sys: None,
        rep_platform: None,
        description: Some("Default description".into()),
    }
}

#[test]
fn template_saved_json() {
    let json = serde_json::json!({"name": "my-tmpl", "action": "saved"});
    let parsed: serde_json::Value =
        serde_json::from_str(&serde_json::to_string(&json).unwrap()).unwrap();
    assert_eq!(parsed["name"], "my-tmpl");
    assert_eq!(parsed["action"], "saved");
}

#[test]
fn template_list_json_serializes() {
    let mut templates: HashMap<String, BugTemplate> = HashMap::new();
    templates.insert("default".into(), make_template());
    let json = serde_json::to_string_pretty(&templates).unwrap();
    let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
    assert!(parsed["default"].is_object());
    assert_eq!(parsed["default"]["product"], "Widget");
    assert_eq!(parsed["default"]["component"], "Backend");
}

#[test]
fn template_detail_json_with_flatten() {
    #[derive(serde::Serialize)]
    struct TemplateView<'a> {
        name: &'a str,
        #[serde(flatten)]
        template: &'a BugTemplate,
    }
    let template = make_template();
    let view = TemplateView {
        name: "test-tmpl",
        template: &template,
    };
    let json = serde_json::to_string_pretty(&view).unwrap();
    let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
    assert_eq!(parsed["name"], "test-tmpl");
    assert_eq!(parsed["product"], "Widget");
    assert_eq!(parsed["priority"], "P1");
    assert!(parsed["version"].is_null());
}

#[test]
fn template_empty_fields_omitted_in_json() {
    let template = BugTemplate {
        product: None,
        component: None,
        version: None,
        priority: None,
        severity: None,
        assignee: None,
        op_sys: None,
        rep_platform: None,
        description: None,
    };
    let json = serde_json::to_string(&template).unwrap();
    let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
    assert!(parsed.as_object().unwrap().is_empty());
}

#[test]
fn template_saved_message_renders_table_text() {
    assert_eq!(
        template_saved_message("default", "Saved"),
        "Saved template 'default'"
    );
}

#[test]
fn template_summary_line_renders_all_present_fields() {
    let line = template_summary_line("aaa", &make_template());
    assert_eq!(
        line,
        "aaa (product=Widget, component=Backend, priority=P1, severity=major)"
    );
}

#[test]
fn template_summary_line_without_fields_is_name_only() {
    let line = template_summary_line(
        "zzz",
        &BugTemplate {
            product: None,
            component: None,
            version: None,
            priority: None,
            severity: None,
            assignee: None,
            op_sys: None,
            rep_platform: None,
            description: None,
        },
    );
    assert_eq!(line, "zzz");
}

fn capture_detail(name: &str, template: &BugTemplate, format: OutputFormat) -> String {
    let mut buf = Vec::new();
    write_template_detail(name, template, format, &mut buf);
    String::from_utf8(buf).unwrap()
}

fn capture_list(templates: &HashMap<String, BugTemplate>, format: OutputFormat) -> String {
    let mut buf = Vec::new();
    write_template_list(templates, format, &mut buf);
    String::from_utf8(buf).unwrap()
}

#[test]
fn write_template_detail_table_renders_missing_fields_as_dash() {
    let template = BugTemplate {
        product: Some("Widget".into()),
        component: None,
        version: None,
        priority: Some("P1".into()),
        severity: None,
        assignee: None,
        op_sys: None,
        rep_platform: None,
        description: Some("Default description".into()),
    };

    let output = capture_detail("default", &template, OutputFormat::Table);

    assert!(output.contains("Name"));
    assert!(output.contains("default"));
    assert!(output.contains("Product"));
    assert!(output.contains("Widget"));
    assert!(output.contains("Component"));
    assert!(output.contains("  -"));
    assert!(output.contains("Description"));
    assert!(output.contains("Default description"));
}

#[test]
fn write_template_list_table_renders_sorted_summaries() {
    let mut templates: HashMap<String, BugTemplate> = HashMap::new();
    templates.insert("zzz".into(), make_template());
    templates.insert(
        "aaa".into(),
        BugTemplate {
            product: Some("Alpha".into()),
            component: None,
            version: None,
            priority: None,
            severity: None,
            assignee: None,
            op_sys: None,
            rep_platform: None,
            description: None,
        },
    );

    let output = capture_list(&templates, OutputFormat::Table);

    let aaa_pos = output.find("aaa").expect("aaa should appear in output");
    let zzz_pos = output.find("zzz").expect("zzz should appear in output");
    assert!(aaa_pos < zzz_pos, "templates should be sorted by name");
    assert!(output.contains("product=Alpha"));
    assert!(output.contains("product=Widget"));
}

#[test]
fn write_template_list_table_announces_empty() {
    let templates: HashMap<String, BugTemplate> = HashMap::new();
    let output = capture_list(&templates, OutputFormat::Table);
    assert!(output.contains("No templates configured."));
}