#![allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
use camino::Utf8PathBuf;
use serial_test::serial;
use tempfile::TempDir;
use wiremock::matchers::{method, path, query_param};
use wiremock::{Mock, MockServer, ResponseTemplate};
use doiget_cli::commands::link::run;
use doiget_cli::commands::output::OutputMode;
mod common;
use common::env_guard::EnvGuard;
const ENV_KEYS: &[&str] = &[
"DOIGET_OPENALEX_BASE",
"DOIGET_CACHE_ROOT",
"DOIGET_STORE_ROOT",
"DOIGET_LOG_PATH",
"DOIGET_CONTACT_EMAIL",
"DOIGET_MODE",
"HOME",
"USERPROFILE",
];
const SAMPLE_WORK: &str = r#"{
"meta": { "count": 1 },
"results": [ {
"id": "https://openalex.org/W55",
"doi": "https://doi.org/10.1103/PhysRevB.1",
"title": "Published Version",
"locations": [
{ "landing_page_url": "https://journals.aps.org/prb/abstract/x" },
{ "pdf_url": "https://arxiv.org/abs/2101.54321v2" }
]
} ]
}"#;
fn utf8(dir: &TempDir) -> Utf8PathBuf {
Utf8PathBuf::from_path_buf(dir.path().to_path_buf()).expect("temp dir path must be UTF-8")
}
#[tokio::test]
#[serial]
async fn link_resolves_doi_to_arxiv_and_logs() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/works"))
.and(query_param("filter", "doi:10.1103/physrevb.1"))
.respond_with(ResponseTemplate::new(200).set_body_string(SAMPLE_WORK))
.mount(&server)
.await;
let dir = TempDir::new().expect("tempdir");
let root = utf8(&dir);
let log_path = root.join("access.jsonl");
let guard = EnvGuard::new(ENV_KEYS);
guard.set("DOIGET_OPENALEX_BASE", &server.uri());
guard.set("DOIGET_CACHE_ROOT", root.join("cache").as_str());
guard.set("DOIGET_STORE_ROOT", root.join("papers").as_str());
guard.set("DOIGET_LOG_PATH", log_path.as_str());
guard.set("DOIGET_MODE", "quiet");
guard.set("HOME", root.as_str());
guard.set("USERPROFILE", root.as_str());
let res = run("10.1103/physrevb.1".to_string(), OutputMode::Quiet).await;
assert!(res.is_ok(), "link run failed: {res:?}");
let log = std::fs::read_to_string(log_path.as_std_path()).expect("read provenance log");
assert!(
log.contains("\"event\":\"fetch\"") && log.contains("\"source\":\"openalex\""),
"missing openalex fetch row in:\n{log}"
);
}
#[tokio::test]
#[serial]
async fn link_unknown_doi_exits_nonzero() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/works"))
.respond_with(
ResponseTemplate::new(200)
.set_body_string(r#"{ "meta": { "count": 0 }, "results": [] }"#),
)
.mount(&server)
.await;
let dir = TempDir::new().expect("tempdir");
let root = utf8(&dir);
let guard = EnvGuard::new(ENV_KEYS);
guard.set("DOIGET_OPENALEX_BASE", &server.uri());
guard.set("DOIGET_CACHE_ROOT", root.join("cache").as_str());
guard.set("DOIGET_STORE_ROOT", root.join("papers").as_str());
guard.set("DOIGET_LOG_PATH", root.join("access.jsonl").as_str());
guard.set("DOIGET_MODE", "quiet");
guard.set("HOME", root.as_str());
guard.set("USERPROFILE", root.as_str());
let err = run("10.0000/nope".to_string(), OutputMode::Quiet)
.await
.expect_err("an unmatched DOI must error");
let exit = err
.downcast_ref::<doiget_cli::commands::fetch::CliExit>()
.expect("NOT_FOUND path must yield a CliExit");
assert_ne!(exit.0, 0, "exit code must be non-zero for an unknown DOI");
}
#[tokio::test]
#[serial]
async fn link_rejects_arxiv_input_without_network() {
let guard = EnvGuard::new(ENV_KEYS);
let _ = &guard;
let err = run("arxiv:2401.12345".to_string(), OutputMode::Quiet)
.await
.expect_err("arXiv input must be a usage error");
assert!(err.to_string().contains("Pass a DOI"), "got: {err}");
}