bzr 0.1.0

A CLI for Bugzilla, inspired by gh
Documentation
use colored::Colorize;

use super::formatting::{print_field, print_formatted, print_optional_field};
use crate::types::{Attachment, OutputFormat};

#[expect(clippy::print_stdout)]
pub fn print_attachments(attachments: &[Attachment], format: OutputFormat) {
    print_formatted(attachments, format, |attachments| {
        if attachments.is_empty() {
            println!("No attachments.");
            return;
        }
        for a in attachments {
            let obsolete = if a.is_obsolete { " [OBSOLETE]" } else { "" };
            let private = if a.is_private { " [PRIVATE]" } else { "" };
            println!(
                "{} #{} - {}{}{}",
                "Attachment".bold(),
                a.id,
                a.summary.bold(),
                obsolete.red(),
                private.red(),
            );
            print_field(
                "File",
                &format!("{} ({}, {} bytes)", a.file_name, a.content_type, a.size),
            );
            print_optional_field("Creator", a.creator.as_deref());
            print_optional_field("Created", a.creation_time.as_deref());
            println!();
        }
    });
}

#[cfg(test)]
#[expect(clippy::unwrap_used)]
mod tests {
    use crate::types::Attachment;

    fn make_attachment(id: u64, summary: &str) -> Attachment {
        Attachment {
            id,
            bug_id: 42,
            file_name: format!("file_{id}.patch"),
            summary: summary.into(),
            content_type: "text/plain".into(),
            creator: Some("author@example.com".into()),
            creation_time: Some("2025-03-01T09:00:00Z".into()),
            last_change_time: Some("2025-03-02T10:00:00Z".into()),
            size: 1234,
            is_obsolete: false,
            is_private: false,
            data: None,
        }
    }

    #[test]
    fn print_attachments_json_empty() {
        let attachments: Vec<Attachment> = vec![];
        let json = serde_json::to_string_pretty(&attachments).unwrap();
        assert_eq!(json, "[]");
    }

    #[test]
    fn print_attachments_json_one_attachment() {
        let attachments = vec![make_attachment(10, "Fix patch")];
        let json = serde_json::to_string_pretty(&attachments).unwrap();
        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
        assert_eq!(parsed[0]["id"], 10);
        assert_eq!(parsed[0]["summary"], "Fix patch");
        assert_eq!(parsed[0]["file_name"], "file_10.patch");
        assert_eq!(parsed[0]["content_type"], "text/plain");
        assert_eq!(parsed[0]["size"], 1234);
    }

    #[test]
    fn attachment_text_format_fields() {
        let att = make_attachment(10, "Fix patch");
        assert_eq!(att.file_name, "file_10.patch");
        assert_eq!(att.content_type, "text/plain");
        assert_eq!(att.size, 1234);
        assert_eq!(att.creator.as_deref(), Some("author@example.com"));
        assert!(!att.is_obsolete);
        assert!(!att.is_private);
    }

    #[test]
    fn print_attachments_json_obsolete_and_private() {
        let mut att = make_attachment(11, "Old patch");
        att.is_obsolete = true;
        att.is_private = true;
        let json = serde_json::to_string(&att).unwrap();
        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
        assert_eq!(parsed["is_obsolete"], true);
        assert_eq!(parsed["is_private"], true);
    }
}