#![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::output::OutputMode;
use doiget_cli::commands::search::{run, ExternalArgs, SortArg};
mod common;
use common::env_guard::EnvGuard;
const ENV_KEYS: &[&str] = &[
"DOIGET_OPENALEX_BASE",
"DOIGET_STORE_ROOT",
"DOIGET_LOG_PATH",
"DOIGET_CONTACT_EMAIL",
"DOIGET_MODE",
"HOME",
"USERPROFILE",
];
const SAMPLE: &str = r#"{
"meta": { "count": 1, "per_page": 25 },
"results": [
{
"id": "https://openalex.org/W777",
"doi": "https://doi.org/10.1234/discovery",
"title": "Discovered Paper",
"publication_year": 2023,
"cited_by_count": 5,
"abstract_inverted_index": { "An": [0], "abstract": [1] },
"authorships": [ { "author": { "display_name": "Grace Hopper" } } ],
"open_access": { "oa_status": "gold" }
}
]
}"#;
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 external_search_runs_and_logs_openalex_fetch() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/works"))
.and(query_param(
"filter",
"title_and_abstract.search:tropical tensor networks",
))
.respond_with(ResponseTemplate::new(200).set_body_string(SAMPLE))
.mount(&server)
.await;
let dir = TempDir::new().expect("tempdir");
let root = utf8(&dir);
let store_root = root.join("papers");
let log_path = root.join("access.jsonl");
let guard = EnvGuard::new(ENV_KEYS);
guard.set("DOIGET_OPENALEX_BASE", &server.uri());
guard.set("DOIGET_STORE_ROOT", store_root.as_str());
guard.set("DOIGET_LOG_PATH", log_path.as_str());
guard.set("DOIGET_CONTACT_EMAIL", "doiget@localhost");
guard.set("DOIGET_MODE", "quiet");
guard.set("HOME", root.as_str());
guard.set("USERPROFILE", root.as_str());
let ext = ExternalArgs {
limit: 25,
from_year: None,
to_year: None,
oa_only: false,
min_citations: None,
min_fwci: None,
min_percentile: None,
author: None,
venue: None,
publisher: None,
sort: SortArg::Relevance,
};
let res = run(
"tropical tensor networks".to_string(),
false, None,
ext,
OutputMode::Quiet,
true,
)
.await;
assert!(res.is_ok(), "external search run failed: {res:?}");
let log = std::fs::read_to_string(log_path.as_std_path()).expect("read provenance log");
assert!(
log.contains("\"event\":\"session_start\""),
"missing session_start row in:\n{log}"
);
assert!(
log.contains("\"event\":\"fetch\"") && log.contains("\"source\":\"openalex\""),
"missing openalex fetch row in:\n{log}"
);
assert!(
log.contains("\"event\":\"session_end\""),
"missing session_end row in:\n{log}"
);
}
#[tokio::test]
#[serial]
async fn external_search_min_fwci_and_percentile_become_filter_clauses() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/works"))
.and(query_param(
"filter",
"title_and_abstract.search:tropical tensor networks,fwci:>2.5,cited_by_percentile_year.min:75",
))
.respond_with(ResponseTemplate::new(200).set_body_string(SAMPLE))
.mount(&server)
.await;
let dir = TempDir::new().expect("tempdir");
let root = utf8(&dir);
let store_root = root.join("papers");
let log_path = root.join("access.jsonl");
let guard = EnvGuard::new(ENV_KEYS);
guard.set("DOIGET_OPENALEX_BASE", &server.uri());
guard.set("DOIGET_STORE_ROOT", store_root.as_str());
guard.set("DOIGET_LOG_PATH", log_path.as_str());
guard.set("DOIGET_CONTACT_EMAIL", "doiget@localhost");
guard.set("DOIGET_MODE", "quiet");
guard.set("HOME", root.as_str());
guard.set("USERPROFILE", root.as_str());
let ext = ExternalArgs {
limit: 25,
from_year: None,
to_year: None,
oa_only: false,
min_citations: None,
min_fwci: Some(2.5),
min_percentile: Some(75),
author: None,
venue: None,
publisher: None,
sort: SortArg::Relevance,
};
let res = run(
"tropical tensor networks".to_string(),
false,
None,
ext,
OutputMode::Quiet,
true,
)
.await;
assert!(
res.is_ok(),
"external search with impact filters failed: {res:?}"
);
}