diff --git a/Cargo.toml b/Cargo.toml index b68de22c..578df91f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ license = "MIT" rust-version = "1.63" # follows `tokio` and `hyper` [workspace.dependencies] -viz = { version = "0.5.0", path = "viz" } +viz = { version = "0.5.0-rc.2", path = "viz" } viz-core = { version = "0.5.0", path = "viz-core" } viz-router = { version = "0.5.0", path = "viz-router" } viz-handlers = { version = "0.5.0", path = "viz-handlers", default-features = false } diff --git a/viz-core/Cargo.toml b/viz-core/Cargo.toml index b6d8230c..4964d33b 100644 --- a/viz-core/Cargo.toml +++ b/viz-core/Cargo.toml @@ -99,8 +99,6 @@ tokio-stream = { workspace = true, optional = true } tokio-util = { workspace = true, optional = true } [dev-dependencies] -viz = { workspace = true, features = ["session"] } -viz-test.workspace = true tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } [package.metadata.docs.rs] diff --git a/viz-core/tests/request.rs b/viz-core/tests/request.rs index afaca620..e1f74c65 100644 --- a/viz-core/tests/request.rs +++ b/viz-core/tests/request.rs @@ -1,6 +1,4 @@ -use std::collections::{BTreeMap, HashMap}; - -use headers::{authorization::Bearer, Authorization, ContentType, HeaderValue}; +use headers::{ContentType, HeaderValue}; use http::uri::Scheme; use serde::{Deserialize, Serialize}; use viz_core::{ @@ -8,14 +6,10 @@ use viz_core::{ // header::{AUTHORIZATION, CONTENT_TYPE, COOKIE, SET_COOKIE}, // StatusCode, header::CONTENT_TYPE, - types::{self, PayloadError}, - Error, + types::PayloadError, IncomingBody, - IntoResponse, Request, RequestExt, - Response, - ResponseExt, Result, }; @@ -70,284 +64,3 @@ fn request_ext() -> Result<()> { Ok(()) } - -#[tokio::test] -async fn request_body() -> Result<()> { - use futures_util::stream::TryStreamExt; - use viz::{ - middleware::{cookie, limits}, - Router, - }; - use viz_test::http::{ - header::{AUTHORIZATION, COOKIE}, - StatusCode, - }; - use viz_test::TestServer; - - let router = Router::new() - .get("/:id", |req: Request| async move { - let id = req.param::("id")?; - Ok(id) - }) - .get("/:username/:repo", |req: Request| async move { - let (username, repo): (String, String) = req.params()?; - Ok(format!("{username}/{repo}")) - }) - .get("/extract-token", |mut req: Request| async move { - let header: types::Header> = req.extract().await?; - Ok(header.into_inner().token().to_string()) - }) - .post("/extract-body", |mut req: Request| async move { - let form: types::Form> = req.extract().await?; - Ok(Response::json(form.into_inner())) - }) - .get("/cookies", |req: Request| async move { - let cookies = req.cookies()?; - let jar = cookies - .jar() - .lock() - .map_err(|e| Error::Responder(e.to_string().into_response()))?; - Ok(jar.iter().count().to_string()) - }) - .get("/cookie", |req: Request| async move { - Ok(req.cookie("viz").unwrap().value().to_string()) - }) - .with(cookie::Config::default()) - .post("/bytes", |mut req: Request| async move { - let data = req.bytes().await?; - Ok(data) - }) - .post("/bytes-with-limit", |mut req: Request| async move { - let data = req.bytes_with(None, 4).await?; - Ok(data) - }) - .post("/bytes-used", |mut req: Request| async move { - req.bytes().await?; - let data = req.bytes().await?; - Ok(data) - }) - .post("/text", |mut req: Request| async move { - let data = req.text().await?; - Ok(Response::text(data)) - }) - .post("/json", |mut req: Request| async move { - let data = req.json::().await?; - Ok(Response::json(data)) - }) - .post("/form", |mut req: Request| async move { - let data = req.form::>().await?; - Ok(Response::json(data)) - }) - .post("/multipart", |mut req: Request| async move { - let mut multipart = req.multipart().await?; - let mut data = HashMap::new(); - - while let Some(mut field) = multipart.try_next().await? { - let buf = field.bytes().await?.to_vec(); - data.insert(field.name, String::from_utf8(buf).map_err(Error::normal)?); - } - - Ok(Response::json(data)) - }) - .with(limits::Config::new().limits(types::Limits::new())); - - let client = TestServer::new(router).await?; - - let resp = client.get("/7").send().await.map_err(Error::normal)?; - assert_eq!(resp.text().await.map_err(Error::normal)?, "7"); - - let resp = client - .get("/viz-rs/viz") - .send() - .await - .map_err(Error::normal)?; - assert_eq!(resp.text().await.map_err(Error::normal)?, "viz-rs/viz"); - - let resp = client - .get("/extract-token") - .header(AUTHORIZATION, "Bearer viz.rs") - .send() - .await - .map_err(Error::normal)?; - assert_eq!(resp.text().await.map_err(Error::normal)?, "viz.rs"); - - let mut form = BTreeMap::new(); - form.insert("password", "rs"); - form.insert("username", "viz"); - let resp = client - .post("/extract-body") - .form(&form) - .send() - .await - .map_err(Error::normal)?; - assert_eq!( - resp.text().await.map_err(Error::normal)?, - r#"{"password":"rs","username":"viz"}"# - ); - - let resp = client - .get("/cookie") - .header(COOKIE, "viz=crate") - .send() - .await - .map_err(Error::normal)?; - assert_eq!(resp.text().await.map_err(Error::normal)?, "crate"); - - let resp = client - .get("/cookies") - .header(COOKIE, "auth=true;dark=false") - .send() - .await - .map_err(Error::normal)?; - assert_eq!(resp.text().await.map_err(Error::normal)?, "2"); - - let resp = client - .post("/bytes") - .body("bytes") - .send() - .await - .map_err(Error::normal)?; - assert_eq!(resp.text().await.map_err(Error::normal)?, "bytes"); - - let resp = client - .post("/bytes-with-limit") - .body("rust") - .send() - .await - .map_err(Error::normal)?; - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.text().await.map_err(Error::normal)?, "rust"); - - let resp = client - .post("/bytes-with-limit") - .body("crate") - .send() - .await - .map_err(Error::normal)?; - assert_eq!(resp.status(), StatusCode::PAYLOAD_TOO_LARGE); - assert_eq!( - resp.text().await.map_err(Error::normal)?, - "payload is too large" - ); - - let resp = client - .post("/bytes-used") - .body("used") - .send() - .await - .map_err(Error::normal)?; - assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); - assert_eq!( - resp.text().await.map_err(Error::normal)?, - "payload has been used" - ); - - let resp = client - .post("/text") - .body("text") - .send() - .await - .map_err(Error::normal)?; - assert_eq!(resp.text().await.map_err(Error::normal)?, "text"); - - let resp = client - .post("/json") - .json(&Page { p: 1 }) - .send() - .await - .map_err(Error::normal)?; - assert_eq!( - resp.json::().await.map_err(Error::normal)?, - Page { p: 1 } - ); - - let mut form = HashMap::new(); - form.insert("username", "viz"); - form.insert("password", "rs"); - let resp = client - .post("/form") - .form(&form) - .send() - .await - .map_err(Error::normal)?; - assert_eq!( - resp.json::>() - .await - .map_err(Error::normal)?, - { - let mut form = HashMap::new(); - form.insert("username".to_string(), "viz".to_string()); - form.insert("password".to_string(), "rs".to_string()); - form - } - ); - - let form = viz_test::multipart::Form::new() - .text("key3", "3") - .text("key4", "4"); - let resp = client - .post("/multipart") - .multipart(form) - .send() - .await - .map_err(Error::normal)?; - assert_eq!( - resp.json::>() - .await - .map_err(Error::normal)?, - { - let mut form = HashMap::new(); - form.insert("key3".to_string(), "3".to_string()); - form.insert("key4".to_string(), "4".to_string()); - form - } - ); - - Ok(()) -} - -#[tokio::test] -async fn request_session() -> Result<()> { - use viz::{ - middleware::{cookie, helper::CookieOptions, session}, - Router, - }; - use viz_test::http::header::{COOKIE, SET_COOKIE}; - use viz_test::{nano_id, sessions, TestServer}; - - let router = Router::new() - .post("/session/set", |req: Request| async move { - let counter = req.session().get::("counter")?.unwrap_or_default() + 1; - req.session().set("counter", counter)?; - Ok(counter.to_string()) - }) - .with(session::Config::new( - session::Store::new( - sessions::MemoryStorage::new(), - nano_id::base64::<32>, - |sid: &str| sid.len() == 32, - ), - CookieOptions::default(), - )) - .with(cookie::Config::default()); - - let client = TestServer::new(router).await?; - - let resp = client - .post("/session/set") - .send() - .await - .map_err(Error::normal)?; - let cookie = resp.headers().get(SET_COOKIE).cloned().unwrap(); - assert_eq!(resp.text().await.map_err(Error::normal)?, "1"); - - let resp = client - .post("/session/set") - .header(COOKIE, cookie) - .send() - .await - .map_err(Error::normal)?; - assert_eq!(resp.text().await.map_err(Error::normal)?, "2"); - - Ok(()) -} diff --git a/viz-core/tests/response.rs b/viz-core/tests/response.rs index 763b8c25..b3bbcb05 100644 --- a/viz-core/tests/response.rs +++ b/viz-core/tests/response.rs @@ -123,30 +123,3 @@ async fn response_ext() -> Result<()> { fn response_ext_panic() { Response::redirect_with_status("/oauth", StatusCode::OK); } - -#[tokio::test] -async fn response_ext_with_server() -> Result<()> { - use viz::{Request, Router}; - use viz_test::TestServer; - - let router = Router::new() - .get("/", |_: Request| async move { Ok("") }) - .post("/", |_: Request| async move { - Ok(Response::with( - Full::new("".into()), - mime::TEXT_XML.as_ref(), - )) - }); - - let client = TestServer::new(router).await?; - - let resp = client.get("/").send().await.map_err(Error::normal)?; - assert_eq!(resp.content_length(), Some(0)); - assert_eq!(resp.text().await.map_err(Error::normal)?, ""); - - let resp = client.post("/").send().await.map_err(Error::normal)?; - assert_eq!(resp.content_length(), Some(6)); - assert_eq!(resp.text().await.map_err(Error::normal)?, ""); - - Ok(()) -} diff --git a/viz/Cargo.toml b/viz/Cargo.toml index e1aaf52b..047706a8 100644 --- a/viz/Cargo.toml +++ b/viz/Cargo.toml @@ -81,6 +81,13 @@ tokio-rustls = { workspace = true, optional = true } tokio-native-tls = { workspace = true, optional = true } [dev-dependencies] +viz-test.workspace = true +bytes.workspace = true +headers.workspace = true +http.workspace = true +http-body-util.workspace = true +mime.workspace = true +serde.workspace = true tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread"] } [package.metadata.docs.rs] diff --git a/viz-core/tests/body.rs b/viz/tests/body.rs similarity index 100% rename from viz-core/tests/body.rs rename to viz/tests/body.rs diff --git a/viz-core/tests/type_payload.rs b/viz/tests/payload.rs similarity index 100% rename from viz-core/tests/type_payload.rs rename to viz/tests/payload.rs diff --git a/viz/tests/request.rs b/viz/tests/request.rs new file mode 100644 index 00000000..f0659d91 --- /dev/null +++ b/viz/tests/request.rs @@ -0,0 +1,303 @@ +use std::collections::{BTreeMap, HashMap}; + +use headers::{authorization::Bearer, Authorization}; +use serde::{Deserialize, Serialize}; +use viz_core::{ + // TODO: reqwest and hyper haven't used the same version of `http`. + // header::{AUTHORIZATION, CONTENT_TYPE, COOKIE, SET_COOKIE}, + // StatusCode, + types::{self}, + Error, + IntoResponse, + Request, + RequestExt, + Response, + ResponseExt, + Result, +}; + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +struct Page { + p: u8, +} + +#[tokio::test] +async fn request_body() -> Result<()> { + use futures_util::stream::TryStreamExt; + use viz::{ + middleware::{cookie, limits}, + Router, + }; + use viz_test::http::{ + header::{AUTHORIZATION, COOKIE}, + StatusCode, + }; + use viz_test::TestServer; + + let router = Router::new() + .get("/:id", |req: Request| async move { + let id = req.param::("id")?; + Ok(id) + }) + .get("/:username/:repo", |req: Request| async move { + let (username, repo): (String, String) = req.params()?; + Ok(format!("{username}/{repo}")) + }) + .get("/extract-token", |mut req: Request| async move { + let header: types::Header> = req.extract().await?; + Ok(header.into_inner().token().to_string()) + }) + .post("/extract-body", |mut req: Request| async move { + let form: types::Form> = req.extract().await?; + Ok(Response::json(form.into_inner())) + }) + .get("/cookies", |req: Request| async move { + let cookies = req.cookies()?; + let jar = cookies + .jar() + .lock() + .map_err(|e| Error::Responder(e.to_string().into_response()))?; + Ok(jar.iter().count().to_string()) + }) + .get("/cookie", |req: Request| async move { + Ok(req.cookie("viz").unwrap().value().to_string()) + }) + .with(cookie::Config::default()) + .post("/bytes", |mut req: Request| async move { + let data = req.bytes().await?; + Ok(data) + }) + .post("/bytes-with-limit", |mut req: Request| async move { + let data = req.bytes_with(None, 4).await?; + Ok(data) + }) + .post("/bytes-used", |mut req: Request| async move { + req.bytes().await?; + let data = req.bytes().await?; + Ok(data) + }) + .post("/text", |mut req: Request| async move { + let data = req.text().await?; + Ok(Response::text(data)) + }) + .post("/json", |mut req: Request| async move { + let data = req.json::().await?; + Ok(Response::json(data)) + }) + .post("/form", |mut req: Request| async move { + let data = req.form::>().await?; + Ok(Response::json(data)) + }) + .post("/multipart", |mut req: Request| async move { + let mut multipart = req.multipart().await?; + let mut data = HashMap::new(); + + while let Some(mut field) = multipart.try_next().await? { + let buf = field.bytes().await?.to_vec(); + data.insert(field.name, String::from_utf8(buf).map_err(Error::normal)?); + } + + Ok(Response::json(data)) + }) + .with(limits::Config::new().limits(types::Limits::new())); + + let client = TestServer::new(router).await?; + + let resp = client.get("/7").send().await.map_err(Error::normal)?; + assert_eq!(resp.text().await.map_err(Error::normal)?, "7"); + + let resp = client + .get("/viz-rs/viz") + .send() + .await + .map_err(Error::normal)?; + assert_eq!(resp.text().await.map_err(Error::normal)?, "viz-rs/viz"); + + let resp = client + .get("/extract-token") + .header(AUTHORIZATION, "Bearer viz.rs") + .send() + .await + .map_err(Error::normal)?; + assert_eq!(resp.text().await.map_err(Error::normal)?, "viz.rs"); + + let mut form = BTreeMap::new(); + form.insert("password", "rs"); + form.insert("username", "viz"); + let resp = client + .post("/extract-body") + .form(&form) + .send() + .await + .map_err(Error::normal)?; + assert_eq!( + resp.text().await.map_err(Error::normal)?, + r#"{"password":"rs","username":"viz"}"# + ); + + let resp = client + .get("/cookie") + .header(COOKIE, "viz=crate") + .send() + .await + .map_err(Error::normal)?; + assert_eq!(resp.text().await.map_err(Error::normal)?, "crate"); + + let resp = client + .get("/cookies") + .header(COOKIE, "auth=true;dark=false") + .send() + .await + .map_err(Error::normal)?; + assert_eq!(resp.text().await.map_err(Error::normal)?, "2"); + + let resp = client + .post("/bytes") + .body("bytes") + .send() + .await + .map_err(Error::normal)?; + assert_eq!(resp.text().await.map_err(Error::normal)?, "bytes"); + + let resp = client + .post("/bytes-with-limit") + .body("rust") + .send() + .await + .map_err(Error::normal)?; + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.text().await.map_err(Error::normal)?, "rust"); + + let resp = client + .post("/bytes-with-limit") + .body("crate") + .send() + .await + .map_err(Error::normal)?; + assert_eq!(resp.status(), StatusCode::PAYLOAD_TOO_LARGE); + assert_eq!( + resp.text().await.map_err(Error::normal)?, + "payload is too large" + ); + + let resp = client + .post("/bytes-used") + .body("used") + .send() + .await + .map_err(Error::normal)?; + assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + assert_eq!( + resp.text().await.map_err(Error::normal)?, + "payload has been used" + ); + + let resp = client + .post("/text") + .body("text") + .send() + .await + .map_err(Error::normal)?; + assert_eq!(resp.text().await.map_err(Error::normal)?, "text"); + + let resp = client + .post("/json") + .json(&Page { p: 1 }) + .send() + .await + .map_err(Error::normal)?; + assert_eq!( + resp.json::().await.map_err(Error::normal)?, + Page { p: 1 } + ); + + let mut form = HashMap::new(); + form.insert("username", "viz"); + form.insert("password", "rs"); + let resp = client + .post("/form") + .form(&form) + .send() + .await + .map_err(Error::normal)?; + assert_eq!( + resp.json::>() + .await + .map_err(Error::normal)?, + { + let mut form = HashMap::new(); + form.insert("username".to_string(), "viz".to_string()); + form.insert("password".to_string(), "rs".to_string()); + form + } + ); + + let form = viz_test::multipart::Form::new() + .text("key3", "3") + .text("key4", "4"); + let resp = client + .post("/multipart") + .multipart(form) + .send() + .await + .map_err(Error::normal)?; + assert_eq!( + resp.json::>() + .await + .map_err(Error::normal)?, + { + let mut form = HashMap::new(); + form.insert("key3".to_string(), "3".to_string()); + form.insert("key4".to_string(), "4".to_string()); + form + } + ); + + Ok(()) +} + +#[tokio::test] +async fn request_session() -> Result<()> { + use viz::{ + middleware::{cookie, helper::CookieOptions, session}, + Router, + }; + use viz_test::http::header::{COOKIE, SET_COOKIE}; + use viz_test::{nano_id, sessions, TestServer}; + + let router = Router::new() + .post("/session/set", |req: Request| async move { + let counter = req.session().get::("counter")?.unwrap_or_default() + 1; + req.session().set("counter", counter)?; + Ok(counter.to_string()) + }) + .with(session::Config::new( + session::Store::new( + sessions::MemoryStorage::new(), + nano_id::base64::<32>, + |sid: &str| sid.len() == 32, + ), + CookieOptions::default(), + )) + .with(cookie::Config::default()); + + let client = TestServer::new(router).await?; + + let resp = client + .post("/session/set") + .send() + .await + .map_err(Error::normal)?; + let cookie = resp.headers().get(SET_COOKIE).cloned().unwrap(); + assert_eq!(resp.text().await.map_err(Error::normal)?, "1"); + + let resp = client + .post("/session/set") + .header(COOKIE, cookie) + .send() + .await + .map_err(Error::normal)?; + assert_eq!(resp.text().await.map_err(Error::normal)?, "2"); + + Ok(()) +} diff --git a/viz/tests/response.rs b/viz/tests/response.rs new file mode 100644 index 00000000..763b8c25 --- /dev/null +++ b/viz/tests/response.rs @@ -0,0 +1,152 @@ +use futures_util::{stream, Stream, StreamExt}; +use headers::{ContentDisposition, ContentType, HeaderMapExt}; +use http_body_util::{BodyExt, Full}; +use hyper::body::Body; +use serde::{Deserialize, Serialize}; +use viz_core::{ + header::{CONTENT_DISPOSITION, CONTENT_LOCATION, LOCATION}, + Error, OutgoingBody, Response, ResponseExt, Result, StatusCode, +}; + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +struct Page { + p: u8, +} + +#[tokio::test] +async fn response_ext() -> Result<()> { + let resp = Response::with(Full::new("".into()), mime::TEXT_XML.as_ref()); + assert!(resp.ok()); + assert!(resp.content_length().is_none()); + let content_type = resp.headers().typed_get::(); + assert_eq!( + Into::::into(content_type.unwrap()), + mime::TEXT_XML + ); + + let body: OutgoingBody = resp.into_body(); + assert_eq!(Body::size_hint(&body).exact(), Some(b"".len() as u64)); + assert_eq!( + BodyExt::collect(body).await.unwrap().to_bytes().to_vec(), + b"" + ); + + let mut resp = Response::text(""); + *resp.status_mut() = StatusCode::NOT_FOUND; + assert!(!resp.ok()); + let content_type = resp.headers().typed_get::(); + assert_eq!( + Into::::into(content_type.unwrap()), + mime::TEXT_PLAIN_UTF_8 + ); + let mut body: OutgoingBody = resp.into_body(); + assert_eq!(Body::size_hint(&body).exact(), Some(0)); + assert!(body.frame().await.is_none()); + assert!(body.is_end_stream()); + + let resp = Response::html(""); + assert!(resp.ok()); + let content_type = resp.headers().typed_get::(); + assert_eq!( + Into::::into(content_type.unwrap()), + mime::TEXT_HTML_UTF_8 + ); + let mut body: OutgoingBody = resp.into_body(); + assert_eq!(Body::size_hint(&body).exact(), Some(7)); + assert_eq!( + body.frame().await.unwrap().unwrap().into_data().unwrap(), + "" + ); + assert!(body.is_end_stream()); + + let resp = Response::json(Page { p: 255 })?; + assert!(resp.ok()); + let content_type = resp.headers().typed_get::(); + assert_eq!( + Into::::into(content_type.unwrap()), + mime::APPLICATION_JSON + ); + + let resp = Response::stream(stream::repeat("viz").take(2).map(Result::<_, Error>::Ok)); + assert!(resp.ok()); + let body: OutgoingBody = resp.into_body(); + assert_eq!(Stream::size_hint(&body), (0, None)); + let (item, stream) = body.into_future().await; + assert_eq!(item.unwrap().unwrap().to_vec(), b"viz"); + let (item, stream) = stream.into_future().await; + assert_eq!(item.unwrap().unwrap().to_vec(), b"viz"); + let (item, _) = stream.into_future().await; + assert!(item.is_none()); + + let resp = Response::attachment("inline"); + let content_disposition = resp.headers().typed_get::().unwrap(); + assert!(content_disposition.is_inline()); + + let resp = Response::attachment("attachment"); + let content_disposition = resp.headers().typed_get::().unwrap(); + assert!(content_disposition.is_attachment()); + + let resp = Response::attachment(r#"attachment; filename="filename.jpg""#); + let content_disposition = resp.headers().get(CONTENT_DISPOSITION).unwrap(); + assert_eq!( + content_disposition, + r#"attachment; filename="filename.jpg""# + ); + + let resp = Response::location("/login"); + let location = resp.headers().get(CONTENT_LOCATION).unwrap(); + assert_eq!(location, "/login"); + + let resp = Response::redirect("/oauth"); + let location = resp.headers().get(LOCATION).unwrap(); + assert_eq!(location, "/oauth"); + + let resp = Response::redirect_with_status("/oauth", StatusCode::TEMPORARY_REDIRECT); + let location = resp.headers().get(LOCATION).unwrap(); + assert_eq!(location, "/oauth"); + assert_eq!(resp.status(), 307); + + let resp = Response::see_other("/oauth"); + assert_eq!(resp.status(), 303); + + let resp = Response::temporary("/oauth"); + assert_eq!(resp.status(), 307); + + let resp = Response::permanent("/oauth"); + assert_eq!(resp.status(), 308); + + Ok(()) +} + +#[test] +#[should_panic(expected = "not a redirection status code")] +fn response_ext_panic() { + Response::redirect_with_status("/oauth", StatusCode::OK); +} + +#[tokio::test] +async fn response_ext_with_server() -> Result<()> { + use viz::{Request, Router}; + use viz_test::TestServer; + + let router = Router::new() + .get("/", |_: Request| async move { Ok("") }) + .post("/", |_: Request| async move { + Ok(Response::with( + Full::new("".into()), + mime::TEXT_XML.as_ref(), + )) + }); + + let client = TestServer::new(router).await?; + + let resp = client.get("/").send().await.map_err(Error::normal)?; + assert_eq!(resp.content_length(), Some(0)); + assert_eq!(resp.text().await.map_err(Error::normal)?, ""); + + let resp = client.post("/").send().await.map_err(Error::normal)?; + assert_eq!(resp.content_length(), Some(6)); + assert_eq!(resp.text().await.map_err(Error::normal)?, ""); + + Ok(()) +}