use std::process::Command;
fn cleanlib_bin() -> std::path::PathBuf {
std::path::PathBuf::from(std::env!("CARGO_BIN_EXE_cleanlib"))
}
#[test]
fn valid_risk_accept_succeeds_and_emits_yaml() {
let out = Command::new(cleanlib_bin())
.args([
"risk-accept",
"--package",
"cors",
"--version",
"2.8.4",
"--justification",
"Upstream patch pending; mitigated by input sanitization.",
])
.output()
.expect("invoke cleanlib");
assert!(
out.status.success(),
"valid risk-accept should exit 0; stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.contains("cors"),
"YAML should carry the package; got: {stdout}"
);
}
fn assert_blank_rejected(flag: &str, args: &[&str], needle: &str) {
let out = Command::new(cleanlib_bin())
.args(args)
.output()
.expect("invoke cleanlib");
assert!(
!out.status.success(),
"empty --{flag} must exit non-zero (was silently accepted); stdout: {}",
String::from_utf8_lossy(&out.stdout)
);
let stderr = String::from_utf8_lossy(&out.stderr).to_lowercase();
assert!(
stderr.contains(needle),
"error should name --{flag}; got stderr: {stderr}"
);
assert!(
String::from_utf8_lossy(&out.stdout).trim().is_empty(),
"empty --{flag} must NOT emit a YAML record to stdout"
);
}
#[test]
fn cleanlib_179_empty_justification_rejected() {
assert_blank_rejected(
"justification",
&[
"risk-accept",
"--package",
"cors",
"--version",
"2.8.4",
"--justification",
"",
],
"justification",
);
}
#[test]
fn cleanlib_180_empty_package_rejected() {
assert_blank_rejected(
"package",
&[
"risk-accept",
"--package",
"",
"--version",
"2.8.4",
"--justification",
"j",
],
"package",
);
}
#[test]
fn cleanlib_181_empty_version_rejected() {
assert_blank_rejected(
"version",
&[
"risk-accept",
"--package",
"cors",
"--version",
"",
"--justification",
"j",
],
"version",
);
}
#[test]
fn cleanlib_182_help_documents_single_quote_workaround() {
let out = Command::new(cleanlib_bin())
.args(["risk-accept", "--help"])
.output()
.expect("invoke cleanlib risk-accept --help");
assert!(out.status.success(), "--help should exit 0");
let help = String::from_utf8_lossy(&out.stdout);
assert!(
help.contains("single quote")
|| help.contains("SINGLE quote")
|| help.contains("single-quote"),
"justification help should mention the single-quote workaround; got: {help}"
);
assert!(
help.contains('!'),
"justification help should mention the `!` history-expansion gotcha; got: {help}"
);
}
#[test]
fn cleanlib_183_overwrite_creates_timestamped_backup() {
let dir = tempfile::tempdir().expect("tempdir");
let target = dir.path().join("risk.yaml");
let run = |just: &str| {
Command::new(cleanlib_bin())
.args([
"risk-accept",
"--package",
"cors",
"--version",
"2.8.4",
"--justification",
just,
"--write-to",
target.to_str().unwrap(),
])
.output()
.expect("invoke cleanlib")
};
assert!(
run("first rationale").status.success(),
"first write should succeed"
);
assert!(
run("second rationale").status.success(),
"second write should succeed"
);
let backups: Vec<_> = std::fs::read_dir(dir.path())
.unwrap()
.filter_map(|e| e.ok())
.map(|e| e.file_name().to_string_lossy().into_owned())
.filter(|n| n.contains("cleanlib-backup"))
.collect();
assert_eq!(
backups.len(),
1,
"second write must leave exactly one timestamped backup; dir had: {backups:?}"
);
let backup_path = dir.path().join(&backups[0]);
let backup_body = std::fs::read_to_string(&backup_path).unwrap();
assert!(
backup_body.contains("first rationale"),
"backup should preserve the prior record's content"
);
let live = std::fs::read_to_string(&target).unwrap();
assert!(
live.contains("second rationale"),
"live file holds the latest write"
);
}
#[test]
fn cleanlib_183_force_skips_backup() {
let dir = tempfile::tempdir().expect("tempdir");
let target = dir.path().join("risk.yaml");
let run = |force: bool| {
let mut args = vec![
"risk-accept",
"--package",
"cors",
"--version",
"2.8.4",
"--justification",
"rationale",
"--write-to",
target.to_str().unwrap(),
];
if force {
args.push("--force");
}
Command::new(cleanlib_bin())
.args(args)
.output()
.expect("invoke cleanlib")
};
assert!(run(false).status.success());
assert!(run(true).status.success(), "force overwrite should succeed");
let backups = std::fs::read_dir(dir.path())
.unwrap()
.filter_map(|e| e.ok())
.filter(|e| e.file_name().to_string_lossy().contains("cleanlib-backup"))
.count();
assert_eq!(backups, 0, "--force must NOT create a backup");
}
#[test]
fn cleanlib_184_missing_dir_reports_clearly_not_permission_denied() {
let dir = tempfile::tempdir().expect("tempdir");
let missing = dir
.path()
.join("does")
.join("not")
.join("exist")
.join("risk.yaml");
let out = Command::new(cleanlib_bin())
.args([
"risk-accept",
"--package",
"cors",
"--version",
"2.8.4",
"--justification",
"rationale",
"--write-to",
missing.to_str().unwrap(),
])
.output()
.expect("invoke cleanlib");
assert!(
!out.status.success(),
"missing parent dir must exit non-zero"
);
let stderr = String::from_utf8_lossy(&out.stderr).to_lowercase();
assert!(
stderr.contains("does not exist"),
"error should say the directory does not exist; got: {stderr}"
);
assert!(
!stderr.contains("permission denied"),
"error must NOT mislead with 'Permission denied'; got: {stderr}"
);
}