#![allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
use std::io::Write;
use assert_cmd::Command;
use serde_json::Value;
use tempfile::TempDir;
fn doiget(dir: &TempDir) -> Command {
let mut cmd = Command::cargo_bin("doiget").expect("locate doiget binary");
let p = dir.path().to_str().expect("tempdir path is UTF-8");
cmd.env("HOME", p)
.env("USERPROFILE", p)
.env("APPDATA", p)
.env("XDG_CONFIG_HOME", p)
.env("DOIGET_LOG_PATH", dir.path().join("access.jsonl"))
.env("DOIGET_STORE_ROOT", dir.path().join("store"))
.env("DOIGET_CONTACT_EMAIL", "test@example.com");
cmd
}
#[test]
fn batch_json_parse_failure_emits_invalid_ref_jsonl() {
let dir = TempDir::new().expect("tempdir");
let refs = dir.path().join("refs.txt");
{
let mut f = std::fs::File::create(&refs).expect("create refs file");
f.write_all(b"# comment\nnot-a-doi\n\n")
.expect("write refs");
}
let output = doiget(&dir)
.args(["--json", "batch", refs.to_str().unwrap()])
.assert()
.failure() .get_output()
.stdout
.clone();
let stdout = String::from_utf8(output).expect("stdout utf-8");
let lines: Vec<&str> = stdout.lines().filter(|s| !s.trim().is_empty()).collect();
assert_eq!(lines.len(), 1, "exactly one JSONL record, got: {stdout}");
let v: Value = serde_json::from_str(lines[0]).expect("line parses as JSON");
assert_eq!(v["ok"], Value::Bool(false));
assert_eq!(v["ref"], "not-a-doi");
assert_eq!(
v["error"]["code"], "INVALID_REF",
"ERRORS.md §3 INVALID_REF on parse failure"
);
assert!(
v["error"]["message"].is_string() && !v["error"]["message"].as_str().unwrap().is_empty(),
"error.message MUST be a non-empty string"
);
}
#[test]
fn batch_json_fetch_failure_emits_fetch_error_jsonl() {
let dir = TempDir::new().expect("tempdir");
let refs = dir.path().join("refs.txt");
std::fs::File::create(&refs)
.expect("create refs file")
.write_all(b"arxiv:2401.99999\n")
.expect("write refs");
let output = doiget(&dir)
.env("DOIGET_ARXIV_BASE", "http://127.0.0.1:1/")
.args(["--json", "batch", refs.to_str().unwrap()])
.assert()
.failure()
.get_output()
.stdout
.clone();
let stdout = String::from_utf8(output).expect("stdout utf-8");
let lines: Vec<&str> = stdout.lines().filter(|s| !s.trim().is_empty()).collect();
assert_eq!(lines.len(), 1, "exactly one JSONL record, got: {stdout}");
let v: Value = serde_json::from_str(lines[0]).expect("line parses as JSON");
assert_eq!(v["ok"], Value::Bool(false));
assert_eq!(v["ref"], "arxiv:2401.99999");
assert_eq!(v["error"]["code"], "FETCH_ERROR");
assert!(
v["error"]["message"].is_string() && !v["error"]["message"].as_str().unwrap().is_empty(),
"error.message MUST be a non-empty string"
);
}
#[test]
fn batch_human_mode_remains_silent_on_stdout() {
let dir = TempDir::new().expect("tempdir");
let refs = dir.path().join("refs.txt");
std::fs::File::create(&refs)
.expect("create refs file")
.write_all(b"not-a-doi\n")
.expect("write refs");
let output = doiget(&dir)
.env("DOIGET_MODE", "human")
.args(["batch", refs.to_str().unwrap()])
.assert()
.failure()
.get_output()
.stdout
.clone();
let stdout = String::from_utf8(output).expect("stdout utf-8");
assert!(
stdout.is_empty(),
"human-mode batch stdout MUST be empty (summary is stderr): {stdout:?}"
);
}