diff --git a/.cargo/config.toml b/.cargo/config.toml index bbfc28a..9d76a7f 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,3 +1,5 @@ [build] rustflags = ["--cfg", "tokio_unstable"] +[target.wasm32-unknown-unknown] +runner = "wasm-bindgen-test-runner" diff --git a/Cargo.lock b/Cargo.lock index 2df58ab..6cfaa15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -126,9 +126,9 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.79", + "quote 1.0.35", + "syn 2.0.53", ] [[package]] @@ -137,9 +137,9 @@ version = "0.1.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "461abc97219de0eaaf81fe3ef974a540158f3d079c2ab200f891f1a2ef201e85" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.79", + "quote 1.0.35", + "syn 2.0.53", ] [[package]] @@ -206,9 +206,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -281,9 +281,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" @@ -327,9 +327,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90239a040c80f5e14809ca132ddc4176ab33d5e17e49691793296e3fcb34d72f" dependencies = [ "heck", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.79", + "quote 1.0.35", + "syn 2.0.53", ] [[package]] @@ -487,6 +487,17 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "default-env" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f753eb82d29277e79efc625e84aecacfd4851ee50e05a8573a4740239a77bfd3" +dependencies = [ + "proc-macro2 0.4.30", + "quote 0.6.13", + "syn 0.15.44", +] + [[package]] name = "digest" version = "0.10.7" @@ -512,6 +523,7 @@ dependencies = [ "base64", "bytes", "console_error_panic_hook", + "default-env", "event-listener", "fastwebsockets 0.6.0", "futures-util", @@ -530,6 +542,7 @@ dependencies = [ "tower-service", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-bindgen-test", "wasm-streams", "wasmtimer", "web-sys", @@ -707,9 +720,9 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.79", + "quote 1.0.35", + "syn 2.0.53", ] [[package]] @@ -783,7 +796,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.2.5", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -802,7 +815,7 @@ dependencies = [ "futures-sink", "futures-util", "http 1.1.0", - "indexmap 2.2.5", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -1025,9 +1038,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.5" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -1238,9 +1251,9 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.79", + "quote 1.0.35", + "syn 2.0.53", ] [[package]] @@ -1311,9 +1324,9 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.79", + "quote 1.0.35", + "syn 2.0.53", ] [[package]] @@ -1340,6 +1353,15 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +dependencies = [ + "unicode-xid", +] + [[package]] name = "proc-macro2" version = "1.0.79" @@ -1367,9 +1389,9 @@ checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" dependencies = [ "anyhow", "itertools", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.79", + "quote 1.0.35", + "syn 2.0.53", ] [[package]] @@ -1381,13 +1403,22 @@ dependencies = [ "prost", ] +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +dependencies = [ + "proc-macro2 0.4.30", +] + [[package]] name = "quote" version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ - "proc-macro2", + "proc-macro2 1.0.79", ] [[package]] @@ -1431,9 +1462,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", @@ -1579,6 +1610,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -1635,9 +1672,9 @@ version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.79", + "quote 1.0.35", + "syn 2.0.53", ] [[package]] @@ -1745,14 +1782,25 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +[[package]] +name = "syn" +version = "0.15.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" +dependencies = [ + "proc-macro2 0.4.30", + "quote 0.6.13", + "unicode-xid", +] + [[package]] name = "syn" version = "2.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.79", + "quote 1.0.35", "unicode-ident", ] @@ -1799,9 +1847,9 @@ version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.79", + "quote 1.0.35", + "syn 2.0.53", ] [[package]] @@ -1850,9 +1898,9 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.79", + "quote 1.0.35", + "syn 2.0.53", ] [[package]] @@ -1978,9 +2026,9 @@ version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.79", + "quote 1.0.35", + "syn 2.0.53", ] [[package]] @@ -2026,6 +2074,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" + [[package]] name = "untrusted" version = "0.9.0" @@ -2106,9 +2160,9 @@ dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.79", + "quote 1.0.35", + "syn 2.0.53", "wasm-bindgen-shared", ] @@ -2130,7 +2184,7 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ - "quote", + "quote 1.0.35", "wasm-bindgen-macro-support", ] @@ -2140,9 +2194,9 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.79", + "quote 1.0.35", + "syn 2.0.53", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2153,6 +2207,31 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +[[package]] +name = "wasm-bindgen-test" +version = "0.3.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9bf62a58e0780af3e852044583deee40983e5886da43a271dd772379987667b" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0" +dependencies = [ + "proc-macro2 1.0.79", + "quote 1.0.35", + "syn 2.0.53", +] + [[package]] name = "wasm-streams" version = "0.4.0" diff --git a/client/Cargo.toml b/client/Cargo.toml index 895d526..4f0c185 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "LGPL-3.0-only" [lib] -crate-type = ["cdylib"] +crate-type = ["cdylib", "rlib"] [dependencies] bytes = "1.5.0" @@ -38,3 +38,8 @@ wasmtimer = "0.2.0" [dependencies.ring] features = ["wasm32_unknown_unknown_js"] + +[dev-dependencies] +default-env = "0.1.1" +wasm-bindgen-test = "0.3.42" +web-sys = { version = "0.3.69", features = ["FormData", "UrlSearchParams"] } diff --git a/client/src/lib.rs b/client/src/lib.rs index 773578d..4245876 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -10,7 +10,7 @@ mod wrappers; use tls_stream::EpxTlsStream; use tokioio::TokioIo; use udp_stream::EpxUdpStream; -use utils::{Boolinator, ReplaceErr, UriExt}; +pub use utils::{Boolinator, ReplaceErr, UriExt}; use websocket::EpxWebSocket; use wrappers::{IncomingBody, ServiceWrapper, TlsWispService, WebSocketWrapper}; @@ -231,7 +231,7 @@ impl EpoxyClient { let mut redirected = false; let mut current_url = req.uri().clone(); let mut current_resp: EpxResponse = self.send_req_inner(req, should_redirect).await?; - for _ in 0..self.redirect_limit - 1 { + for _ in 0..self.redirect_limit { match current_resp { EpxResponse::Success(_) => break, EpxResponse::Redirect((_, req)) => { diff --git a/client/src/wrappers.rs b/client/src/wrappers.rs index 24e4188..3c8caf8 100644 --- a/client/src/wrappers.rs +++ b/client/src/wrappers.rs @@ -173,6 +173,7 @@ pub struct WebSocketWrapper { inner: SendWrapper, open_event: Arc, error_event: Arc, + close_event: Arc, closed: Arc, // used to retain the closures @@ -282,6 +283,7 @@ impl WebSocketWrapper { inner: SendWrapper::new(ws), open_event, error_event, + close_event: close_event.clone(), closed: closed.clone(), onopen: SendWrapper::new(onopen), onclose: SendWrapper::new(onclose), @@ -334,5 +336,8 @@ impl Drop for WebSocketWrapper { self.inner.set_onclose(None); self.inner.set_onerror(None); self.inner.set_onmessage(None); + self.closed.store(true, Ordering::Release); + self.close_event.notify(usize::MAX); + let _ = self.inner.close(); } } diff --git a/client/test.sh b/client/test.sh new file mode 100644 index 0000000..7dee978 --- /dev/null +++ b/client/test.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +(cd ..; cargo b --bin epoxy-server) +../target/debug/epoxy-server & +server_pid=$! +sleep 1 +echo "server_pid: $server_pid" + +cargo test --target wasm32-unknown-unknown + +kill $server_pid diff --git a/client/tests/fetch.rs b/client/tests/fetch.rs new file mode 100644 index 0000000..02eabb2 --- /dev/null +++ b/client/tests/fetch.rs @@ -0,0 +1,270 @@ +use default_env::default_env; +use epoxy_client::EpoxyClient; +use js_sys::{JsString, Object, Reflect, Uint8Array, JSON}; +use tokio::sync::OnceCell; +use wasm_bindgen::JsValue; +use wasm_bindgen_futures::JsFuture; +use wasm_bindgen_test::*; +use web_sys::{FormData, Headers, Response, UrlSearchParams}; + +wasm_bindgen_test_configure!(run_in_dedicated_worker); + +static USER_AGENT: &str = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"; +static EPOXY_CLIENT: OnceCell = OnceCell::const_new(); + +async fn get_client_w_ua(useragent: &str, redirect_limit: usize) -> EpoxyClient { + EpoxyClient::new( + "ws://localhost:4000".into(), + useragent.into(), + redirect_limit, + ) + .await + .ok() + .expect("Failed to create client") +} + +macro_rules! fetch { + ($url:expr, $opts:expr) => { + EPOXY_CLIENT + .get_or_init(|| get_client_w_ua(USER_AGENT, 10)) + .await + .fetch($url, $opts) + .await + .ok() + .expect("Failed to fetch") + }; +} +macro_rules! httpbin { + ($url:literal) => { + concat!(default_env!("HTTPBIN_URL", "https://httpbin.org/"), $url) + }; +} + +async fn get_body_json(resp: &Response) -> JsValue { + JsFuture::from(resp.json().unwrap()).await.unwrap() +} +async fn get_body_text(resp: &Response) -> JsValue { + JsFuture::from(resp.text().unwrap()).await.unwrap() +} + +fn get_header(body: &JsValue, header: &str) -> Result { + Reflect::get(body, &JsValue::from("headers")) + .and_then(|x| Reflect::get(&x, &JsValue::from(header))) +} +fn get_resp_body(body: &JsValue) -> Result { + Reflect::get(body, &JsValue::from("data")) +} +fn get_resp_form(body: &JsValue) -> Result { + Reflect::get(body, &JsValue::from("form")) +} + +fn check_resp(resp: &Response, url: &str, status: u16, status_text: &str) { + assert_eq!(resp.url(), url); + assert_eq!(resp.status(), status); + assert_eq!(resp.status_text(), status_text); +} + +#[wasm_bindgen_test] +async fn get() { + let url = httpbin!("get"); + let resp = fetch!(url.into(), Object::new()); + + check_resp(&resp, url, 200, "OK"); + + let body: Object = get_body_json(&resp).await.into(); + assert_eq!( + get_header(&body, "User-Agent"), + Ok(JsValue::from(USER_AGENT)) + ); +} + +#[wasm_bindgen_test] +async fn gzip() { + let url = httpbin!("gzip"); + let resp = fetch!(url.into(), Object::new()); + + check_resp(&resp, url, 200, "OK"); + + let body: Object = get_body_json(&resp).await.into(); + assert_eq!( + get_header(&body, "Accept-Encoding"), + Ok(JsValue::from("gzip, br")) + ); +} + +#[wasm_bindgen_test] +async fn brotli() { + let url = httpbin!("brotli"); + let resp = fetch!(url.into(), Object::new()); + + check_resp(&resp, url, 200, "OK"); + + let body: Object = get_body_json(&resp).await.into(); + assert_eq!( + get_header(&body, "Accept-Encoding"), + Ok(JsValue::from("gzip, br")) + ); +} + +#[wasm_bindgen_test] +async fn redirect() { + let url = httpbin!("redirect/2"); + let resp = fetch!(url.into(), Object::new()); + + check_resp(&resp, httpbin!("get"), 200, "OK"); + + get_body_json(&resp).await; +} + +#[wasm_bindgen_test] +async fn redirect_limit() { + // new client created due to redirect limit difference + let client = get_client_w_ua(USER_AGENT, 2).await; + let url = httpbin!("redirect/3"); + let resp = client + .fetch(url.into(), Object::new()) + .await + .ok() + .expect("Failed to fetch"); + + check_resp(&resp, httpbin!("relative-redirect/1"), 302, "Found"); + + assert_eq!(get_body_text(&resp).await, JsValue::from("")); +} + +#[wasm_bindgen_test] +async fn redirect_manual() { + let url = httpbin!("redirect/2"); + + let obj = Object::new(); + Reflect::set(&obj, &JsValue::from("redirect"), &JsValue::from("manual")).unwrap(); + + let resp = fetch!(url.into(), obj); + + check_resp(&resp, url, 302, "Found"); + + get_body_text(&resp).await; +} + +#[wasm_bindgen_test] +async fn post_string() { + let url = httpbin!("post"); + let obj = Object::new(); + Reflect::set(&obj, &JsValue::from("method"), &JsValue::from("POST")).unwrap(); + Reflect::set(&obj, &JsValue::from("body"), &JsValue::from("epoxy body")).unwrap(); + let resp = fetch!(url.into(), obj); + + check_resp(&resp, url, 200, "OK"); + + let body: Object = get_body_json(&resp).await.into(); + assert_eq!(get_resp_body(&body), Ok(JsValue::from("epoxy body"))); +} + +#[wasm_bindgen_test] +async fn post_arraybuffer() { + let url = httpbin!("post"); + + let obj = Object::new(); + Reflect::set(&obj, &JsValue::from("method"), &JsValue::from("POST")).unwrap(); + let req_body = b"epoxy body"; + let u8array = Uint8Array::new_with_length(req_body.len().try_into().unwrap()); + u8array.copy_from(req_body); + Reflect::set(&obj, &JsValue::from("body"), &u8array).unwrap(); + + let resp = fetch!(url.into(), obj); + + check_resp(&resp, url, 200, "OK"); + + let body: Object = get_body_json(&resp).await.into(); + assert_eq!(get_resp_body(&body), Ok(JsValue::from("epoxy body"))); +} + +#[wasm_bindgen_test] +async fn post_formdata() { + let url = httpbin!("post"); + + let obj = Object::new(); + Reflect::set(&obj, &JsValue::from("method"), &JsValue::from("POST")).unwrap(); + let req_body = FormData::new().unwrap(); + req_body.set_with_str("a", "b").unwrap(); + Reflect::set(&obj, &JsValue::from("body"), &req_body).unwrap(); + + let resp = fetch!(url.into(), obj); + + check_resp(&resp, url, 200, "OK"); + + let body: Object = get_body_json(&resp).await.into(); + assert_eq!( + get_resp_form(&body).and_then(|x| JSON::stringify(&x)), + Ok(JsString::from(r#"{"a":"b"}"#)) + ); + assert!(JsString::from(get_header(&body, "Content-Type").unwrap()) + .includes("multipart/form-data", 0)); +} + +#[wasm_bindgen_test] +async fn post_urlsearchparams() { + let url = httpbin!("post"); + + let obj = Object::new(); + Reflect::set(&obj, &JsValue::from("method"), &JsValue::from("POST")).unwrap(); + let req_body = UrlSearchParams::new_with_str("a=b").unwrap(); + Reflect::set(&obj, &JsValue::from("body"), &req_body).unwrap(); + + let resp = fetch!(url.into(), obj); + + check_resp(&resp, url, 200, "OK"); + + let body: Object = get_body_json(&resp).await.into(); + assert_eq!( + get_resp_form(&body).and_then(|x| JSON::stringify(&x)), + Ok(JsString::from(r#"{"a":"b"}"#)) + ); + assert!(JsString::from(get_header(&body, "Content-Type").unwrap()) + .includes("application/x-www-form-urlencoded", 0)); +} + +#[wasm_bindgen_test] +async fn headers_obj() { + let url = httpbin!("get"); + + let obj = Object::new(); + let headers = Object::new(); + Reflect::set( + &headers, + &JsValue::from("x-header-one"), + &JsValue::from("value"), + ) + .unwrap(); + Reflect::set(&obj, &JsValue::from("headers"), &headers).unwrap(); + + let resp = fetch!(url.into(), obj); + + check_resp(&resp, url, 200, "OK"); + + let body: Object = get_body_json(&resp).await.into(); + assert_eq!( + get_header(&body, "X-Header-One"), + Ok(JsValue::from("value")) + ); +} + +#[wasm_bindgen_test] +async fn headers_headers() { + let url = httpbin!("get"); + + let obj = Object::new(); + let headers = Headers::new().unwrap(); + headers.set("x-header-one", "value").unwrap(); + Reflect::set(&obj, &JsValue::from("headers"), &headers).unwrap(); + + let resp = fetch!(url.into(), obj); + + check_resp(&resp, url, 200, "OK"); + + let body: Object = get_body_json(&resp).await.into(); + assert_eq!( + get_header(&body, "X-Header-One"), + Ok(JsValue::from("value")) + ); +}