mod common;
use common::{assert_only_ascii, rusty_pwgen_cmd};
#[test]
fn default_invocation_emits_one_8char_password() {
let output = rusty_pwgen_cmd().assert().success().get_output().clone();
let stdout = String::from_utf8_lossy(&output.stdout);
let lines: Vec<&str> = stdout.lines().collect();
assert_eq!(
lines.len(),
1,
"piped stdout → exactly one password; got {lines:?}"
);
assert_eq!(lines[0].len(), 8, "default length is 8");
assert_only_ascii(&output.stdout);
}
#[test]
fn explicit_length_12_count_5() {
let output = rusty_pwgen_cmd()
.arg("12")
.arg("5")
.assert()
.success()
.get_output()
.clone();
let lines: Vec<&str> = std::str::from_utf8(&output.stdout)
.unwrap()
.lines()
.collect();
assert_eq!(lines.len(), 5);
for line in &lines {
assert_eq!(line.len(), 12, "expected 12-char passwords; got {line:?}");
}
}
#[test]
fn consecutive_runs_differ() {
let a = rusty_pwgen_cmd()
.arg("12")
.assert()
.success()
.get_output()
.clone();
let b = rusty_pwgen_cmd()
.arg("12")
.assert()
.success()
.get_output()
.clone();
assert_ne!(
a.stdout, b.stdout,
"consecutive default-mode runs must differ"
);
}
#[test]
fn default_charset_is_alphanumeric_no_symbols() {
let output = rusty_pwgen_cmd()
.arg("16")
.arg("10")
.assert()
.success()
.get_output()
.clone();
for byte in output.stdout.iter() {
if byte.is_ascii_whitespace() {
continue;
}
assert!(
byte.is_ascii_alphanumeric(),
"default charset (no -y) must be alphanumeric only; got {:?}",
*byte as char
);
}
}
#[test]
fn secure_mode_alphanumeric_with_uniform_sample() {
let output = rusty_pwgen_cmd()
.arg("-s")
.arg("16")
.arg("100")
.assert()
.success()
.get_output()
.clone();
let s = std::str::from_utf8(&output.stdout).unwrap();
for line in s.lines() {
assert_eq!(line.len(), 16);
assert!(
line.bytes().all(|b| b.is_ascii_alphanumeric()),
"secure default → alphanumeric only; got {line:?}"
);
}
}
#[test]
fn secure_with_symbols_can_include_symbol_chars() {
let output = rusty_pwgen_cmd()
.arg("-s")
.arg("-y")
.arg("32")
.arg("100")
.assert()
.success()
.get_output()
.clone();
let s = std::str::from_utf8(&output.stdout).unwrap();
let has_symbol = s
.bytes()
.any(|b| !b.is_ascii_alphanumeric() && b != b'\n' && b != b'\r' && b.is_ascii());
assert!(
has_symbol,
"with -y, expected at least one symbol char in 3200 bytes"
);
}
#[test]
fn ambiguous_filter_drops_l1_oi0() {
let output = rusty_pwgen_cmd()
.arg("-s")
.arg("-B")
.arg("16")
.arg("100")
.assert()
.success()
.get_output()
.clone();
for byte in output.stdout.iter() {
if byte.is_ascii_whitespace() {
continue;
}
assert!(
!b"l1O0I".contains(byte),
"ambiguous char '{}' in output despite -B",
*byte as char
);
}
}
#[test]
fn no_vowels_implies_secure_and_excludes_vowels() {
let output = rusty_pwgen_cmd()
.arg("-v")
.arg("16")
.arg("50")
.assert()
.success()
.get_output()
.clone();
for byte in output.stdout.iter() {
if byte.is_ascii_whitespace() {
continue;
}
assert!(
!b"aeiouAEIOU".contains(byte),
"vowel '{}' in -v output",
*byte as char
);
}
}
#[test]
fn no_capitalize_and_no_numerals_lowercase_only() {
let output = rusty_pwgen_cmd()
.arg("-A")
.arg("-0")
.arg("12")
.arg("20")
.assert()
.success()
.get_output()
.clone();
for byte in output.stdout.iter() {
if byte.is_ascii_whitespace() {
continue;
}
assert!(
byte.is_ascii_lowercase(),
"expected lowercase only with -A -0; got '{}'",
*byte as char
);
}
}
#[test]
fn default_mode_rejects_conflicting_caps_flags() {
let output = rusty_pwgen_cmd()
.arg("-c")
.arg("-A")
.arg("8")
.arg("1")
.assert()
.failure()
.get_output()
.clone();
assert_eq!(output.status.code(), Some(2));
}
#[test]
fn default_mode_rejects_conflicting_one_vs_columnar_flags() {
let output = rusty_pwgen_cmd()
.arg("-1")
.arg("-C")
.arg("8")
.arg("1")
.assert()
.failure()
.get_output()
.clone();
assert_eq!(output.status.code(), Some(2));
}
#[test]
fn length_below_5_falls_back_to_secure_with_warning() {
let output = rusty_pwgen_cmd()
.arg("3")
.arg("5")
.assert()
.success()
.get_output()
.clone();
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("using secure mode"),
"expected Default-mode warning for length < 5; got: {stderr:?}"
);
for line in std::str::from_utf8(&output.stdout).unwrap().lines() {
assert_eq!(line.len(), 3);
}
}
#[test]
fn force_one_column_via_dash_one() {
let output = rusty_pwgen_cmd()
.arg("-1")
.arg("8")
.arg("5")
.assert()
.success()
.get_output()
.clone();
let lines: Vec<&str> = std::str::from_utf8(&output.stdout)
.unwrap()
.lines()
.collect();
assert_eq!(lines.len(), 5);
for line in &lines {
assert!(!line.contains(' '));
}
}
#[test]
fn force_columnar_via_dash_capital_c() {
let output = rusty_pwgen_cmd()
.arg("-C")
.arg("8")
.arg("16")
.env("RUSTY_PWGEN_TEST_COLUMNS", "80")
.assert()
.success()
.get_output()
.clone();
let s = std::str::from_utf8(&output.stdout).unwrap();
assert!(
s.lines().any(|line| line.contains(' ')),
"expected columnar (spaces between passwords); got {s:?}"
);
}
#[test]
fn version_flag_succeeds() {
rusty_pwgen_cmd().arg("--version").assert().success();
}
#[test]
fn help_flag_succeeds() {
rusty_pwgen_cmd().arg("--help").assert().success();
}