use paper_gap::{analyse, arxiv::Paper, provider, report, repos::Repository};
fn result(title: &str, score: u32) -> report::PaperResult {
report::PaperResult {
paper: Paper {
id: title.into(),
title: title.into(),
abstract_text: "abstract".into(),
authors: vec!["Ada".into()],
published_date: Some("2026-01-01".into()),
updated_date: None,
doi: None,
arxiv_id: Some("2601.00001".into()),
url: "https://arxiv.org/abs/2601.00001".into(),
categories: vec!["cs.PL".into()],
extracted_methods: Vec::new(),
source_provider: "arxiv".into(),
},
code_links: Vec::new(),
repositories: Vec::new(),
repository_matches: Vec::new(),
repository_analyses: Vec::new(),
pwc_outcome: provider::ProviderOutcome::success("papers-with-code"),
repository_search_outcome: provider::ProviderOutcome::success("repository-search"),
repository_inspection_outcomes: Vec::new(),
gap_assessment: analyse::GapAssessment {
implementation_gap_score: score,
components: analyse::ScoreComponents {
no_code_score: score,
..Default::default()
},
gaps: vec![analyse::Gap {
gap_type: analyse::GapType::NoPublicImplementationFound,
points: score,
explanation: "because".into(),
}],
},
}
}
#[test]
fn markdown_ranks_by_gap_score_and_explains_components() {
let scan = report::ScanReport {
schema_version: 1,
provenance: Default::default(),
paper_provider_outcomes: Vec::new(),
paper_results: vec![result("low", 1), result("high", 50)],
};
let markdown = report::render(&scan, report::OutputFormat::Markdown).unwrap();
assert!(markdown.find("## 1. high").unwrap() < markdown.find("## 2. low").unwrap());
assert!(markdown.contains("Implementation gap score: 50"));
assert!(markdown.contains("no_code_score: 50"));
assert!(markdown.contains("NoPublicImplementationFound: +50 — because"));
}
#[test]
fn json_round_trips_scan_report() {
let scan = report::ScanReport {
schema_version: 1,
provenance: report::ReportProvenance {
generated_at: Some("2026-06-29T12:00:00Z".parse().unwrap()),
operation: Some(report::OperationKind::Scan),
selectors: report::ReportSelectors {
query: Some("symbolic computation".into()),
..Default::default()
},
limit: Some(25),
paper_source: Some("all".into()),
repo_source: Some("github".into()),
},
paper_provider_outcomes: Vec::new(),
paper_results: vec![result("paper", 50)],
};
let json = report::render(&scan, report::OutputFormat::Json).unwrap();
let parsed: report::ScanReport = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.schema_version, 1);
assert_eq!(
parsed.provenance.operation,
Some(report::OperationKind::Scan)
);
assert_eq!(
parsed.provenance.selectors.query.as_deref(),
Some("symbolic computation")
);
assert_eq!(parsed.provenance.repo_source.as_deref(), Some("github"));
assert_eq!(parsed.paper_results.len(), 1);
assert_eq!(
parsed.paper_results[0]
.gap_assessment
.implementation_gap_score,
50
);
}
#[test]
fn markdown_shows_repository_quality_signals() {
let mut result = result("paper", 10);
result.repositories.push(Repository {
forge: "github".into(),
url: "https://github.com/a/b".into(),
owner: "a".into(),
name: "b".into(),
description: None,
primary_language: Some("Rust".into()),
stars: None,
forks: None,
license: Some("MIT".into()),
archived: Some(false),
default_branch: Some("main".into()),
last_commit_date: None,
source_provider: "github".into(),
});
result
.repository_matches
.push(paper_gap::repos::RepositoryMatch {
repository: result.repositories[0].clone(),
confidence_score: 60,
evidence: vec![paper_gap::repos::MatchEvidence {
kind: "exact_title".into(),
value: "paper".into(),
points: 60,
}],
});
result
.repository_analyses
.push(analyse::RepositoryAnalysis {
inspection_succeeded: true,
has_readme: true,
has_license: true,
has_tests: true,
has_ci: true,
has_docs: true,
has_examples: true,
has_package_manifest: true,
has_reproducibility_scripts: true,
has_benchmarks: true,
is_notebook_only: false,
is_stale: false,
is_archived: false,
primary_language: Some("Rust".into()),
});
let markdown = report::render(
&report::ScanReport {
schema_version: 1,
provenance: Default::default(),
paper_provider_outcomes: Vec::new(),
paper_results: vec![result],
},
report::OutputFormat::Markdown,
)
.unwrap();
assert!(markdown.contains("https://github.com/a/b (github)"));
assert!(markdown.contains("match 60 [exact_title=paper (+60)]"));
assert!(
markdown.contains("README, license, tests, CI, docs, examples, package, repro, benchmarks")
);
}
#[test]
fn old_json_defaults_provider_outcomes_to_success() {
let mut value = serde_json::to_value(report::ScanReport {
schema_version: 1,
provenance: Default::default(),
paper_provider_outcomes: Vec::new(),
paper_results: vec![result("paper", 1)],
})
.unwrap();
let item = value["paper_results"][0].as_object_mut().unwrap();
item.remove("pwc_outcome");
item.remove("repository_search_outcome");
item.remove("repository_inspection_outcomes");
item.remove("repository_matches");
let parsed: report::ScanReport = serde_json::from_value(value).unwrap();
assert!(parsed.paper_results[0].pwc_outcome.succeeded());
assert!(parsed.paper_results[0].repository_matches.is_empty());
assert!(
parsed.paper_results[0]
.repository_search_outcome
.succeeded()
);
}
#[test]
fn markdown_shows_provider_failure() {
let mut result = result("paper", 0);
result.pwc_outcome = provider::ProviderOutcome {
provider: "papers-with-code".into(),
status: provider::ProviderStatus::RateLimited,
message: Some("HTTP 429".into()),
};
let markdown = report::render(
&report::ScanReport {
schema_version: 1,
provenance: Default::default(),
paper_provider_outcomes: Vec::new(),
paper_results: vec![result],
},
report::OutputFormat::Markdown,
)
.unwrap();
assert!(markdown.contains("Provider warning: papers-with-code rate_limited — HTTP 429"));
}
#[test]
fn old_json_defaults_missing_paper_provider_outcomes() {
let mut value = serde_json::to_value(report::ScanReport {
schema_version: 1,
provenance: Default::default(),
paper_provider_outcomes: Vec::new(),
paper_results: vec![result("paper", 1)],
})
.unwrap();
value
.as_object_mut()
.unwrap()
.remove("paper_provider_outcomes");
let parsed: report::ScanReport = serde_json::from_value(value).unwrap();
assert!(parsed.paper_provider_outcomes.is_empty());
}
#[test]
fn markdown_shows_paper_provider_failure() {
let scan = report::ScanReport {
schema_version: 1,
provenance: Default::default(),
paper_provider_outcomes: vec![provider::ProviderOutcome {
provider: "openalex".into(),
status: provider::ProviderStatus::Unavailable,
message: Some("timeout".into()),
}],
paper_results: Vec::new(),
};
let markdown = report::render(&scan, report::OutputFormat::Markdown).unwrap();
assert!(markdown.contains("Paper provider warning: openalex unavailable — timeout"));
}
#[test]
fn old_json_defaults_schema_and_provenance() {
let parsed: report::ScanReport = serde_json::from_str(r#"{"paper_results":[]}"#).unwrap();
assert_eq!(parsed.schema_version, 0);
assert!(parsed.provenance.operation.is_none());
let markdown = report::render(&parsed, report::OutputFormat::Markdown).unwrap();
assert!(!markdown.contains("Schema version:"));
assert!(!markdown.contains("Operation:"));
}
#[test]
fn markdown_shows_report_provenance() {
let scan = report::ScanReport {
schema_version: 1,
provenance: report::ReportProvenance {
generated_at: Some("2026-06-29T12:00:00Z".parse().unwrap()),
operation: Some(report::OperationKind::Inspect),
selectors: report::ReportSelectors {
arxiv: Some("2603.21852".into()),
..Default::default()
},
limit: Some(1),
paper_source: Some("arxiv".into()),
repo_source: Some("none".into()),
},
paper_provider_outcomes: Vec::new(),
paper_results: Vec::new(),
};
let markdown = report::render(&scan, report::OutputFormat::Markdown).unwrap();
assert!(markdown.contains("Schema version: 1"));
assert!(markdown.contains("Operation: inspect"));
assert!(markdown.contains("Selectors: arxiv=2603.21852"));
assert!(markdown.contains("Repository source: none"));
}