use assert_cmd::Command;
use predicates::prelude::*;
fn bin() -> Command {
Command::cargo_bin("cargo-fresh").expect("binary built")
}
#[test]
fn version_flag_prints_crate_version() {
bin()
.arg("--version")
.assert()
.success()
.stdout(predicate::str::contains(env!("CARGO_PKG_VERSION")));
}
#[test]
fn help_lists_core_flags() {
let assert = bin().arg("--help").assert().success();
let out = String::from_utf8_lossy(&assert.get_output().stdout).into_owned();
for flag in [
"--dry-run",
"--batch",
"--filter",
"--exclude",
"--include-prerelease",
"--registry-url",
"--format",
"--no-cargo-search-fallback",
"--check-prebuilt",
] {
assert!(out.contains(flag), "help missing {flag}\n--- help ---\n{out}");
}
}
#[test]
fn cargo_subcommand_form_help_works() {
bin()
.args(["fresh", "--help"])
.assert()
.success()
.stdout(predicate::str::contains("--dry-run"));
}
#[test]
fn completion_bash_emits_script() {
let assert = bin().args(["completion", "bash"]).assert().success();
let out = String::from_utf8_lossy(&assert.get_output().stdout).into_owned();
assert!(
out.contains("_cargo-fresh") || out.contains("complete -F"),
"bash completion looks empty:\n{out}"
);
}
#[test]
fn completion_fish_emits_script() {
let assert = bin().args(["completion", "fish"]).assert().success();
let out = String::from_utf8_lossy(&assert.get_output().stdout).into_owned();
assert!(out.contains("complete -c"), "fish completion looks empty:\n{out}");
}
#[test]
fn man_subcommand_emits_roff() {
let assert = bin().arg("man").assert().success();
let out = String::from_utf8_lossy(&assert.get_output().stdout).into_owned();
assert!(out.starts_with(".ie") || out.contains(".TH"), "man output missing roff header:\n{}", &out[..out.len().min(200)]);
for marker in [".TH cargo-fresh", ".SH NAME", ".SH SYNOPSIS", ".SH OPTIONS"] {
assert!(out.contains(marker), "man output missing {marker}");
}
for flag in ["\\-\\-dry\\-run", "\\-\\-format", "\\-\\-include\\-prerelease"] {
assert!(out.contains(flag), "man output missing {flag}");
}
}
#[test]
fn json_mode_keeps_stdout_clean() {
let assert = bin()
.args(["--batch", "--dry-run", "--format=json", "--filter=__nonexistent_pkg_xyz__"])
.assert()
.success();
let out = String::from_utf8_lossy(&assert.get_output().stdout).into_owned();
let trimmed = out.trim();
assert!(
trimmed.starts_with('{') && trimmed.ends_with('}'),
"stdout should be a single JSON object, got:\n{out}"
);
assert!(
trimmed.lines().count() == 1,
"stdout should be exactly one line of JSON, got {} lines:\n{out}",
trimmed.lines().count()
);
assert!(
trimmed.contains("\"schema_version\":2"),
"JSON missing schema_version=2:\n{out}"
);
}
#[test]
fn json_mode_emits_new_contract_fields() {
let assert = bin()
.args(["--batch", "--dry-run", "--format=json", "--filter=__nonexistent_pkg_xyz__"])
.assert()
.success();
let out = String::from_utf8_lossy(&assert.get_output().stdout).into_owned();
let trimmed = out.trim();
for key in [
"\"version_check_errors\":",
"\"selected\":",
"\"attempted\":",
"\"check_errors\":",
] {
assert!(trimmed.contains(key), "JSON missing {key}\n{out}");
}
}
#[test]
fn no_color_env_strips_ansi_from_stderr() {
let assert = bin()
.env("NO_COLOR", "1")
.args(["--batch", "--dry-run", "--filter=__nonexistent_pkg_xyz__"])
.assert()
.success();
let err = String::from_utf8_lossy(&assert.get_output().stderr).into_owned();
assert!(
!err.contains("\x1b["),
"stderr should be ANSI-free under NO_COLOR=1, got:\n{err}"
);
assert!(err.contains("Checking"), "expected 'Checking' verb in stderr:\n{err}");
}
#[test]
fn clicolor_force_keeps_ansi_when_redirected() {
let assert = bin()
.env("CLICOLOR_FORCE", "1")
.env_remove("NO_COLOR")
.args(["--batch", "--dry-run", "--filter=__nonexistent_pkg_xyz__"])
.assert()
.success();
let err = String::from_utf8_lossy(&assert.get_output().stderr).into_owned();
assert!(
err.contains("\x1b["),
"stderr should contain ANSI escapes under CLICOLOR_FORCE=1, got:\n{err}"
);
}
#[test]
fn non_json_mode_keeps_status_off_stdout() {
let assert = bin()
.args(["--batch", "--dry-run", "--filter=__nonexistent_pkg_xyz__"])
.assert()
.success();
let out = String::from_utf8_lossy(&assert.get_output().stdout).into_owned();
assert!(
out.trim().is_empty(),
"stdout should be empty in non-JSON mode, got:\n{out}"
);
}
#[test]
fn jobs_flag_accepts_zero_and_positive() {
for args in [
vec!["-j0", "--help"],
vec!["-j1", "--help"],
vec!["-j4", "--help"],
vec!["--jobs", "16", "--help"],
] {
bin().args(&args).assert().success();
}
}
#[test]
fn jobs_flag_rejects_negative() {
let assert = bin()
.args(["-j", "-1", "--help"])
.assert()
.failure();
let stderr = String::from_utf8_lossy(&assert.get_output().stderr).into_owned();
assert!(
stderr.contains("invalid value") || stderr.contains("-1"),
"expected clap to reject -j -1, got: {stderr}"
);
}