reserve 0.2.0

Check domain name availability across grouped extensions, straight from the registry
//! End-to-end checks that run the built binary and read stdout, stderr, and the exit code together.

#![allow(
    clippy::expect_used,
    reason = "clippy.toml exempts test modules, and an integration test is a separate crate it cannot reach"
)]

use assert_cmd::Command;
use predicates::prelude::*;

/// @docgen Every test here must answer offline, so only the subcommands that never open a socket are exercised.
fn reserve() -> Command {
    let mut command = Command::cargo_bin("reserve").expect("the binary is built for the test run");
    command.env_remove("RESERVE_CONCURRENCY");
    command.env_remove("RESERVE_TIMEOUT");
    command.env_remove("RESERVE_WIDTH");
    command.env_remove("RESERVE_NO_INPUT");
    // @docgen An exported COLUMNS or TERM would quietly hand the test a different renderer than the one CI runs.
    command.env_remove("COLUMNS");
    command.env_remove("LINES");
    command.env("TERM", "xterm-256color");
    command.env("NO_COLOR", "1");
    command
}

#[test]
fn help_goes_to_stdout_and_exits_zero() {
    reserve()
        .arg("--help")
        .assert()
        .success()
        .stdout(predicate::str::contains("Check one or more names"))
        .stdout(predicate::str::contains("EXAMPLES:"));
}

#[test]
fn version_goes_to_stdout_and_exits_zero() {
    reserve()
        .arg("--version")
        .assert()
        .success()
        .stdout(predicate::str::contains(env!("CARGO_PKG_VERSION")));
}

#[test]
fn every_subcommand_answers_its_own_help() {
    for command in ["groups", "extensions", "config", "doctor", "completions"] {
        reserve().args([command, "--help"]).assert().success();
    }
}

#[test]
fn an_unknown_flag_is_a_usage_error() {
    reserve()
        .arg("--not-a-real-flag")
        .assert()
        .code(2)
        .stderr(predicate::str::contains("--not-a-real-flag"));
}

#[test]
fn a_missing_name_is_refused_before_any_lookup_starts() {
    reserve()
        .assert()
        .code(2)
        .stderr(predicate::str::contains("name.list_empty"));
}

#[test]
fn an_unattended_run_never_waits_for_an_answer() {
    // A prompt belongs to a person at a terminal. Everywhere else the run must
    // fail fast rather than hang forever waiting for input that cannot arrive.
    for extra in [vec!["--no-input"], vec!["--json"], vec![]] {
        let mut command = reserve();
        command.args(&extra);
        command
            .timeout(std::time::Duration::from_secs(20))
            .assert()
            .code(2)
            .stderr(predicate::str::contains("name.list_empty"));
    }

    reserve()
        .env("CI", "true")
        .timeout(std::time::Duration::from_secs(20))
        .assert()
        .code(2)
        .stderr(predicate::str::contains("name.list_empty"));
}

#[test]
fn conflicting_flags_are_refused_by_the_parser() {
    reserve()
        .args(["--no-input", "--interactive", "example"])
        .assert()
        .code(2);
}

#[test]
fn an_unknown_group_names_the_value_and_carries_its_error_id() {
    reserve()
        .args(["extensions", "--group", "definitely-not-a-group"])
        .assert()
        .code(2)
        .stderr(predicate::str::contains("group.unknown"));
}

#[test]
fn the_machine_readable_mode_emits_one_json_document_on_stdout() {
    let output = reserve()
        .args(["groups", "--json"])
        .assert()
        .success()
        .get_output()
        .stdout
        .clone();

    let parsed: serde_json::Value =
        serde_json::from_slice(&output).expect("the json mode emits one complete document");
    assert!(
        parsed.as_array().is_some_and(|rows| !rows.is_empty()),
        "the group listing is a non-empty array"
    );
}

#[test]
fn a_piped_run_carries_no_escape_codes_and_no_progress_line() {
    let output = reserve()
        .args(["extensions", "--group", "popular"])
        .assert()
        .success()
        .get_output()
        .clone();

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(
        !stdout.contains('\u{1b}'),
        "piped output must stay parseable, with no styling"
    );
    assert!(
        !output.stderr.contains(&b'\r'),
        "the progress line must never render when stderr is not a terminal"
    );
}

#[test]
fn the_boolean_environment_fallback_accepts_how_a_script_spells_true() {
    for spelling in ["1", "true", "yes", "on"] {
        reserve()
            .env("RESERVE_NO_INPUT", spelling)
            .args(["groups"])
            .assert()
            .success();
    }
}

#[test]
fn the_completion_script_is_written_for_every_shell_offered() {
    for shell in ["bash", "elvish", "fish", "powershell", "zsh"] {
        reserve()
            .args(["completions", shell])
            .assert()
            .success()
            .stdout(predicate::str::contains("reserve"));
    }
}

#[test]
fn the_diagnostic_command_reports_in_both_forms() {
    reserve().arg("doctor").assert().success();

    let output = reserve()
        .args(["doctor", "--json"])
        .assert()
        .success()
        .get_output()
        .stdout
        .clone();

    let parsed: serde_json::Value =
        serde_json::from_slice(&output).expect("the diagnostic json mode parses");
    assert!(parsed.get("version").is_some(), "the build is identified");
}

#[test]
fn the_resolved_settings_are_reported_with_where_each_came_from() {
    reserve()
        .args(["config", "show"])
        .assert()
        .success()
        .stdout(predicate::str::contains("timeout"))
        .stdout(predicate::str::contains("built-in default"));

    // A value that came from the environment must not be reported as a default.
    reserve()
        .env("RESERVE_TIMEOUT", "60")
        .args(["config", "show"])
        .assert()
        .success()
        .stdout(predicate::str::contains("60"))
        .stdout(predicate::str::contains("environment"));

    // And one that came from the command line must say so. Only the global
    // flags reach a subcommand, so width is the one to check here.
    reserve()
        .args(["config", "show", "--width", "100"])
        .assert()
        .success()
        .stdout(predicate::str::contains("100"))
        .stdout(predicate::str::contains("flag"));

    reserve().args(["config", "path"]).assert().success();
}

#[test]
fn the_diagnostic_names_where_the_registry_list_comes_from() {
    reserve()
        .arg("doctor")
        .assert()
        .success()
        .stdout(predicate::str::contains("Network"))
        .stdout(predicate::str::contains("data.iana.org"));

    let output = reserve()
        .args(["doctor", "--json"])
        .assert()
        .success()
        .get_output()
        .stdout
        .clone();
    let parsed: serde_json::Value =
        serde_json::from_slice(&output).expect("the diagnostic json mode parses");
    assert!(
        parsed.get("network").is_some(),
        "the json form carries the network block the help promises"
    );
    assert!(
        parsed.get("paths").is_some(),
        "the json form carries the paths the text form prints"
    );
}

#[test]
fn append_cannot_start_a_write_on_its_own() {
    reserve()
        .args(["--append", "example", "--tld", "com"])
        .assert()
        .code(2)
        .stderr(predicate::str::contains("append"));
}

#[test]
fn cautious_composes_with_a_pacing_flag_instead_of_refusing_it() {
    reserve()
        .args(["extensions", "--group", "popular"])
        .env("RESERVE_CONCURRENCY", "8")
        .assert()
        .success();
}