#![allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
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 utf-8");
cmd.env("HOME", p)
.env("USERPROFILE", p)
.env("APPDATA", p)
.env("XDG_CONFIG_HOME", p)
.env("DOIGET_MODE", "human");
cmd
}
fn parse_capabilities(stdout: Vec<u8>) -> Value {
let s = String::from_utf8(stdout).expect("stdout utf-8");
serde_json::from_str(&s).expect("capabilities stdout parses as JSON")
}
#[test]
fn capabilities_emits_valid_json_with_all_top_level_keys() {
let dir = TempDir::new().expect("tempdir");
let out = doiget(&dir)
.arg("capabilities")
.assert()
.success()
.get_output()
.stdout
.clone();
let v = parse_capabilities(out);
for key in [
"version",
"features",
"modes",
"global_flags",
"subcommands",
"env_vars",
"mcp_tools",
"docs",
] {
assert!(
v.get(key).is_some(),
"top-level key `{key}` missing from capabilities JSON"
);
}
}
#[test]
fn capabilities_inventory_includes_every_subcommand() {
let dir = TempDir::new().expect("tempdir");
let out = doiget(&dir)
.arg("capabilities")
.assert()
.success()
.get_output()
.stdout
.clone();
let v = parse_capabilities(out);
let subs = v["subcommands"]
.as_array()
.expect("subcommands is an array");
let names: Vec<&str> = subs
.iter()
.map(|s| s["name"].as_str().expect("subcommand name is a string"))
.collect();
for expected in [
"fetch",
"batch",
"info",
"list-recent",
"search",
"bib",
"csl",
"audit-log",
"provenance",
"config",
"serve",
"capabilities",
] {
assert!(
names.contains(&expected),
"subcommand `{expected}` missing from capabilities inventory; got {names:?}"
);
}
let has_graph = names.contains(&"graph");
if cfg!(feature = "citation") {
assert!(
has_graph,
"`citation` feature was compiled in but `graph` is missing \
from capabilities inventory; got {names:?}"
);
} else {
assert!(
!has_graph,
"`graph` appeared in capabilities inventory without the \
`citation` feature; got {names:?}"
);
}
}
#[test]
fn capabilities_fetch_subcommand_carries_artifact_status() {
let dir = TempDir::new().expect("tempdir");
let out = doiget(&dir)
.arg("capabilities")
.assert()
.success()
.get_output()
.stdout
.clone();
let v = parse_capabilities(out);
let fetch = v["subcommands"]
.as_array()
.expect("subcommands array")
.iter()
.find(|s| s["name"] == "fetch")
.expect("fetch subcommand present");
assert_eq!(
fetch["json_mode"]["status"], "artifact",
"fetch.json_mode MUST carry a status discriminant, got {fetch:?}"
);
}
#[test]
fn capabilities_quiet_mode_produces_no_stdout() {
let dir = TempDir::new().expect("tempdir");
Command::cargo_bin("doiget")
.expect("locate doiget binary")
.env("HOME", dir.path())
.env("USERPROFILE", dir.path())
.env("APPDATA", dir.path())
.env("XDG_CONFIG_HOME", dir.path())
.args(["--quiet", "capabilities"])
.assert()
.success()
.stdout(predicates::str::is_empty());
}
#[test]
fn capabilities_quiet_mode_via_doiget_mode_env_produces_no_stdout() {
let dir = TempDir::new().expect("tempdir");
Command::cargo_bin("doiget")
.expect("locate doiget binary")
.env("HOME", dir.path())
.env("USERPROFILE", dir.path())
.env("APPDATA", dir.path())
.env("XDG_CONFIG_HOME", dir.path())
.env("DOIGET_MODE", "quiet")
.arg("capabilities")
.assert()
.success()
.stdout(predicates::str::is_empty());
}
#[test]
fn capabilities_non_tty_default_still_emits_inventory() {
let dir = TempDir::new().expect("tempdir");
let p = dir.path().to_str().expect("tempdir path utf-8");
let out = Command::cargo_bin("doiget")
.expect("locate doiget binary")
.env("HOME", p)
.env("USERPROFILE", p)
.env("APPDATA", p)
.env("XDG_CONFIG_HOME", p)
.env_remove("DOIGET_MODE")
.arg("capabilities")
.assert()
.success()
.get_output()
.stdout
.clone();
let v = parse_capabilities(out);
assert!(
v.get("subcommands").is_some(),
"capabilities MUST emit its JSON inventory on the non-TTY \
implicit-Quiet path (#219 / #220 regression)"
);
}
#[test]
fn capabilities_modes_field_matches_output_mode_enum() {
let dir = TempDir::new().expect("tempdir");
let out = doiget(&dir)
.arg("capabilities")
.assert()
.success()
.get_output()
.stdout
.clone();
let v = parse_capabilities(out);
let modes = v["modes"]
.as_array()
.expect("modes is an array")
.iter()
.map(|m| m.as_str().expect("mode string").to_string())
.collect::<Vec<_>>();
assert_eq!(
modes,
vec!["human", "json", "quiet", "mcp"],
"modes field MUST mirror the OutputMode enum (ADR-0017)"
);
}
#[test]
fn capabilities_env_vars_list_is_non_empty_and_doiget_prefixed() {
let dir = TempDir::new().expect("tempdir");
let out = doiget(&dir)
.arg("capabilities")
.assert()
.success()
.get_output()
.stdout
.clone();
let v = parse_capabilities(out);
let env_vars = v["env_vars"].as_array().expect("env_vars is an array");
assert!(!env_vars.is_empty(), "env_vars MUST be non-empty");
for ev in env_vars {
let name = ev["name"].as_str().expect("env var has a name");
assert!(
name.starts_with("DOIGET_"),
"env var name MUST use DOIGET_ prefix, got `{name}`"
);
}
}
#[test]
fn capabilities_mcp_tools_list_is_non_empty_and_doiget_prefixed() {
let dir = TempDir::new().expect("tempdir");
let out = doiget(&dir)
.arg("capabilities")
.assert()
.success()
.get_output()
.stdout
.clone();
let v = parse_capabilities(out);
let tools = v["mcp_tools"].as_array().expect("mcp_tools is an array");
assert!(!tools.is_empty(), "mcp_tools MUST be non-empty");
for t in tools {
let name = t["name"].as_str().expect("mcp tool has a name");
assert!(
name.starts_with("doiget_"),
"MCP tool name MUST use doiget_ prefix, got `{name}`"
);
}
}