use logbrew_cli::{CliError, parse_command, write_cli_error};
#[test]
fn rejects_non_numeric_limit_with_agent_next_step() {
let error =
parse_command(["logbrew", "logs", "--limit", "banana", "--json"]).expect_err("bad limit");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value = serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "invalid_limit");
assert_eq!(body["message"], "invalid limit: banana");
assert_eq!(body["next"], "use --limit with a positive whole number");
}
#[test]
fn rejects_zero_limit_with_human_next_step() {
let error = parse_command(["logbrew", "issues", "--limit", "0"]).expect_err("bad limit");
let mut output = Vec::new();
write_cli_error(&error, false, &mut output).expect("error writes");
let text = String::from_utf8(output).expect("utf8 output");
assert_eq!(
text,
"invalid limit: 0\nNext: use --limit with a positive whole number\n"
);
}
#[test]
fn rejects_unknown_read_status_with_agent_next_step() {
let error =
parse_command(["logbrew", "issues", "--status", "done", "--json"]).expect_err("bad status");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value = serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "unknown_status");
assert_eq!(body["message"], "unknown issue status: done");
assert_eq!(
body["next"],
"use one of unresolved/open, resolved/closed, ignored"
);
}
#[test]
fn rejects_unknown_set_status_with_human_next_step() {
let error =
parse_command(["logbrew", "set", "issue", "issue_123", "done"]).expect_err("bad status");
let mut output = Vec::new();
write_cli_error(&error, false, &mut output).expect("error writes");
let text = String::from_utf8(output).expect("utf8 output");
assert_eq!(
text,
"unknown issue status: done\nNext: use one of unresolved/open, resolved/closed, ignored\n"
);
}
#[test]
fn rejects_unknown_resources_with_command_specific_next_steps() {
for (args, message, next) in [
(
&["logbrew", "read", "metrics", "--json"][..],
"unknown resource: metrics",
"choose one of logs, issues, actions, releases, trace, issue",
),
(
&["logbrew", "watch", "traces", "--json"][..],
"unknown resource: traces",
"choose logs or actions",
),
(
&["logbrew", "explain", "logs", "--json"][..],
"unknown resource: logs",
"choose issue or trace",
),
(
&["logbrew", "set", "release", "api@1", "resolved", "--json"][..],
"unknown resource: release",
"choose issue",
),
] {
let error = parse_command(args.iter().copied()).expect_err("unknown resource");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value =
serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "unknown_resource");
assert_eq!(body["message"], message);
assert_eq!(body["next"], next);
}
}
#[test]
fn suggests_obvious_top_level_command_typos_for_agents() {
for (command, next) in [
("logg", "did you mean logbrew logs?"),
("releaze", "did you mean logbrew releases?"),
("statuz", "did you mean logbrew status?"),
] {
let error = parse_command(["logbrew", command, "--json"]).expect_err("unknown command");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value =
serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "unknown_command");
assert_eq!(body["message"], format!("unknown command: {command}"));
assert_eq!(body["next"], next);
}
}
#[test]
fn rejects_top_level_flag_typos_as_flags() {
for args in [
&["logbrew", "--bogus", "--json"][..],
&["logbrew", "--json=true", "status", "--json"][..],
] {
let error = parse_command(args.iter().copied()).expect_err("top-level flag fails");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value =
serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "unknown_flag");
assert_eq!(body["message"], format!("unknown flag: {}", args[1]));
assert_eq!(body["next"], "run logbrew --help");
}
}
#[test]
fn rejects_inline_values_on_simple_command_flags_with_command_help() {
assert_eq!(
parse_command(["logbrew", "logs", "--json=true", "--json"]),
Err(CliError::UnsupportedFlag {
flag: "--json=true".to_owned(),
command: "read logs",
next: "run logbrew read logs --help",
})
);
}
#[test]
fn rejects_plural_trace_read_resource_with_singular_next_step() {
let error = parse_command(["logbrew", "read", "traces", "--json"]).expect_err("bad resource");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value = serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "unknown_resource");
assert_eq!(body["message"], "unknown resource: traces");
assert_eq!(
body["next"],
"use singular trace with an id: logbrew read trace <trace_id>"
);
}
#[test]
fn rejects_unknown_command_with_human_help_next_step() {
let error = parse_command(["logbrew", "inspect"]).expect_err("unknown command");
let mut output = Vec::new();
write_cli_error(&error, false, &mut output).expect("error writes");
let text = String::from_utf8(output).expect("utf8 output");
assert_eq!(text, "unknown command: inspect\nNext: run logbrew --help\n");
}
#[test]
fn rejects_release_flag_without_value_before_json() {
let error =
parse_command(["logbrew", "logs", "--release", "--json"]).expect_err("bad release flag");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value = serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "missing_flag_value");
assert_eq!(body["message"], "missing value for --release");
assert_eq!(body["next"], "provide a value after --release");
}
#[test]
fn rejects_single_dash_flag_like_value_with_agent_next_step() {
let error =
parse_command(["logbrew", "logs", "--release", "-x", "--json"]).expect_err("bad release");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value = serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "missing_flag_value");
assert_eq!(body["message"], "missing value for --release");
assert_eq!(body["next"], "provide a value after --release");
}
#[test]
fn keeps_missing_value_recovery_before_later_unsupported_filter() {
for args in [
&[
"logbrew",
"logs",
"--release",
"--status",
"unresolved",
"--json",
][..],
&[
"logbrew",
"logs",
"--release=",
"--status",
"unresolved",
"--json",
][..],
] {
let error = parse_command(args.iter().copied()).expect_err("release value is missing");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value =
serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "missing_flag_value");
assert_eq!(body["message"], "missing value for --release");
assert_eq!(body["next"], "provide a value after --release");
}
}
#[test]
fn keeps_invalid_value_recovery_before_later_unsupported_filter() {
for (args, error_code, message, next) in [
(
&[
"logbrew",
"logs",
"--limit=banana",
"--status",
"unresolved",
"--json",
][..],
"invalid_limit",
"invalid limit: banana",
"use --limit with a positive whole number",
),
(
&[
"logbrew",
"logs",
"--level",
"panic",
"--status",
"unresolved",
"--json",
][..],
"unknown_log_level",
"unknown log level: panic",
"use one of trace, debug, info, warn, error, fatal",
),
(
&[
"logbrew", "issues", "--status", "done", "--level", "error", "--json",
][..],
"unknown_status",
"unknown issue status: done",
"use one of unresolved/open, resolved/closed, ignored",
),
] {
let error = parse_command(args.iter().copied()).expect_err("filter value is invalid");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value =
serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], error_code);
assert_eq!(body["message"], message);
assert_eq!(body["next"], next);
}
}
#[test]
fn keeps_duplicate_flag_recovery_before_later_unsupported_filter() {
for (args, message, next) in [
(
&[
"logbrew",
"logs",
"--json",
"--json",
"--status",
"unresolved",
][..],
"duplicate flag: --json",
"use --json once",
),
(
&[
"logbrew",
"logs",
"--release",
"checkout@1",
"--release",
"checkout@2",
"--status",
"unresolved",
"--json",
][..],
"duplicate flag: --release",
"use --release once",
),
(
&[
"logbrew",
"logs",
"--env",
"production",
"--environment",
"staging",
"--status",
"unresolved",
"--json",
][..],
"duplicate flag: --environment",
"use --environment once",
),
] {
let error = parse_command(args.iter().copied()).expect_err("filter is duplicated");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value =
serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "duplicate_flag");
assert_eq!(body["message"], message);
assert_eq!(body["next"], next);
}
}
#[test]
fn rejects_empty_equals_flag_value_with_agent_next_step() {
let error =
parse_command(["logbrew", "logs", "--release=", "--json"]).expect_err("bad release flag");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value = serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "missing_flag_value");
assert_eq!(body["message"], "missing value for --release");
assert_eq!(body["next"], "provide a value after --release");
}
#[test]
fn rejects_name_flag_without_value_before_environment() {
let error = parse_command([
"logbrew",
"actions",
"--name",
"--environment",
"production",
])
.expect_err("bad name flag");
let mut output = Vec::new();
write_cli_error(&error, false, &mut output).expect("error writes");
let text = String::from_utf8(output).expect("utf8 output");
assert_eq!(
text,
"missing value for --name\nNext: provide a value after --name\n"
);
}
#[test]
fn rejects_alias_flag_without_value_before_json() {
let error = parse_command(["logbrew", "logs", "--env", "--json"]).expect_err("bad env flag");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value = serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "missing_flag_value");
assert_eq!(body["message"], "missing value for --env");
assert_eq!(body["next"], "provide a value after --env");
}
#[test]
fn rejects_duplicate_release_filter_with_agent_next_step() {
let error = parse_command([
"logbrew",
"logs",
"--release",
"api@1",
"--release",
"api@2",
"--json",
])
.expect_err("duplicate release fails");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value = serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "duplicate_flag");
assert_eq!(body["message"], "duplicate flag: --release");
assert_eq!(body["next"], "use --release once");
}
#[test]
fn rejects_duplicate_alias_and_canonical_filters_with_agent_next_step() {
let error = parse_command([
"logbrew",
"logs",
"--env",
"production",
"--environment",
"staging",
"--json",
])
.expect_err("duplicate environment fails");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value = serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "duplicate_flag");
assert_eq!(body["message"], "duplicate flag: --environment");
assert_eq!(body["next"], "use --environment once");
}
#[test]
fn rejects_duplicate_json_with_agent_next_step() {
for args in [
&["logbrew", "--json", "status", "--json"][..],
&["logbrew", "--json", "search", "--json", "--", "--timeout"],
&["logbrew", "help", "logs", "--json", "--json"],
&["logbrew", "logs", "--help", "--json", "--json"],
] {
let error = parse_command(args.iter().copied()).expect_err("duplicate json fails");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value =
serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "duplicate_flag");
assert_eq!(body["message"], "duplicate flag: --json");
assert_eq!(body["next"], "use --json once");
}
}
#[test]
fn rejects_duplicate_login_flag_with_human_next_step() {
let error =
parse_command(["logbrew", "login", "--no-open", "--no-open"]).expect_err("duplicate flag");
let mut output = Vec::new();
write_cli_error(&error, false, &mut output).expect("error writes");
let text = String::from_utf8(output).expect("utf8 output");
assert_eq!(
text,
"duplicate flag: --no-open\nNext: use --no-open once\n"
);
}
#[test]
fn rejects_unexpected_status_argument_with_agent_next_step() {
let error =
parse_command(["logbrew", "status", "production", "--json"]).expect_err("extra arg");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value = serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "unexpected_argument");
assert_eq!(
body["message"],
"unexpected argument for status: production"
);
assert_eq!(body["next"], "run logbrew status --help");
}
#[test]
fn rejects_unexpected_read_argument_with_filter_hint() {
let error = parse_command(["logbrew", "logs", "checkout@1"]).expect_err("extra arg");
let mut output = Vec::new();
write_cli_error(&error, false, &mut output).expect("error writes");
let text = String::from_utf8(output).expect("utf8 output");
assert_eq!(
text,
"unexpected argument for read: checkout@1\nNext: use --release <release> or run logbrew \
read --help\n"
);
}
#[test]
fn rejects_trace_word_after_read_shortcut_with_trace_hint() {
let error = parse_command([
"logbrew",
"logs",
"trace",
"4bf92f3577b34da6a3ce929d0e0e4736",
"--json",
])
.expect_err("trace word after logs fails");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value = serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "unexpected_argument");
assert_eq!(body["message"], "unexpected argument for read: trace");
assert_eq!(
body["next"],
"use --trace <trace_id> or run logbrew trace <trace_id>"
);
}
#[test]
fn rejects_filter_words_after_read_shortcuts_with_specific_hints() {
for (argument, expected_next) in [
(
"env",
"use --environment <environment> or --env <environment>",
),
(
"environment",
"use --environment <environment> or --env <environment>",
),
("release", "use --release <release>"),
(
"project",
"use --project <project_id> or --project-id <project_id>",
),
(
"status",
"use --status unresolved/open, --status resolved/closed, or --status ignored",
),
(
"level",
"use --level trace, debug, info, warn, error, or fatal",
),
("search", "use --search <text>"),
(
"user",
"use --user <distinct_id> or --distinct-id <distinct_id>",
),
("name", "use --name <name>"),
("since", "use --since <duration>"),
("limit", "use --limit with a positive whole number"),
] {
let error = parse_command(["logbrew", "logs", argument, "value", "--json"])
.expect_err("filter word after read shortcut fails");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value =
serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "unexpected_argument");
assert_eq!(
body["message"],
format!("unexpected argument for read: {argument}")
);
assert_eq!(body["next"], expected_next);
}
}
#[test]
fn rejects_flag_like_missing_read_ids_with_agent_next_steps() {
for (args, message, next) in [
(
["logbrew", "read", "trace", "--json"],
"missing argument: trace_id",
"provide a trace id",
),
(
["logbrew", "read", "issue", "--json"],
"missing argument: issue_id",
"provide an issue id",
),
] {
let error = parse_command(args).expect_err("flag is not an id");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value =
serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "missing_argument");
assert_eq!(body["message"], message);
assert_eq!(body["next"], next);
}
}
#[test]
fn rejects_missing_resources_with_command_specific_next_steps() {
for (args, next) in [
(
&["logbrew", "read", "--json"][..],
"choose one of logs, issues, actions, releases, trace, issue",
),
(
&["logbrew", "watch", "--json"][..],
"choose logs or actions",
),
(
&["logbrew", "explain", "--json"][..],
"choose issue or trace",
),
(&["logbrew", "set", "--json"][..], "choose issue"),
] {
let error = parse_command(args.iter().copied()).expect_err("missing resource fails");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value =
serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "missing_argument");
assert_eq!(body["message"], "missing argument: resource");
assert_eq!(body["next"], next);
}
}
#[test]
fn rejects_missing_search_text_with_log_search_next_step() {
for command in ["search", "find", "grep"] {
let error = parse_command(["logbrew", command, "--json"]).expect_err("missing search text");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value =
serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "missing_argument");
assert_eq!(body["message"], format!("missing argument: {command}"));
assert_eq!(
body["next"],
"provide search text or run logbrew logs --help"
);
}
}
#[test]
fn rejects_empty_search_separator_with_log_search_next_step() {
let error = parse_command(["logbrew", "search", "--"]).expect_err("missing search text");
let mut output = Vec::new();
write_cli_error(&error, false, &mut output).expect("error writes");
let text = String::from_utf8(output).expect("utf8 output");
assert_eq!(
text,
"missing argument: search\nNext: provide search text or run logbrew logs --help\n"
);
}
#[test]
fn rejects_empty_logs_separator_with_search_value_next_step() {
let error = parse_command(["logbrew", "logs", "--"]).expect_err("missing search text");
let mut output = Vec::new();
write_cli_error(&error, false, &mut output).expect("error writes");
let text = String::from_utf8(output).expect("utf8 output");
assert_eq!(
text,
"missing value for --search\nNext: provide a value after --search\n"
);
}
#[test]
fn rejects_flag_like_missing_explain_id_with_human_next_step() {
let error = parse_command(["logbrew", "explain", "trace", "--json"])
.expect_err("flag is not an explain id");
let mut output = Vec::new();
write_cli_error(&error, false, &mut output).expect("error writes");
let text = String::from_utf8(output).expect("utf8 output");
assert_eq!(
text,
"missing argument: trace_id\nNext: provide a trace id\n"
);
}
#[test]
fn rejects_flag_like_missing_set_fields_with_agent_next_steps() {
for (args, message, next) in [
(
&["logbrew", "set", "issue", "--json"][..],
"missing argument: issue_id",
"provide an issue id",
),
(
&["logbrew", "set", "issue", "issue_123", "--json"][..],
"missing argument: status",
"provide one of unresolved/open, resolved/closed, ignored",
),
] {
let error = parse_command(args.iter().copied()).expect_err("flag is not a set field");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value =
serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "missing_argument");
assert_eq!(body["message"], message);
assert_eq!(body["next"], next);
}
}
#[test]
fn writes_parse_errors_as_json_for_agents() {
let error = parse_command(["logbrew", "releases", "--bogus", "--json"])
.expect_err("unknown flag fails");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value = serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "unknown_flag");
assert_eq!(body["message"], "unknown flag: --bogus");
assert_eq!(body["next"], "run logbrew read releases --help");
}
#[test]
fn rejects_read_filters_on_login_with_command_help_next_step() {
for (args, message, next) in [
(
["logbrew", "login", "--release", "api@1", "--json"],
"unsupported flag for login: --release",
"run logbrew login --help",
),
(
["logbrew", "logout", "--release", "api@1", "--json"],
"unsupported flag for logout: --release",
"run logbrew logout --help",
),
] {
let error = parse_command(args).expect_err("unsupported flag fails");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value =
serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "unsupported_flag");
assert_eq!(body["message"], message);
assert_eq!(body["next"], next);
}
}
#[test]
fn rejects_read_filters_on_status_with_command_help_next_step() {
let error = parse_command(["logbrew", "status", "--limit", "10", "--json"])
.expect_err("unsupported flag fails");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value = serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "unsupported_flag");
assert_eq!(body["message"], "unsupported flag for status: --limit");
assert_eq!(body["next"], "run logbrew status --help");
}
#[test]
fn rejects_ignored_trace_detail_filters_with_command_help_next_step() {
let error = parse_command([
"logbrew",
"read",
"trace",
"4bf92f3577b34da6a3ce929d0e0e4736",
"--limit",
"10",
"--json",
])
.expect_err("unsupported trace detail filter fails");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value = serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "unsupported_flag");
assert_eq!(body["message"], "unsupported flag for read trace: --limit");
assert_eq!(body["next"], "run logbrew read trace --help");
}
#[test]
fn rejects_detail_filters_before_validating_list_only_values() {
for (args, message, next) in [
(
&[
"logbrew",
"read",
"trace",
"4bf92f3577b34da6a3ce929d0e0e4736",
"--limit",
"0",
"--json",
][..],
"unsupported flag for read trace: --limit",
"run logbrew read trace --help",
),
(
&[
"logbrew",
"4bf92f3577b34da6a3ce929d0e0e4736",
"--limit",
"0",
"--json",
][..],
"unsupported flag for read trace: --limit",
"run logbrew read trace --help",
),
(
&[
"logbrew",
"read",
"issue",
"issue_123",
"--status",
"closed",
"--json",
][..],
"unsupported flag for read issue: --status",
"run logbrew read issue --help",
),
(
&["logbrew", "issue_123", "--status", "closed", "--json"][..],
"unsupported flag for read issue: --status",
"run logbrew read issue --help",
),
] {
let error = parse_command(args.iter().copied()).expect_err("detail filter is unsupported");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value =
serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "unsupported_flag");
assert_eq!(body["message"], message);
assert_eq!(body["next"], next);
}
}
#[test]
fn rejects_log_only_filters_on_issue_lists_with_command_help_next_step() {
let error = parse_command(["logbrew", "issues", "--level", "error", "--json"])
.expect_err("unsupported issue list filter fails");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value = serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "unsupported_flag");
assert_eq!(body["message"], "unsupported flag for read issues: --level");
assert_eq!(body["next"], "run logbrew read issues --help");
}
#[test]
fn rejects_list_filters_that_target_cannot_apply() {
for (args, message, next) in [
(
&["logbrew", "logs", "--status", "unresolved", "--json"][..],
"unsupported flag for read logs: --status",
"run logbrew read logs --help",
),
(
&["logbrew", "logs", "--status", "closed", "--json"][..],
"unsupported flag for read logs: --status",
"run logbrew read logs --help",
),
(
&["logbrew", "logs", "--name", "checkout_failed", "--json"][..],
"unsupported flag for read logs: --name",
"run logbrew read logs --help",
),
(
&["logbrew", "actions", "--status", "unresolved", "--json"][..],
"unsupported flag for read actions: --status",
"run logbrew read actions --help",
),
(
&["logbrew", "issues", "--level", "panic", "--json"][..],
"unsupported flag for read issues: --level",
"run logbrew read issues --help",
),
(
&["logbrew", "releases", "--name", "checkout_failed", "--json"][..],
"unsupported flag for read releases: --name",
"run logbrew read releases --help",
),
(
&["logbrew", "releases", "--level", "panic", "--json"][..],
"unsupported flag for read releases: --level",
"run logbrew read releases --help",
),
] {
let error = parse_command(args.iter().copied()).expect_err("unsupported filter fails");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value =
serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "unsupported_flag");
assert_eq!(body["message"], message);
assert_eq!(body["next"], next);
}
}
#[test]
fn rejects_search_without_value_with_human_next_step() {
let error = parse_command(["logbrew", "logs", "--search", "--json"])
.expect_err("missing search value fails");
let mut output = Vec::new();
write_cli_error(&error, false, &mut output).expect("error writes");
let text = String::from_utf8(output).expect("utf8 output");
assert_eq!(
text,
"missing value for --search\nNext: provide a value after --search\n"
);
}
#[test]
fn rejects_unknown_log_level_with_agent_next_step() {
let error =
parse_command(["logbrew", "logs", "--level", "urgent", "--json"]).expect_err("bad level");
let mut output = Vec::new();
write_cli_error(&error, true, &mut output).expect("error writes");
let body: serde_json::Value = serde_json::from_slice(output.as_slice()).expect("valid json");
assert_eq!(body["ok"], false);
assert_eq!(body["error"], "unknown_log_level");
assert_eq!(body["message"], "unknown log level: urgent");
assert_eq!(
body["next"],
"use one of trace, debug, info, warn, error, fatal"
);
}
#[test]
fn rejects_unknown_log_level_with_human_next_step() {
let error = parse_command(["logbrew", "logs", "--level", "panic"]).expect_err("bad level");
let mut output = Vec::new();
write_cli_error(&error, false, &mut output).expect("error writes");
let text = String::from_utf8(output).expect("utf8 output");
assert_eq!(
text,
"unknown log level: panic\nNext: use one of trace, debug, info, warn, error, fatal\n"
);
}
#[test]
fn rejects_ignored_issue_detail_filters_with_human_next_step() {
let error = parse_command([
"logbrew",
"read",
"issue",
"issue_123",
"--release",
"checkout@1.2.3",
])
.expect_err("unsupported issue detail filter fails");
let mut output = Vec::new();
write_cli_error(&error, false, &mut output).expect("error writes");
let text = String::from_utf8(output).expect("utf8 output");
assert_eq!(
text,
"unsupported flag for read issue: --release\nNext: run logbrew read issue --help\n"
);
}
#[test]
fn rejects_setup_flags_on_watch_with_command_help_next_step() {
let error =
parse_command(["logbrew", "watch", "logs", "--auto"]).expect_err("unsupported flag fails");
let mut output = Vec::new();
write_cli_error(&error, false, &mut output).expect("error writes");
let text = String::from_utf8(output).expect("utf8 output");
assert_eq!(
text,
"unsupported flag for watch: --auto\nNext: run logbrew watch --help\n"
);
}
#[test]
fn writes_parse_errors_with_human_next_step() {
let error = parse_command(["logbrew", "releases", "--bogus"]).expect_err("unknown flag fails");
let mut output = Vec::new();
write_cli_error(&error, false, &mut output).expect("error writes");
let text = String::from_utf8(output).expect("utf8 output");
assert_eq!(
text,
"unknown flag: --bogus\nNext: run logbrew read releases --help\n"
);
}