Phase 1 — Foundation
Mirror NPE's dependency-free spine in Rust:
* exceptions — NetworkError + ParsingError + ContentUnavailable
+ ExtractionError tree, with reqwest/serde_json conversions
* localization — Localization + ContentCountry, default (en, GB)
* downloader/ — Downloader trait, Request builder, Response,
reqwest blocking default impl
* page — continuation-token carrier
* image — Image + ImageSet + ResolutionLevel
(HEIGHT_UNKNOWN/WIDTH_UNKNOWN = -1)
* metainfo — title/content/url/url_text grab-bag
* service — StreamingService trait + LinkType + ServiceInfo
* newpipe — process-global Downloader / Localization /
ContentCountry singleton
Foundational invariants nailed down (per SPEC §3):
* HTTP non-2xx returns Ok(Response); only 429 throws NetworkError::Recaptcha
* Response header keys lowercase-normalized
* Request.add_header PARITY with NPE bug (silent overwrite);
append_header is our clean addition
* default Localization is en-GB
* No cookie jar in the default downloader
Tests: 7 unit + 7 live smoke against httpbin.org (gated on
'online-tests' feature). All green.
This commit is contained in:
parent
f44b46fab5
commit
46201c731f
16 changed files with 2689 additions and 1 deletions
95
tests/foundation_smoke.rs
Normal file
95
tests/foundation_smoke.rs
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
// Phase 1 smoke — exercises the foundation against live httpbin.org.
|
||||
//
|
||||
// Per SPEC §4 Phase 1 "Done when": build a Request, send through default
|
||||
// Downloader, parse Response, confirm latest_url follows redirects.
|
||||
//
|
||||
// These tests hit the network — gated on the `online` cfg so CI offline
|
||||
// runs aren't broken.
|
||||
|
||||
#![cfg(feature = "online-tests")]
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use strawcore::downloader::request::Request;
|
||||
use strawcore::downloader::ReqwestDownloader;
|
||||
use strawcore::exceptions::NetworkError;
|
||||
use strawcore::localization::{ContentCountry, Localization};
|
||||
use strawcore::{Downloader, NewPipe};
|
||||
|
||||
#[test]
|
||||
fn get_through_default_downloader() {
|
||||
let dl = ReqwestDownloader::new().expect("build downloader");
|
||||
let resp = dl.get("https://httpbin.org/get").expect("transport");
|
||||
assert_eq!(resp.response_code(), 200);
|
||||
assert!(resp.response_body().contains("\"url\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn latest_url_follows_redirects() {
|
||||
let dl = ReqwestDownloader::new().expect("build downloader");
|
||||
let resp = dl
|
||||
.get("https://httpbin.org/redirect/3")
|
||||
.expect("transport");
|
||||
assert_eq!(resp.response_code(), 200);
|
||||
assert!(
|
||||
resp.latest_url().ends_with("/get"),
|
||||
"latest_url should land at /get after 3 redirects, got {}",
|
||||
resp.latest_url()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_2xx_returns_ok_not_err() {
|
||||
let dl = ReqwestDownloader::new().expect("build downloader");
|
||||
let resp = dl.get("https://httpbin.org/status/404").expect("transport");
|
||||
assert_eq!(resp.response_code(), 404);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_429_surfaces_as_recaptcha_err() {
|
||||
let dl = ReqwestDownloader::new().expect("build downloader");
|
||||
let err = dl.get("https://httpbin.org/status/429").expect_err("429 must be NetworkError");
|
||||
match err {
|
||||
NetworkError::Recaptcha { url } => assert!(url.contains("/status/429")),
|
||||
other => panic!("expected Recaptcha, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn localization_header_attached_when_enabled() {
|
||||
let dl = ReqwestDownloader::new().expect("build downloader");
|
||||
let req = Request::get("https://httpbin.org/headers")
|
||||
.localization(Some(Localization::new("en", Some("GB".into()))))
|
||||
.build();
|
||||
let resp = dl.execute(req).expect("transport");
|
||||
assert_eq!(resp.response_code(), 200);
|
||||
assert!(
|
||||
resp.response_body().to_ascii_lowercase().contains("accept-language"),
|
||||
"Accept-Language should be echoed by httpbin"
|
||||
);
|
||||
assert!(resp.response_body().contains("en-GB"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn header_keys_lowercased_in_response() {
|
||||
let dl = ReqwestDownloader::new().expect("build downloader");
|
||||
let resp = dl.get("https://httpbin.org/get").expect("transport");
|
||||
for (k, _) in resp.response_headers() {
|
||||
assert_eq!(k, &k.to_ascii_lowercase(), "header key {k} not lowercased");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn newpipe_singleton_wires_downloader() {
|
||||
let dl = Arc::new(ReqwestDownloader::new().expect("build downloader"));
|
||||
NewPipe::init_full(
|
||||
dl.clone(),
|
||||
Localization::default(),
|
||||
ContentCountry::default(),
|
||||
);
|
||||
|
||||
let from_global = NewPipe::downloader().expect("downloader registered");
|
||||
let resp = from_global.get("https://httpbin.org/get").expect("transport");
|
||||
assert_eq!(resp.response_code(), 200);
|
||||
assert_eq!(NewPipe::preferred_localization().localization_code(), "en-GB");
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue