use patent::model::{Match, Query, Source as SourceId};
use patent::sources::artifacthub::ArtifactHub;
use patent::sources::aur::Aur;
use patent::sources::crates_io::CratesIo;
use patent::sources::docker_hub::DockerHub;
use patent::sources::github::GitHub;
use patent::sources::go::GoPkgDev;
use patent::sources::hacker_news::HackerNews;
use patent::sources::hex::Hex;
use patent::sources::homebrew::Homebrew;
use patent::sources::maven::Maven;
use patent::sources::npm::Npm;
use patent::sources::nuget::NuGet;
use patent::sources::packagist::Packagist;
use patent::sources::pypi::PyPI;
use patent::sources::rubygems::RubyGems;
use patent::sources::vscode::VsCodeMarketplace;
use patent::sources::SourceAdapter;
fn client() -> reqwest::Client {
reqwest::Client::builder()
.user_agent(concat!(
"patent/",
env!("CARGO_PKG_VERSION"),
" (prior-art search; https://github.com/r14dd/patent)"
))
.build()
.unwrap()
}
fn query(idea: &str, keywords: &[&str]) -> Query {
Query {
idea: idea.to_string(),
keywords: keywords.iter().map(|k| k.to_string()).collect(),
}
}
fn assert_live(matches: &[Match], expected: SourceId) {
assert!(
!matches.is_empty(),
"{expected} returned zero matches for a popular query — the live API \
shape has likely drifted and the adapter can no longer parse it"
);
for m in matches {
assert!(
!m.name.trim().is_empty(),
"{expected}: a match has an empty name: {m:?}"
);
assert!(
!m.url.trim().is_empty(),
"{expected}: match {:?} has an empty url",
m.name
);
assert_eq!(
m.source, expected,
"{expected}: a match is attributed to the wrong source"
);
}
eprintln!("✓ {expected}: {} matches", matches.len());
}
macro_rules! live {
($name:ident, $adapter:expr, $source:expr, $idea:expr, $keywords:expr) => {
#[tokio::test]
#[ignore = "hits the live network; run with --ignored"]
async fn $name() {
let adapter = $adapter;
let matches = adapter
.search(&query($idea, $keywords))
.await
.unwrap_or_else(|e| panic!("{} live search errored: {e}", $source));
assert_live(&matches, $source);
}
};
}
live!(
live_crates_io,
CratesIo::new(client()),
SourceId::CratesIo,
"a fast async runtime for rust",
&["async", "runtime"]
);
live!(
live_github,
GitHub::new(client()),
SourceId::GitHub,
"a kubernetes command line tool",
&["kubernetes", "cli"]
);
live!(
live_npm,
Npm::new(client()),
SourceId::Npm,
"react component library",
&["react"]
);
#[tokio::test]
#[ignore = "live network; also an ACCEPTED DEGRADATION — PyPI is bot-walled with no keyless search, see comment"]
async fn live_pypi() {
let adapter = PyPI::new(client());
let matches = adapter
.search(&query("http requests library for python", &["requests"]))
.await
.unwrap_or_else(|e| panic!("PyPI live search errored: {e}"));
assert_live(&matches, SourceId::PyPI);
}
live!(
live_hacker_news,
HackerNews::new(client()),
SourceId::HackerNews,
"database",
&["database"]
);
live!(
live_go,
GoPkgDev::new(client()),
SourceId::Go,
"web framework for go",
&["gin"]
);
live!(
live_maven,
Maven::new(client()),
SourceId::Maven,
"json serialization library",
&["jackson"]
);
live!(
live_rubygems,
RubyGems::new(client()),
SourceId::RubyGems,
"a ruby web framework",
&["rails"]
);
live!(
live_docker_hub,
DockerHub::new(client()),
SourceId::DockerHub,
"a web server container image",
&["nginx"]
);
live!(
live_vscode,
VsCodeMarketplace::new(client()),
SourceId::VsCodeMarketplace,
"python language support",
&["python"]
);
live!(
live_nuget,
NuGet::new(client()),
SourceId::NuGet,
"a json library",
&["json"]
);
live!(
live_homebrew,
Homebrew::new(client()),
SourceId::Homebrew,
"download files from the web",
&["wget"]
);
live!(
live_packagist,
Packagist::new(client()),
SourceId::Packagist,
"a php web framework",
&["laravel"]
);
live!(
live_hex,
Hex::new(client()),
SourceId::Hex,
"an elixir web framework",
&["phoenix"]
);
live!(
live_artifacthub,
ArtifactHub::new(client()),
SourceId::ArtifactHub,
"prometheus monitoring helm chart",
&["prometheus"]
);
live!(
live_aur,
Aur::new(client()),
SourceId::Aur,
"an aur helper",
&["yay"]
);