#![cfg(feature = "cli")]
use assert_cmd::Command;
use predicates::prelude::*;
fn grr() -> Command {
let mut cmd = Command::cargo_bin("grr").unwrap();
let home = std::env::temp_dir().join("grr-cli-test-home");
cmd.env("HOME", &home).env("USERPROFILE", &home);
cmd
}
fn configured_grr() -> Command {
let mut cmd = grr();
cmd.env("GRR_CONFIG_PATH", "no-such-config-for-tests.toml")
.env("GRR_OAUTH__CLIENT_ID", "test-id.apps.googleusercontent.com")
.env("GRR_OAUTH__CLIENT_SECRET", "test-secret");
cmd
}
#[test]
fn version_output_is_a_banner_with_the_semver_on_the_first_line() {
grr()
.arg("--version")
.assert()
.success()
.stdout(predicate::str::starts_with(concat!(
"grr ",
env!("CARGO_PKG_VERSION"),
"\n"
)))
.stdout(predicate::str::contains("(o) (o)"))
.stdout(predicate::str::contains("\\_/"))
.stdout(predicate::str::contains(
"Google tools from the terminal, at maximum performance",
))
.stdout(predicate::str::contains("\u{1b}").not());
}
#[test]
fn version_draws_the_pixel_mascot_when_the_terminal_advertises_colour() {
let output = grr()
.env_remove("NO_COLOR")
.env("CLICOLOR_FORCE", "1")
.arg("--version")
.assert()
.success()
.get_output()
.stdout
.clone();
let banner = String::from_utf8(output).expect("banner is UTF-8");
let first_line = banner.lines().next().expect("banner has a first line");
assert_eq!(first_line, format!("grr {}", env!("CARGO_PKG_VERSION")));
assert!(banner.contains('\u{2580}'));
assert!(banner.contains("\u{1b}[38;2;"), "no truecolor foreground");
assert!(banner.contains("\u{1b}[48;2;"), "no truecolor background");
assert!(
banner.contains("Google tools from the terminal, at maximum performance"),
"the colour path drops the tagline"
);
assert!(banner.contains("\u{2580}\u{1b}[0m\n"));
}
#[test]
fn no_color_beats_the_force_opt_in() {
grr()
.env("NO_COLOR", "1")
.env("CLICOLOR_FORCE", "1")
.arg("--version")
.assert()
.success()
.stdout(predicate::str::contains("(o) (o)"))
.stdout(predicate::str::contains("\u{2580}").not());
}
#[test]
fn top_level_help_lists_static_commands_and_every_service() {
grr()
.arg("--help")
.assert()
.success()
.stdout(predicate::str::contains("auth"))
.stdout(predicate::str::contains("api"))
.stdout(predicate::str::contains("schema"))
.stdout(predicate::str::contains("transport"))
.stdout(predicate::str::contains("skills"))
.stdout(predicate::str::contains("gmail"))
.stdout(predicate::str::contains("calendar"))
.stdout(predicate::str::contains("drive"))
.stdout(predicate::str::contains("people"))
.stdout(predicate::str::contains("chat"))
.stdout(predicate::str::contains("forms"))
.stdout(predicate::str::contains("tasks"))
.stdout(predicate::str::contains("docs"))
.stdout(predicate::str::contains("sheets"))
.stdout(predicate::str::contains("slides"));
}
#[test]
fn gmail_help_shows_the_resource_tree() {
grr()
.arg("gmail")
.arg("--help")
.assert()
.success()
.stdout(predicate::str::contains("users"))
.stdout(predicate::str::contains("Gmail API operations"));
}
#[test]
fn gmail_users_messages_lists_leaf_commands() {
grr()
.arg("gmail")
.arg("users")
.arg("messages")
.arg("--help")
.assert()
.success()
.stdout(predicate::str::contains("gmail.users.messages.list"))
.stdout(predicate::str::contains("gmail.users.messages.get"))
.stdout(predicate::str::contains("gmail.users.messages.send"));
}
#[test]
fn message_list_help_documents_typed_flags() {
grr()
.arg("gmail")
.arg("users")
.arg("messages")
.arg("list")
.arg("--help")
.assert()
.success()
.stdout(predicate::str::contains("--user-id <USER_ID>"))
.stdout(predicate::str::contains("--max-results <MAX_RESULTS>"))
.stdout(predicate::str::contains("--label-ids <LABEL_IDS>"))
.stdout(predicate::str::contains("--q <Q>"))
.stdout(predicate::str::contains(
"gmail.users.messages.list [OPTIONS] --user-id <USER_ID>",
))
.stdout(predicate::str::contains("--params <JSON>"))
.stdout(predicate::str::contains("--body-file <PATH|->"))
.stdout(predicate::str::contains("--query <KEY=VALUE>"))
.stdout(predicate::str::contains("--dry-run"))
.stdout(predicate::str::contains("-f, --format <FORMAT>"));
}
#[test]
fn a_leaf_rejects_a_missing_required_flag() {
grr()
.arg("gmail")
.arg("users")
.arg("messages")
.arg("list")
.assert()
.failure()
.stderr(predicate::str::contains("--user-id"));
}
#[test]
fn a_leaf_rejects_unknown_flags() {
grr()
.arg("gmail")
.arg("users")
.arg("messages")
.arg("list")
.arg("--user-id")
.arg("me")
.arg("--bogus")
.assert()
.failure()
.stderr(predicate::str::contains("unexpected argument"));
}
#[test]
fn format_rejection_is_a_clap_error() {
grr()
.arg("gmail")
.arg("users")
.arg("messages")
.arg("list")
.arg("--user-id")
.arg("me")
.arg("--format")
.arg("invalid")
.assert()
.failure()
.stderr(predicate::str::contains("invalid value"));
}
#[test]
fn enum_typed_flags_reject_values_outside_the_documented_set() {
grr()
.arg("gmail")
.arg("users")
.arg("messages")
.arg("get")
.arg("--user-id")
.arg("me")
.arg("--id")
.arg("m1")
.arg("--param-format")
.arg("not-a-format")
.assert()
.failure()
.stderr(predicate::str::contains("invalid value"));
}
#[test]
fn bare_gmail_without_subcommand_is_a_usage_error() {
grr().arg("gmail").assert().failure();
}
#[test]
fn auth_help_lists_login_and_status() {
grr()
.arg("auth")
.arg("--help")
.assert()
.success()
.stdout(predicate::str::contains("login"))
.stdout(predicate::str::contains("status"))
.stdout(predicate::str::contains("setup"))
.stdout(predicate::str::contains("--device"));
}
#[test]
fn schema_dumps_json_without_config() {
grr()
.arg("schema")
.assert()
.success()
.stdout(predicate::str::contains("\"name\":\"grr\""))
.stdout(predicate::str::contains("\"subcommands\""))
.stdout(predicate::str::contains("gmail.users.messages.list"))
.stdout(predicate::str::contains("gmail"));
}
#[test]
fn schema_dumps_every_service() {
grr()
.arg("schema")
.assert()
.success()
.stdout(predicate::str::contains("gmail"))
.stdout(predicate::str::contains("calendar"))
.stdout(predicate::str::contains("drive"))
.stdout(predicate::str::contains("people"))
.stdout(predicate::str::contains("chat"))
.stdout(predicate::str::contains("forms"))
.stdout(predicate::str::contains("tasks"))
.stdout(predicate::str::contains("docs"))
.stdout(predicate::str::contains("sheets"))
.stdout(predicate::str::contains("slides"));
}
#[test]
fn schema_table_output_renders_field_value_table() {
grr()
.arg("schema")
.arg("-f")
.arg("table")
.assert()
.success()
.stdout(predicate::str::contains("Field"))
.stdout(predicate::str::contains("Value"))
.stdout(predicate::str::contains("name"))
.stdout(predicate::str::contains("subcommands"));
}
#[test]
fn a_service_with_no_curated_history_is_reachable() {
grr()
.arg("tasks")
.arg("tasklists")
.arg("list")
.arg("--help")
.assert()
.success()
.stdout(predicate::str::contains("tasks.tasklists.list"));
}
#[test]
fn the_generated_tree_and_api_call_print_identical_dry_runs() {
let tree = configured_grr()
.arg("gmail")
.arg("users")
.arg("messages")
.arg("list")
.arg("--user-id")
.arg("me")
.arg("--dry-run")
.assert()
.success();
let tree_output = String::from_utf8_lossy(&tree.get_output().stdout).into_owned();
let api = configured_grr()
.arg("api")
.arg("call")
.arg("gmail.users.messages.list")
.arg("--param")
.arg("userId=me")
.arg("--dry-run")
.assert()
.success();
let api_output = String::from_utf8_lossy(&api.get_output().stdout).into_owned();
assert_eq!(
tree_output, api_output,
"dry-run diverged between the generated tree and `grr api call`"
);
assert!(tree_output.contains("\"dryRun\":true"), "in: {tree_output}");
assert!(
tree_output.contains("https://gmail.googleapis.com/gmail/v1/users/me/messages"),
"in: {tree_output}"
);
}