mod common;
use std::sync::Arc;
use aspero_core::{
Agreement, Capabilities, Claim, Error, Page, Provider, ProviderId, ResiliencePolicy, Response,
Result, SearchMode, SearchQuery, WorkId,
};
use aspero_engine::{Engine, Verdict, Via};
use async_trait::async_trait;
use common::*;
#[tokio::test]
async fn a_lookalike_from_the_matcher_leads_to_the_real_paper() {
let (engine, openalex, _) = live_like();
let v = engine
.verify(
&Claim::from_text("Vaswani 2017 Attention is all you need"),
5,
)
.await;
assert_eq!(v.verdict, Verdict::Verified, "{}", v.reason);
let best = &v.report.candidates[v.best.unwrap()];
assert!(best.ids.contains(&"arXiv:1706.03762".parse().unwrap()));
assert_eq!(
openalex.queries(),
[(SearchMode::Keywords, "attention is all you need".to_owned())]
);
let lookalike = v
.report
.candidates
.iter()
.find(|c| c.record.work.issued.unwrap().year() == 2025)
.unwrap();
assert_eq!(lookalike.assessment.year.agreement, Agreement::Mismatch);
}
#[tokio::test]
async fn verify_skips_paid_searches_once_the_claimed_doi_verifies() {
let openalex = Arc::new(
Scripted::new("openalex")
.keyword()
.with(vec![deep_learning()]),
);
let crossref = Arc::new(
Scripted::new("crossref")
.matcher(vec![deep_learning()])
.with(vec![deep_learning()]),
);
let engine = engine(vec![openalex.clone(), crossref]);
let claim = Claim::from_text(
"LeCun Y, Bengio Y, Hinton G. Deep learning. Nature. 2015;521:436-444. doi:10.1038/nature14539",
);
let v = engine.verify(&claim, 5).await;
assert_eq!(v.verdict, Verdict::Verified, "{}", v.reason);
assert!(openalex.queries().is_empty(), "no OpenAlex search needed");
engine.resolve(&claim, 5).await;
assert_eq!(openalex.queries().len(), 1);
}
#[tokio::test]
async fn one_matchers_exact_hit_does_not_stop_the_search_for_the_original() {
let dated = |mut r: aspero_core::SourceRecord, month| {
r.work.issued = aspero_core::PartialDate::new(2015, Some(month), None);
r
};
let digest = dated(
work(
"crossref",
&["10.9999/digest"],
"Deep learning",
&LECUN,
2015,
),
9,
);
let openalex = Arc::new(
Scripted::new("openalex")
.keyword()
.with(vec![dated(deep_learning(), 5)]),
);
let crossref = Arc::new(Scripted::new("crossref").matcher(vec![digest]));
let engine = engine(vec![openalex.clone(), crossref]);
let claim = Claim::from_text("LeCun Y, Bengio Y, Hinton G. Deep learning. 2015.");
let v = engine.verify(&claim, 5).await;
assert_eq!(v.verdict, Verdict::Verified, "{}", v.reason);
assert_eq!(openalex.queries().len(), 1);
let best = &v.report.candidates[v.best.unwrap()];
assert!(best.ids.contains(&"10.1038/nature14539".parse().unwrap()));
}
#[tokio::test]
async fn an_invented_doi_is_a_mismatch_pointing_at_the_real_work() {
let (engine, _, _) = live_like();
let claim = Claim::from_text(
"LeCun, Bengio, Hinton. Deep learning. Nature 2015. doi:10.1038/nature99999",
);
let crossref_matches_real = Arc::new(Scripted::new("crossref").matcher(vec![deep_learning()]));
let engine2 = self::engine(vec![crossref_matches_real]);
let v = engine2.verify(&claim, 5).await;
assert_eq!(v.verdict, Verdict::Mismatch, "{}", v.reason);
assert!(v.reason.contains("does not exist"), "{}", v.reason);
assert!(v.reason.contains("10.1038/nature14539"), "{}", v.reason);
let nothing =
Claim::from_text("Smith J. A paper that was never written. 2019. doi:10.9999/nope");
let v = engine.verify(¬hing, 5).await;
assert_eq!(v.verdict, Verdict::NotFound, "{}", v.reason);
}
#[tokio::test]
async fn a_real_doi_attached_to_the_wrong_citation_is_a_mismatch() {
let crossref = Arc::new(
Scripted::new("crossref")
.matcher(vec![deep_learning()])
.with(vec![deep_learning(), deep_learning_for_ai()]),
);
let engine = engine(vec![crossref]);
let claim = Claim::from_text(
"LeCun Y, Bengio Y, Hinton G. Deep learning. Nature. 2015. doi:10.1145/3448250",
);
let v = engine.verify(&claim, 5).await;
assert_eq!(v.verdict, Verdict::Mismatch, "{}", v.reason);
assert!(v.reason.contains("Deep learning for AI"), "{}", v.reason);
let best = &v.report.candidates[v.best.unwrap()];
assert_eq!(
best.record.ids,
["10.1038/nature14539".parse::<WorkId>().unwrap()]
);
}
#[tokio::test]
async fn the_right_paper_with_the_wrong_year_is_a_mismatch_not_a_miss() {
let (engine, _, _) = live_like();
let claim = Claim::from_text("Vaswani A, Shazeer N. Attention is all you need. NeurIPS 2019.");
let v = engine.verify(&claim, 5).await;
assert_eq!(v.verdict, Verdict::Mismatch, "{}", v.reason);
assert!(v.reason.contains("year (2017)"), "{}", v.reason);
let best = &v.report.candidates[v.best.unwrap()];
assert!(best.ids.contains(&"arXiv:1706.03762".parse().unwrap()));
}
#[tokio::test]
async fn identifier_only_claims_check_existence_without_searching() {
let (engine, openalex, crossref) = live_like();
let mut claim = Claim::default();
claim.ids = vec!["10.1038/nature14539".parse().unwrap()];
let v = engine.verify(&claim, 5).await;
assert_eq!(v.verdict, Verdict::Verified, "{}", v.reason);
assert!(v.reason.contains("exists"));
assert!(openalex.queries().is_empty() && crossref.queries().is_empty());
}
#[tokio::test]
async fn records_sharing_an_identifier_merge_into_one_candidate() {
let openalex = Arc::new(
Scripted::new("openalex")
.keyword()
.with(vec![deep_learning()]),
);
let crossref = Arc::new(Scripted::new("crossref").matcher(vec![deep_learning()]));
let engine = engine(vec![openalex, crossref]);
let report = engine
.resolve(&Claim::from_text("Deep learning, LeCun 2015"), 5)
.await;
assert_eq!(report.candidates.len(), 1);
let vias: Vec<Via> = report.candidates[0]
.sightings
.iter()
.map(|s| s.via)
.collect();
assert_eq!(vias, [Via::CitationMatch, Via::TitleSearch]);
}
#[tokio::test]
async fn provider_failures_make_an_empty_result_unverifiable() {
struct Down(Capabilities);
#[async_trait]
impl Provider for Down {
fn id(&self) -> ProviderId {
ProviderId::from_static("down")
}
fn capabilities(&self) -> &Capabilities {
&self.0
}
async fn search(&self, _: &SearchQuery) -> Result<Response<Page>> {
Err(Error::AuthRejected { message: None })
}
}
let mut caps = Capabilities::default();
caps.search = true;
caps.citation_search = true;
let engine = Engine::builder()
.provider(Arc::new(Down(caps)), ResiliencePolicy::default(), 0)
.build();
let v = engine
.verify(&Claim::from_text("Anything at all, 2020"), 5)
.await;
assert_eq!(v.verdict, Verdict::Unverifiable, "{}", v.reason);
assert_eq!(v.report.errors.len(), 1);
}
#[tokio::test]
async fn abstracts_are_filled_from_other_providers_with_provenance() {
let openalex =
Arc::new(Scripted::new("openalex").with(vec![deep_learning(), attention_real()]));
let crossref = Arc::new(Scripted::new("crossref").with(vec![
with_abstract(
deep_learning(),
"Deep learning allows computational models…",
),
with_abstract(attention_lookalike(), "unrelated"),
]));
let engine = engine(vec![openalex, crossref]);
let has_own = with_abstract(
attention_real(),
"The dominant sequence transduction models…",
);
let mut lacks = deep_learning();
lacks.source = ProviderId::from_static("openalex");
let nowhere = work("openalex", &["10.1000/no-abstract"], "Obscure", &[], 1999);
let report = engine.abstracts(&[&lacks, &has_own, &nowhere]).await;
let found: Vec<Option<(String, String)>> = report
.found
.iter()
.map(|a| {
a.as_ref()
.map(|a| (a.source.to_string(), a.text.chars().take(13).collect()))
})
.collect();
assert_eq!(
found,
[
Some(("crossref".into(), "Deep learning".into())),
Some(("openalex".into(), "The dominant ".into())),
None,
]
);
}