#![expect(clippy::unwrap_used, clippy::panic)]
use super::*;
use crate::types::ProductListType;
use clap::{CommandFactory, Parser};
const COVERED_PATHS: &[&[&str]] = &[
&[],
&["bug"],
&["comment"],
&["attachment"],
&["config"],
&["product"],
&["field"],
&["user"],
&["group"],
&["whoami"],
&["server"],
&["classification"],
&["component"],
&["template"],
&["query"],
&["bug", "list"],
&["bug", "view"],
&["bug", "search"],
&["bug", "history"],
&["bug", "create"],
&["bug", "my"],
&["bug", "clone"],
&["bug", "update"],
&["config", "set-server"],
&["config", "set-default"],
&["config", "show"],
&["config", "set-keyring"],
&["config", "unset-keyring"],
&["config", "migrate-to-keyring"],
&["query", "save"],
&["query", "list"],
&["query", "show"],
&["query", "delete"],
&["query", "run"],
&["attachment", "list"],
&["attachment", "download"],
&["attachment", "upload"],
&["attachment", "update"],
&["comment", "list"],
&["comment", "add"],
&["comment", "tag"],
&["comment", "search-tags"],
&["user", "search"],
&["user", "create"],
&["user", "update"],
&["group", "add-user"],
&["group", "remove-user"],
&["group", "list-users"],
&["group", "view"],
&["group", "create"],
&["group", "update"],
&["product", "list"],
&["product", "view"],
&["product", "create"],
&["product", "update"],
&["template", "save"],
&["template", "list"],
&["template", "show"],
&["template", "delete"],
&["component", "create"],
&["component", "update"],
&["whoami", "show"],
&["server", "info"],
&["classification", "view"],
&["field", "aliases"],
&["field", "list"],
];
#[test]
fn cli_doc_long_about_coverage() {
let cmd = Cli::command();
for path in COVERED_PATHS {
let mut current = &cmd;
for &name in *path {
let next = current.get_subcommands().find(|c| c.get_name() == name);
current =
next.unwrap_or_else(|| panic!("subcommand path {path:?} not found in clap tree"));
}
let about = current.get_about().map(ToString::to_string);
let long_about = current.get_long_about().map(ToString::to_string);
assert!(
long_about.is_some(),
"subcommand path {path:?} is missing long_about (multi-paragraph doc comment required)"
);
assert_ne!(
about, long_about,
"subcommand path {path:?} has long_about identical to about (single-paragraph doc -- expand to multi-paragraph per docs/dev/cli-doc-style.md)"
);
}
}
#[test]
fn parse_bug_list_minimal() {
let cli = Cli::try_parse_from(["bzr", "bug", "list"]).unwrap();
assert!(matches!(
cli.command,
Commands::Bug {
action: BugAction::List { .. }
}
));
}
#[test]
fn parse_bug_view_by_id() {
let cli = Cli::try_parse_from(["bzr", "bug", "view", "12345"]).unwrap();
match cli.command {
Commands::Bug {
action: BugAction::View { id, .. },
} => assert_eq!(id, "12345"),
_ => panic!("expected Bug View"),
}
}
#[test]
fn parse_global_json_flag() {
let cli = Cli::try_parse_from(["bzr", "--json", "bug", "list"]).unwrap();
assert!(cli.json);
}
#[test]
fn parse_global_server_flag() {
let cli = Cli::try_parse_from(["bzr", "--server", "myserver", "bug", "list"]).unwrap();
assert_eq!(cli.server.as_deref(), Some("myserver"));
}
#[test]
fn parse_config_set_server() {
let cli = Cli::try_parse_from([
"bzr",
"config",
"set-server",
"prod",
"--url",
"https://bz.example.com",
"--api-key",
"secret123",
])
.unwrap();
assert!(matches!(
cli.command,
Commands::Config {
action: ConfigAction::SetServer { .. }
}
));
}
#[test]
fn parse_config_set_server_with_env_var() {
let cli = Cli::try_parse_from([
"bzr",
"config",
"set-server",
"prod",
"--url",
"https://bz.example.com",
"--api-key-env",
"BZR_API_KEY",
])
.unwrap();
assert!(matches!(
cli.command,
Commands::Config {
action: ConfigAction::SetServer { .. }
}
));
}
#[test]
fn parse_unknown_command_fails() {
let result = Cli::try_parse_from(["bzr", "nonexistent"]);
assert!(result.is_err());
}
#[test]
fn parse_whoami() {
let cli = Cli::try_parse_from(["bzr", "whoami", "show"]).unwrap();
assert!(matches!(cli.command, Commands::Whoami { action: _ }));
}
#[test]
fn parse_bug_search() {
let cli = Cli::try_parse_from(["bzr", "bug", "search", "crash"]).unwrap();
match cli.command {
Commands::Bug {
action: BugAction::Search { query, limit, .. },
} => {
assert_eq!(query.as_deref(), Some("crash"));
assert_eq!(limit, None);
}
_ => panic!("expected Bug Search"),
}
}
#[test]
fn parse_bug_search_with_limit() {
let cli = Cli::try_parse_from(["bzr", "bug", "search", "crash", "--limit", "10"]).unwrap();
match cli.command {
Commands::Bug {
action: BugAction::Search { limit, .. },
} => assert_eq!(limit, Some(10)),
_ => panic!("expected Bug Search"),
}
}
#[test]
fn parse_bug_history() {
let cli = Cli::try_parse_from(["bzr", "bug", "history", "42"]).unwrap();
match cli.command {
Commands::Bug {
action: BugAction::History { id, since },
} => {
assert_eq!(id, 42);
assert!(since.is_none());
}
_ => panic!("expected Bug History"),
}
}
#[test]
fn parse_bug_create() {
let cli = Cli::try_parse_from([
"bzr",
"bug",
"create",
"--product",
"TestProduct",
"--component",
"General",
"--summary",
"Test bug",
])
.unwrap();
match cli.command {
Commands::Bug {
action:
BugAction::Create {
product,
component,
summary,
version,
..
},
} => {
assert_eq!(product.as_deref(), Some("TestProduct"));
assert_eq!(component.as_deref(), Some("General"));
assert_eq!(summary, "Test bug");
assert_eq!(version, None);
}
_ => panic!("expected Bug Create"),
}
}
#[test]
fn parse_bug_update_with_flags() {
let cli = Cli::try_parse_from([
"bzr", "bug", "update", "42", "--status", "RESOLVED", "--flag", "review+",
])
.unwrap();
match cli.command {
Commands::Bug {
action: BugAction::Update {
ids, status, flag, ..
},
} => {
assert_eq!(ids, vec![42]);
assert_eq!(status.as_deref(), Some("RESOLVED"));
assert_eq!(flag, vec!["review+"]);
}
_ => panic!("expected Bug Update"),
}
}
#[test]
fn parse_comment_list() {
let cli = Cli::try_parse_from(["bzr", "comment", "list", "99"]).unwrap();
match cli.command {
Commands::Comment {
action: CommentAction::List { bug_id, .. },
} => assert_eq!(bug_id, 99),
_ => panic!("expected Comment List"),
}
}
#[test]
fn parse_comment_add_with_body() {
let cli = Cli::try_parse_from(["bzr", "comment", "add", "42", "--body", "This is a comment"])
.unwrap();
match cli.command {
Commands::Comment {
action:
CommentAction::Add {
bug_id,
body,
private,
},
} => {
assert_eq!(bug_id, 42);
assert_eq!(body.as_deref(), Some("This is a comment"));
assert!(!private);
}
_ => panic!("expected Comment Add"),
}
}
#[test]
fn parse_comment_add_with_private() {
let cli = Cli::try_parse_from([
"bzr",
"comment",
"add",
"42",
"--body",
"secret note",
"--private",
])
.unwrap();
match cli.command {
Commands::Comment {
action:
CommentAction::Add {
bug_id,
body,
private,
},
} => {
assert_eq!(bug_id, 42);
assert_eq!(body.as_deref(), Some("secret note"));
assert!(private);
}
_ => panic!("expected Comment Add"),
}
}
#[test]
fn parse_attachment_list() {
let cli = Cli::try_parse_from(["bzr", "attachment", "list", "42"]).unwrap();
match cli.command {
Commands::Attachment {
action: AttachmentAction::List { bug_id },
} => assert_eq!(bug_id, 42),
_ => panic!("expected Attachment List"),
}
}
#[test]
fn parse_attachment_download() {
let cli = Cli::try_parse_from(["bzr", "attachment", "download", "100"]).unwrap();
match cli.command {
Commands::Attachment {
action: AttachmentAction::Download { id, out },
} => {
assert_eq!(id, 100);
assert!(out.is_none());
}
_ => panic!("expected Attachment Download"),
}
}
#[test]
fn parse_product_list() {
let cli = Cli::try_parse_from(["bzr", "product", "list"]).unwrap();
match cli.command {
Commands::Product {
action: ProductAction::List { r#type },
} => assert_eq!(r#type, ProductListType::Accessible),
_ => panic!("expected Product List"),
}
}
#[test]
fn parse_product_view() {
let cli = Cli::try_parse_from(["bzr", "product", "view", "Firefox"]).unwrap();
match cli.command {
Commands::Product {
action: ProductAction::View { name },
} => assert_eq!(name, "Firefox"),
_ => panic!("expected Product View"),
}
}
#[test]
fn parse_user_search() {
let cli = Cli::try_parse_from(["bzr", "user", "search", "alice"]).unwrap();
match cli.command {
Commands::User {
action: UserAction::Search { query, details },
} => {
assert_eq!(query, "alice");
assert!(!details);
}
_ => panic!("expected User Search"),
}
}
#[test]
fn parse_group_add_user() {
let cli = Cli::try_parse_from([
"bzr",
"group",
"add-user",
"--group",
"admin",
"--user",
"alice@test.com",
])
.unwrap();
match cli.command {
Commands::Group {
action: GroupAction::AddUser { group, user },
} => {
assert_eq!(group, "admin");
assert_eq!(user, "alice@test.com");
}
_ => panic!("expected Group AddUser"),
}
}
#[test]
fn parse_field_list() {
let cli = Cli::try_parse_from(["bzr", "field", "list", "status"]).unwrap();
match cli.command {
Commands::Field {
action: FieldAction::List { name },
} => assert_eq!(name, "status"),
_ => panic!("expected Field List"),
}
}
#[test]
fn parse_server_info() {
let cli = Cli::try_parse_from(["bzr", "server", "info"]).unwrap();
assert!(matches!(
cli.command,
Commands::Server {
action: ServerAction::Info
}
));
}
#[test]
fn parse_classification_view() {
let cli = Cli::try_parse_from(["bzr", "classification", "view", "Unclassified"]).unwrap();
match cli.command {
Commands::Classification {
action: ClassificationAction::View { name },
} => assert_eq!(name, "Unclassified"),
_ => panic!("expected Classification View"),
}
}
#[test]
fn parse_component_create() {
let cli = Cli::try_parse_from([
"bzr",
"component",
"create",
"--product",
"TestProduct",
"--name",
"Backend",
"--description",
"Backend component",
"--default-assignee",
"dev@test.com",
])
.unwrap();
match cli.command {
Commands::Component {
action:
ComponentAction::Create {
product,
name,
description,
default_assignee,
},
} => {
assert_eq!(product, "TestProduct");
assert_eq!(name, "Backend");
assert_eq!(description, "Backend component");
assert_eq!(default_assignee, "dev@test.com");
}
_ => panic!("expected Component Create"),
}
}
#[test]
fn parse_verbose_flag() {
let cli = Cli::try_parse_from(["bzr", "-vvv", "whoami", "show"]).unwrap();
assert_eq!(cli.verbose, 3);
}
#[test]
fn parse_no_color_flag() {
let cli = Cli::try_parse_from(["bzr", "--no-color", "whoami", "show"]).unwrap();
assert!(cli.no_color);
}
#[test]
fn parse_quiet_flag() {
let cli = Cli::try_parse_from(["bzr", "--quiet", "whoami", "show"]).unwrap();
assert!(cli.quiet);
}
#[test]
fn parse_api_override() {
let cli = Cli::try_parse_from(["bzr", "--api", "xmlrpc", "whoami", "show"]).unwrap();
assert_eq!(cli.api, Some(ApiMode::XmlRpc));
}
#[test]
fn parse_config_set_default() {
let cli = Cli::try_parse_from(["bzr", "config", "set-default", "prod"]).unwrap();
match cli.command {
Commands::Config {
action: ConfigAction::SetDefault { name },
} => assert_eq!(name, "prod"),
_ => panic!("expected Config SetDefault"),
}
}
#[test]
fn parse_config_show() {
let cli = Cli::try_parse_from(["bzr", "config", "show"]).unwrap();
assert!(matches!(
cli.command,
Commands::Config {
action: ConfigAction::Show
}
));
}
#[test]
fn parse_bug_list_with_filters() {
let cli = Cli::try_parse_from([
"bzr",
"bug",
"list",
"--product",
"Firefox",
"--status",
"NEW",
"--limit",
"25",
])
.unwrap();
match cli.command {
Commands::Bug {
action:
BugAction::List {
product,
status,
limit,
..
},
} => {
assert_eq!(product, vec!["Firefox"]);
assert_eq!(status, vec!["NEW"]);
assert_eq!(limit, 25);
}
_ => panic!("expected Bug List"),
}
}
#[test]
fn parse_comment_tag() {
let cli = Cli::try_parse_from([
"bzr", "comment", "tag", "200", "--add", "spam", "--remove", "good",
])
.unwrap();
match cli.command {
Commands::Comment {
action:
CommentAction::Tag {
comment_id,
add,
remove,
},
} => {
assert_eq!(comment_id, 200);
assert_eq!(add, vec!["spam"]);
assert_eq!(remove, vec!["good"]);
}
_ => panic!("expected Comment Tag"),
}
}
#[test]
fn parse_bug_my_defaults() {
let cli = Cli::try_parse_from(["bzr", "bug", "my"]).unwrap();
match cli.command {
Commands::Bug {
action:
BugAction::My {
created,
cc,
all,
limit,
..
},
} => {
assert!(!created);
assert!(!cc);
assert!(!all);
assert_eq!(limit, 50);
}
_ => panic!("expected Bug My"),
}
}
#[test]
fn parse_bug_my_all_conflicts_with_created() {
let result = Cli::try_parse_from(["bzr", "bug", "my", "--all", "--created"]);
assert!(result.is_err(), "--all should conflict with --created");
}
#[test]
fn parse_bug_my_all_conflicts_with_cc() {
let result = Cli::try_parse_from(["bzr", "bug", "my", "--all", "--cc"]);
assert!(result.is_err(), "--all should conflict with --cc");
}
#[test]
fn parse_bug_clone_minimal() {
let cli = Cli::try_parse_from(["bzr", "bug", "clone", "123"]).unwrap();
match cli.command {
Commands::Bug {
action: BugAction::Clone { id, summary, .. },
} => {
assert_eq!(id, "123");
assert!(summary.is_none());
}
_ => panic!("expected Bug Clone"),
}
}
#[test]
fn parse_template_save_with_fields() {
let cli = Cli::try_parse_from([
"bzr",
"template",
"save",
"security-bug",
"--product",
"Security",
"--component",
"Vulnerabilities",
"--severity",
"critical",
])
.unwrap();
match cli.command {
Commands::Template {
action:
TemplateAction::Save {
name,
product,
component,
severity,
..
},
} => {
assert_eq!(name, "security-bug");
assert_eq!(product.as_deref(), Some("Security"));
assert_eq!(component.as_deref(), Some("Vulnerabilities"));
assert_eq!(severity.as_deref(), Some("critical"));
}
_ => panic!("expected Template Save"),
}
}
#[test]
fn parse_query_save_list_kind() {
let cli = Cli::try_parse_from([
"bzr",
"query",
"save",
"firefox-new",
"--product",
"Firefox",
"--status",
"NEW",
"--limit",
"25",
])
.unwrap();
match cli.command {
Commands::Query {
action:
QueryAction::Save {
name,
product,
status,
limit,
..
},
} => {
assert_eq!(name, "firefox-new");
assert_eq!(product, vec!["Firefox"]);
assert_eq!(status, vec!["NEW"]);
assert_eq!(limit, Some(25));
}
_ => panic!("expected Query Save"),
}
}
#[test]
fn parse_query_save_search_kind() {
let cli = Cli::try_parse_from([
"bzr",
"query",
"save",
"crashes",
"--search",
"crash in tab",
"--limit",
"10",
])
.unwrap();
match cli.command {
Commands::Query {
action:
QueryAction::Save {
name,
search,
limit,
..
},
} => {
assert_eq!(name, "crashes");
assert_eq!(search.as_deref(), Some("crash in tab"));
assert_eq!(limit, Some(10));
}
_ => panic!("expected Query Save"),
}
}
#[test]
fn parse_query_run() {
let cli = Cli::try_parse_from(["bzr", "query", "run", "firefox-new"]).unwrap();
match cli.command {
Commands::Query {
action: QueryAction::Run { name, limit, .. },
} => {
assert_eq!(name, "firefox-new");
assert!(limit.is_none());
}
_ => panic!("expected Query Run"),
}
}
#[test]
fn parse_query_run_with_limit_override() {
let cli = Cli::try_parse_from(["bzr", "query", "run", "firefox-new", "--limit", "10"]).unwrap();
match cli.command {
Commands::Query {
action: QueryAction::Run { name, limit, .. },
} => {
assert_eq!(name, "firefox-new");
assert_eq!(limit, Some(10));
}
_ => panic!("expected Query Run"),
}
}
#[test]
fn parse_query_list() {
let cli = Cli::try_parse_from(["bzr", "query", "list"]).unwrap();
assert!(matches!(
cli.command,
Commands::Query {
action: QueryAction::List
}
));
}
#[test]
fn parse_query_show() {
let cli = Cli::try_parse_from(["bzr", "query", "show", "firefox-new"]).unwrap();
match cli.command {
Commands::Query {
action: QueryAction::Show { name },
} => {
assert_eq!(name, "firefox-new");
}
_ => panic!("expected Query Show"),
}
}
#[test]
fn parse_query_delete() {
let cli = Cli::try_parse_from(["bzr", "query", "delete", "firefox-new"]).unwrap();
match cli.command {
Commands::Query {
action: QueryAction::Delete { name },
} => {
assert_eq!(name, "firefox-new");
}
_ => panic!("expected Query Delete"),
}
}
#[test]
fn parse_set_server_tls_ca_cert() {
let cli = Cli::try_parse_from([
"bzr",
"config",
"set-server",
"test",
"--url",
"https://example.com",
"--api-key",
"key",
"--tls-ca-cert",
"/path/to/ca.pem",
])
.unwrap();
match cli.command {
Commands::Config {
action: ConfigAction::SetServer { tls_ca_cert, .. },
} => assert_eq!(tls_ca_cert.as_deref(), Some("/path/to/ca.pem")),
_ => panic!("expected Config SetServer"),
}
}
#[test]
fn parse_set_server_tls_insecure_conflicts_with_ca_cert() {
let result = Cli::try_parse_from([
"bzr",
"config",
"set-server",
"test",
"--url",
"https://example.com",
"--api-key",
"key",
"--tls-insecure",
"--tls-ca-cert",
"/path/to/ca.pem",
]);
assert!(result.is_err(), "should conflict");
}
#[test]
fn parse_set_server_tls_pin_now() {
let cli = Cli::try_parse_from([
"bzr",
"config",
"set-server",
"test",
"--url",
"https://example.com",
"--api-key",
"key",
"--tls-pin-now",
])
.unwrap();
match cli.command {
Commands::Config {
action: ConfigAction::SetServer { tls_pin_now, .. },
} => assert!(tls_pin_now),
_ => panic!("expected Config SetServer"),
}
}
#[test]
fn parse_set_server_tls_pin_sha256() {
let cli = Cli::try_parse_from([
"bzr",
"config",
"set-server",
"test",
"--url",
"https://example.com",
"--api-key",
"key",
"--tls-pin-sha256",
"sha256//abc123",
])
.unwrap();
match cli.command {
Commands::Config {
action: ConfigAction::SetServer { tls_pin_sha256, .. },
} => assert_eq!(tls_pin_sha256.as_deref(), Some("sha256//abc123")),
_ => panic!("expected Config SetServer"),
}
}
#[test]
fn parse_set_server_tls_pin_clear() {
let cli = Cli::try_parse_from([
"bzr",
"config",
"set-server",
"test",
"--url",
"https://example.com",
"--api-key",
"key",
"--tls-pin-clear",
])
.unwrap();
match cli.command {
Commands::Config {
action: ConfigAction::SetServer { tls_pin_clear, .. },
} => assert!(tls_pin_clear),
_ => panic!("expected Config SetServer"),
}
}
#[test]
fn parse_set_server_tls_pin_sha256_conflicts_with_tls_insecure() {
let result = Cli::try_parse_from([
"bzr",
"config",
"set-server",
"test",
"--url",
"https://example.com",
"--api-key",
"key",
"--tls-insecure",
"--tls-pin-sha256",
"sha256//abc123",
]);
assert!(
result.is_err(),
"--tls-insecure should conflict with --tls-pin-sha256"
);
}
#[test]
fn parse_set_server_tls_pin_now_conflicts_with_tls_pin_sha256() {
let result = Cli::try_parse_from([
"bzr",
"config",
"set-server",
"test",
"--url",
"https://example.com",
"--api-key",
"key",
"--tls-pin-now",
"--tls-pin-sha256",
"sha256//abc123",
]);
assert!(
result.is_err(),
"--tls-pin-now should conflict with --tls-pin-sha256"
);
}
#[test]
fn parse_set_server_tls_pin_clear_conflicts_with_tls_pin_now() {
let result = Cli::try_parse_from([
"bzr",
"config",
"set-server",
"test",
"--url",
"https://example.com",
"--api-key",
"key",
"--tls-pin-clear",
"--tls-pin-now",
]);
assert!(
result.is_err(),
"--tls-pin-clear should conflict with --tls-pin-now"
);
}
#[test]
fn parse_whoami_without_subcommand() {
let cli = Cli::try_parse_from(["bzr", "whoami"]).unwrap();
match cli.command {
Commands::Whoami { action } => assert!(action.is_none()),
_ => panic!("expected Whoami"),
}
}
#[test]
fn parse_attachment_upload_with_summary() {
let cli = Cli::try_parse_from([
"bzr",
"attachment",
"upload",
"42",
"patch.diff",
"--summary",
"Fix crash",
])
.unwrap();
match cli.command {
Commands::Attachment {
action:
AttachmentAction::Upload {
bug_id,
file,
summary,
..
},
} => {
assert_eq!(bug_id, 42);
assert_eq!(file, "patch.diff");
assert_eq!(summary.as_deref(), Some("Fix crash"));
}
_ => panic!("expected Attachment Upload"),
}
}
#[test]
fn parse_attachment_upload_with_private_flag() {
let cli = Cli::try_parse_from([
"bzr",
"attachment",
"upload",
"42",
"secret.bin",
"--private",
])
.unwrap();
match cli.command {
Commands::Attachment {
action: AttachmentAction::Upload {
bug_id, private, ..
},
} => {
assert_eq!(bug_id, 42);
assert!(private, "--private should set the flag to true");
}
_ => panic!("expected Attachment Upload"),
}
}
#[test]
fn parse_attachment_upload_without_private_defaults_to_false() {
let cli = Cli::try_parse_from(["bzr", "attachment", "upload", "42", "f.txt"]).unwrap();
match cli.command {
Commands::Attachment {
action: AttachmentAction::Upload { private, .. },
} => assert!(!private, "--private absent should default to false"),
_ => panic!("expected Attachment Upload"),
}
}
#[test]
fn parse_template_delete() {
let cli = Cli::try_parse_from(["bzr", "template", "delete", "security-bug"]).unwrap();
match cli.command {
Commands::Template {
action: TemplateAction::Delete { name },
} => assert_eq!(name, "security-bug"),
_ => panic!("expected Template Delete"),
}
}