165 lines
6.5 KiB
Rust
165 lines
6.5 KiB
Rust
//! Show-page queries (`tmdbid:<id> season:<n>`, no free text): releases are
|
|
//! matched to the show BY NAME against its card in meta-core, and stamped as a
|
|
//! title match. Own binary because the card source is read from
|
|
//! `META_CORE_URL`, set once here.
|
|
|
|
use std::collections::BTreeMap;
|
|
|
|
use meta_feeder_sdk::hash::compute_card_cid;
|
|
use meta_feeder_sdk::serve::{configure_plugins, router};
|
|
use meta_feeder_sdk::{GatewayQuery, QueryRequest, QueryResponse};
|
|
use usenet_feeder::usenet::UsenetPlugin;
|
|
use wiremock::matchers::{method, path, query_param};
|
|
use wiremock::{Mock, MockServer, ResponseTemplate};
|
|
|
|
const KEY: &str = "metamesh-key";
|
|
|
|
fn item(guid: &str, title: &str, cat: u32) -> String {
|
|
format!(
|
|
r#"<item><title>{title}</title><guid isPermaLink="true">http://localhost/details/{guid}</guid>
|
|
<newznab:attr name="category" value="{cat}"/><newznab:attr name="size" value="443121252"/></item>"#
|
|
)
|
|
}
|
|
|
|
fn rss(items: &[String]) -> String {
|
|
format!(
|
|
r#"<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:newznab="http://www.newznab.com/DTD/2010/feeds/attributes/"><channel>{}</channel></rss>"#,
|
|
items.join("")
|
|
)
|
|
}
|
|
|
|
const NZB: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
|
|
<nzb xmlns="http://www.newzbin.com/DTD/2003/nzb">
|
|
<file poster="[email protected]" date="1" subject="TT [1/1] "a.mkv" yEnc">
|
|
<groups><group>alt.binaries.teevee</group></groups>
|
|
<segments><segment bytes="100" number="1">[email protected]</segment></segments>
|
|
</file>
|
|
</nzb>"#;
|
|
|
|
fn anchored(filters: &[(&str, &[&str])]) -> GatewayQuery {
|
|
let mut q = GatewayQuery::from_free_text("");
|
|
q.filters = filters
|
|
.iter()
|
|
.map(|(k, vs)| (k.to_string(), vs.iter().map(|s| s.to_string()).collect()))
|
|
.collect::<BTreeMap<_, _>>();
|
|
q
|
|
}
|
|
|
|
async fn records(base: &str, q: GatewayQuery) -> Vec<meta_feeder_sdk::DiscoveryRecord> {
|
|
let r = reqwest::Client::new()
|
|
.post(format!("{base}/query"))
|
|
.json(&QueryRequest { upstream_id: "usenet".into(), query: q, max_results: 50 })
|
|
.send()
|
|
.await
|
|
.expect("POST /query");
|
|
assert!(r.status().is_success(), "query status {}", r.status());
|
|
r.json::<QueryResponse>().await.expect("query json").records
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn a_show_page_query_matches_releases_by_name_against_the_card() {
|
|
let nntmux = MockServer::start().await;
|
|
let core = MockServer::start().await;
|
|
std::env::set_var("META_CORE_URL", core.uri());
|
|
|
|
// The card the card feeder stored for tv 112442.
|
|
let locator = compute_card_cid("tmdb", "tv:112442").unwrap();
|
|
Mock::given(method("GET"))
|
|
.and(path(format!("/meta/{locator}")))
|
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
|
"hashId": locator,
|
|
"metadata": {
|
|
"title": "Trash Truck",
|
|
"fileType": "card",
|
|
"poster": "bafkreiposter",
|
|
"description/eng": "Hank and his best pal, a giant trash truck.",
|
|
"imdbid": "tt9288860",
|
|
"genres/Kids": "true"
|
|
}
|
|
})))
|
|
.mount(&core)
|
|
.await;
|
|
// No card for any other id.
|
|
Mock::given(method("GET"))
|
|
.respond_with(ResponseTemplate::new(404))
|
|
.with_priority(10)
|
|
.mount(&core)
|
|
.await;
|
|
|
|
Mock::given(method("GET"))
|
|
.and(path("/api/v1/api"))
|
|
.and(query_param("t", "caps"))
|
|
.respond_with(ResponseTemplate::new(200).set_body_string("<caps/>"))
|
|
.mount(&nntmux)
|
|
.await;
|
|
Mock::given(method("GET"))
|
|
.and(path("/api/v1/api"))
|
|
.and(query_param("t", "search"))
|
|
.and(query_param("q", "Trash Truck"))
|
|
.respond_with(ResponseTemplate::new(200).set_body_string(rss(&[
|
|
item("aaa111", "Trash.Truck.S02E16.Mint.Choco.Boom.1080p.NF.WEB-DL.DDP5.1.H.264-NTb", 5040),
|
|
item("bbb222", "Trash.Truck.S01E02.Slumber.Party.1080p.NF.WEB-DL.DDP5.1.x264-LAZY", 5040),
|
|
item("ccc333", "Trash.Truck.Fan.Made.Toy.Unboxing.Compilation.Vol.3.1080p", 5040),
|
|
])))
|
|
.mount(&nntmux)
|
|
.await;
|
|
// Only the release that matches name AND season is ever downloaded.
|
|
Mock::given(method("GET"))
|
|
.and(path("/getnzb"))
|
|
.and(query_param("id", "aaa111.nzb"))
|
|
.respond_with(ResponseTemplate::new(200).set_body_string(NZB))
|
|
.expect(1)
|
|
.mount(&nntmux)
|
|
.await;
|
|
Mock::given(method("GET"))
|
|
.and(path("/getnzb"))
|
|
.respond_with(ResponseTemplate::new(200).set_body_string(NZB))
|
|
.with_priority(10)
|
|
.expect(0)
|
|
.mount(&nntmux)
|
|
.await;
|
|
|
|
let dir = tempfile::tempdir().expect("tempdir");
|
|
let cache = dir.path().join("gateway").join("usenet");
|
|
std::fs::create_dir_all(&cache).expect("mkdir");
|
|
std::fs::write(
|
|
cache.join("config.json"),
|
|
serde_json::json!({ "nntmux_url": nntmux.uri(), "nntmux_api_key": KEY }).to_string(),
|
|
)
|
|
.expect("write config");
|
|
let configured = configure_plugins(vec![Box::new(UsenetPlugin::new())], dir.path()).expect("configure");
|
|
let app = router(configured, "test".to_string(), dir.path());
|
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.expect("bind");
|
|
let base = format!("http://{}", listener.local_addr().expect("addr"));
|
|
tokio::spawn(async move { axum::serve(listener, app).await.expect("serve") });
|
|
|
|
// The season walk's shape: id + season + the TV kinds, no free text.
|
|
let recs = records(
|
|
&base,
|
|
anchored(&[
|
|
("tmdbid", &["112442"]),
|
|
("season", &["2"]),
|
|
("contentKind", &["series", "episode", "pack"]),
|
|
]),
|
|
)
|
|
.await;
|
|
assert_eq!(recs.len(), 1, "only the S02 release whose name is the show, got {recs:?}");
|
|
let f = &recs[0].fields;
|
|
assert_eq!(recs[0].record_id, "aaa111");
|
|
assert_eq!(f["tmdbid"], "112442");
|
|
assert_eq!(f["anchored"], "true");
|
|
assert_eq!(f["anchorMethod"], "title", "a name match, never an id-confirmed anchor");
|
|
assert_eq!(f["imdbid"], "tt9288860");
|
|
assert_eq!(f["poster"], "bafkreiposter");
|
|
assert_eq!(f["description/eng"], "Hank and his best pal, a giant trash truck.");
|
|
assert!(!f.contains_key("genres/Kids"));
|
|
assert_eq!(f["season"], "2");
|
|
assert_eq!(f["episode"], "16");
|
|
assert_eq!(f["indexer"], "nntmux");
|
|
assert_eq!(f["source/nntmux"], "true");
|
|
assert_eq!(f["fileName"], "Trash.Truck.S02E16.Mint.Choco.Boom.1080p.NF.WEB-DL.DDP5.1.H.264-NTb");
|
|
|
|
// No card on the mesh for this id → nothing to match by name.
|
|
assert!(records(&base, anchored(&[("tmdbid", &["999"])])).await.is_empty());
|
|
}
|