#![allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
use assert_cmd::Command;
use predicates::prelude::*;
use tempfile::TempDir;
#[test]
fn fetch_invalid_ref_emits_cargo_style_error_and_exit_2() {
let td = TempDir::new().expect("tempdir");
Command::cargo_bin("doiget")
.expect("doiget binary built")
.env("DOIGET_STORE_ROOT", td.path().to_str().expect("utf-8"))
.env("DOIGET_LOG_PATH", "")
.args(["fetch", "not a doi"])
.assert()
.code(2)
.stderr(predicate::str::contains("error[INVALID_REF]: invalid ref"))
.stdout(predicate::str::is_empty());
}
fn ref_taking_commands() -> Vec<Vec<String>> {
const NOT_A_REF: &[&str] = &[
"verify",
"batch",
"resolve-citation",
"batch-resolve-citations",
"version",
"config",
"serve",
"search",
"list-recent",
"provenance",
"audit-log",
"lint",
"capabilities",
"help",
];
let out = Command::cargo_bin("doiget")
.expect("doiget binary built")
.arg("--help")
.output()
.expect("run doiget --help");
let help = String::from_utf8_lossy(&out.stdout);
let mut found: Vec<String> = help
.lines()
.filter(|l| l.starts_with(" "))
.filter_map(|l| l.split_whitespace().next())
.filter(|n| !n.is_empty() && !n.starts_with('-'))
.filter(|n| n.chars().all(|c| c.is_ascii_lowercase() || c == '-'))
.filter(|n| !NOT_A_REF.contains(n))
.map(str::to_string)
.collect();
found.sort();
found.dedup();
assert!(
found.len() >= 9,
"parsed only {found:?} from --help — the parser has drifted from clap's output"
);
found
.into_iter()
.map(|c| match c.as_str() {
"source" => vec!["source", "--out", ".", BAD_REF]
.into_iter()
.map(str::to_string)
.collect(),
"annotate" => vec!["annotate", BAD_REF, "a note"]
.into_iter()
.map(str::to_string)
.collect(),
_ => vec![c, BAD_REF.to_string()],
})
.collect()
}
const BAD_REF: &str = "not a doi";
#[test]
fn every_ref_taking_command_emits_the_error_code_contract() {
let commands = ref_taking_commands();
let td = TempDir::new().expect("tempdir");
let root = td.path().to_str().expect("utf-8");
let mut broken: Vec<String> = Vec::new();
for argv in &commands {
let mut cmd = Command::cargo_bin("doiget").expect("doiget binary built");
cmd.env("DOIGET_STORE_ROOT", root)
.env("DOIGET_LOG_PATH", "")
.env("DOIGET_CONTACT_EMAIL", "test@example.com")
.args(argv);
let out = cmd.output().expect("run doiget");
let stderr = String::from_utf8_lossy(&out.stderr);
let first = stderr.lines().next().unwrap_or("");
if first.starts_with("error: unrecognized subcommand") {
continue;
}
if !first.starts_with("error[") {
broken.push(format!("{}: {first}", argv.join(" ")));
}
if stderr.contains("Caused by:") {
broken.push(format!("{}: leaked a `Caused by:` chain", argv.join(" ")));
}
}
assert!(
broken.is_empty(),
"these commands do not honour the error[CODE] contract for an invalid ref:\n {}",
broken.join("\n ")
);
}
#[test]
fn every_ref_taking_command_exits_2_for_an_invalid_ref() {
let commands = ref_taking_commands();
let td = TempDir::new().expect("tempdir");
let root = td.path().to_str().expect("utf-8");
let mut wrong: Vec<String> = Vec::new();
for argv in &commands {
let mut cmd = Command::cargo_bin("doiget").expect("doiget binary built");
cmd.env("DOIGET_STORE_ROOT", root)
.env("DOIGET_LOG_PATH", "")
.env("DOIGET_CONTACT_EMAIL", "test@example.com")
.args(argv);
let out = cmd.output().expect("run doiget");
let stderr = String::from_utf8_lossy(&out.stderr);
if stderr.starts_with("error: unrecognized subcommand") {
continue;
}
if out.status.code() != Some(2) {
wrong.push(format!("{}: exit {:?}", argv.join(" "), out.status.code()));
}
}
assert!(
wrong.is_empty(),
"docs/ERRORS.md §4: an unparsable ref is misuse (exit 2), not a failed fetch \
(exit 1). These disagree:\n {}",
wrong.join("\n ")
);
}
#[test]
fn verify_renders_a_missing_input_file_as_misuse_not_an_anyhow_dump() {
let td = TempDir::new().expect("tempdir");
Command::cargo_bin("doiget")
.expect("doiget binary built")
.env("DOIGET_STORE_ROOT", td.path().to_str().expect("utf-8"))
.env("DOIGET_LOG_PATH", "")
.args(["verify", "no-such-file.bib"])
.assert()
.code(2)
.stderr(
predicate::str::starts_with("error: failed to read reference file")
.and(predicate::str::contains("Caused by:").not()),
)
.stdout(predicate::str::is_empty());
}
#[test]
fn an_unrecognised_ref_names_neither_shape_rather_than_arxiv() {
let td = TempDir::new().expect("tempdir");
Command::cargo_bin("doiget")
.expect("doiget binary built")
.env("DOIGET_STORE_ROOT", td.path().to_str().expect("utf-8"))
.env("DOIGET_LOG_PATH", "")
.args(["fetch", "not-a-doi"])
.assert()
.stderr(
predicate::str::contains("neither a DOI")
.and(predicate::str::contains("nor an arXiv id")),
);
}