diff options
Diffstat (limited to 'third_party/rust/warp/tests')
-rw-r--r-- | third_party/rust/warp/tests/addr.rs | 24 | ||||
-rw-r--r-- | third_party/rust/warp/tests/body.rs | 202 | ||||
-rw-r--r-- | third_party/rust/warp/tests/cookie.rs | 50 | ||||
-rw-r--r-- | third_party/rust/warp/tests/cors.rs | 184 | ||||
-rw-r--r-- | third_party/rust/warp/tests/ext.rs | 28 | ||||
-rw-r--r-- | third_party/rust/warp/tests/filter.rs | 158 | ||||
-rw-r--r-- | third_party/rust/warp/tests/fs.rs | 271 | ||||
-rw-r--r-- | third_party/rust/warp/tests/header.rs | 71 | ||||
-rw-r--r-- | third_party/rust/warp/tests/host.rs | 87 | ||||
-rw-r--r-- | third_party/rust/warp/tests/method.rs | 52 | ||||
-rw-r--r-- | third_party/rust/warp/tests/multipart.rs | 54 | ||||
-rw-r--r-- | third_party/rust/warp/tests/path.rs | 411 | ||||
-rw-r--r-- | third_party/rust/warp/tests/query.rs | 139 | ||||
-rw-r--r-- | third_party/rust/warp/tests/redirect.rs | 57 | ||||
-rw-r--r-- | third_party/rust/warp/tests/reply_with.rs | 64 | ||||
-rw-r--r-- | third_party/rust/warp/tests/tracing.rs | 48 | ||||
-rw-r--r-- | third_party/rust/warp/tests/ws.rs | 288 |
17 files changed, 2188 insertions, 0 deletions
diff --git a/third_party/rust/warp/tests/addr.rs b/third_party/rust/warp/tests/addr.rs new file mode 100644 index 0000000000..12fc46936f --- /dev/null +++ b/third_party/rust/warp/tests/addr.rs @@ -0,0 +1,24 @@ +#![deny(warnings)] + +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + +#[tokio::test] +async fn remote_addr_missing() { + let extract_remote_addr = warp::addr::remote(); + + let req = warp::test::request(); + let resp = req.filter(&extract_remote_addr).await.unwrap(); + assert_eq!(resp, None) +} + +#[tokio::test] +async fn remote_addr_present() { + let extract_remote_addr = warp::addr::remote(); + + let req = warp::test::request().remote_addr("1.2.3.4:5678".parse().unwrap()); + let resp = req.filter(&extract_remote_addr).await.unwrap(); + assert_eq!( + resp, + Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)), 5678)) + ) +} diff --git a/third_party/rust/warp/tests/body.rs b/third_party/rust/warp/tests/body.rs new file mode 100644 index 0000000000..112eaff141 --- /dev/null +++ b/third_party/rust/warp/tests/body.rs @@ -0,0 +1,202 @@ +#![deny(warnings)] + +use bytes::Buf; +use futures_util::TryStreamExt; +use warp::Filter; + +#[tokio::test] +async fn matches() { + let _ = pretty_env_logger::try_init(); + + let concat = warp::body::bytes(); + + let req = warp::test::request().path("/nothing-matches-me"); + + assert!(req.matches(&concat).await); + + let p = warp::path("body"); + let req = warp::test::request().path("/body"); + + let and = p.and(concat); + + assert!(req.matches(&and).await); +} + +#[tokio::test] +async fn server_error_if_taking_body_multiple_times() { + let _ = pretty_env_logger::try_init(); + + let concat = warp::body::bytes(); + let double = concat.and(concat).map(|_, _| warp::reply()); + + let res = warp::test::request().reply(&double).await; + + assert_eq!(res.status(), 500); + assert_eq!(res.body(), "Request body consumed multiple times"); +} + +#[tokio::test] +async fn content_length_limit() { + let _ = pretty_env_logger::try_init(); + + let limit = warp::body::content_length_limit(30).map(warp::reply); + + let res = warp::test::request().reply(&limit).await; + assert_eq!(res.status(), 411, "missing content-length returns 411"); + + let res = warp::test::request() + .header("content-length", "999") + .reply(&limit) + .await; + assert_eq!(res.status(), 413, "over limit returns 413"); + + let res = warp::test::request() + .header("content-length", "2") + .reply(&limit) + .await; + assert_eq!(res.status(), 200, "under limit succeeds"); +} + +#[tokio::test] +async fn json() { + let _ = pretty_env_logger::try_init(); + + let json = warp::body::json::<Vec<i32>>(); + + let req = warp::test::request().body("[1, 2, 3]"); + + let vec = req.filter(&json).await.unwrap(); + assert_eq!(vec, &[1, 2, 3]); + + let req = warp::test::request() + .header("content-type", "application/json") + .body("[3, 2, 1]"); + + let vec = req.filter(&json).await.unwrap(); + assert_eq!(vec, &[3, 2, 1], "matches content-type"); +} + +#[tokio::test] +async fn json_rejects_bad_content_type() { + let _ = pretty_env_logger::try_init(); + + let json = warp::body::json::<Vec<i32>>().map(|_| warp::reply()); + + let req = warp::test::request() + .header("content-type", "text/xml") + .body("[3, 2, 1]"); + + let res = req.reply(&json).await; + assert_eq!( + res.status(), + 415, + "bad content-type should be 415 Unsupported Media Type" + ); +} + +#[tokio::test] +async fn json_invalid() { + let _ = pretty_env_logger::try_init(); + + let json = warp::body::json::<Vec<i32>>().map(|vec| warp::reply::json(&vec)); + + let res = warp::test::request().body("lol#wat").reply(&json).await; + assert_eq!(res.status(), 400); + let prefix = b"Request body deserialize error: "; + assert_eq!(&res.body()[..prefix.len()], prefix); +} + +#[test] +fn json_size_of() { + let json = warp::body::json::<Vec<i32>>(); + assert_eq!(std::mem::size_of_val(&json), 0); +} + +#[tokio::test] +async fn form() { + let _ = pretty_env_logger::try_init(); + + let form = warp::body::form::<Vec<(String, String)>>(); + + let req = warp::test::request().body("foo=bar&baz=quux"); + + let vec = req.filter(&form).await.unwrap(); + let expected = vec![ + ("foo".to_owned(), "bar".to_owned()), + ("baz".to_owned(), "quux".to_owned()), + ]; + assert_eq!(vec, expected); +} + +#[tokio::test] +async fn form_rejects_bad_content_type() { + let _ = pretty_env_logger::try_init(); + + let form = warp::body::form::<Vec<(String, String)>>().map(|_| warp::reply()); + + let req = warp::test::request() + .header("content-type", "application/x-www-form-urlencoded") + .body("foo=bar"); + + let res = req.reply(&form).await; + assert_eq!(res.status(), 200); + + let req = warp::test::request() + .header("content-type", "text/xml") + .body("foo=bar"); + let res = req.reply(&form).await; + assert_eq!( + res.status(), + 415, + "bad content-type should be 415 Unsupported Media Type" + ); +} + +#[tokio::test] +async fn form_allows_charset() { + let _ = pretty_env_logger::try_init(); + + let form = warp::body::form::<Vec<(String, String)>>(); + + let req = warp::test::request() + .header( + "content-type", + "application/x-www-form-urlencoded; charset=utf-8", + ) + .body("foo=bar"); + + let vec = req.filter(&form).await.unwrap(); + let expected = vec![("foo".to_owned(), "bar".to_owned())]; + assert_eq!(vec, expected); +} + +#[tokio::test] +async fn form_invalid() { + let _ = pretty_env_logger::try_init(); + + let form = warp::body::form::<Vec<i32>>().map(|vec| warp::reply::json(&vec)); + + let res = warp::test::request().body("nope").reply(&form).await; + assert_eq!(res.status(), 400); + let prefix = b"Request body deserialize error: "; + assert_eq!(&res.body()[..prefix.len()], prefix); +} + +#[tokio::test] +async fn stream() { + let _ = pretty_env_logger::try_init(); + + let stream = warp::body::stream(); + + let body = warp::test::request() + .body("foo=bar") + .filter(&stream) + .await + .expect("filter() stream"); + + let bufs: Result<Vec<_>, warp::Error> = body.try_collect().await; + let bufs = bufs.unwrap(); + + assert_eq!(bufs.len(), 1); + assert_eq!(bufs[0].chunk(), b"foo=bar"); +} diff --git a/third_party/rust/warp/tests/cookie.rs b/third_party/rust/warp/tests/cookie.rs new file mode 100644 index 0000000000..4cf286a3e1 --- /dev/null +++ b/third_party/rust/warp/tests/cookie.rs @@ -0,0 +1,50 @@ +#![deny(warnings)] + +#[tokio::test] +async fn cookie() { + let foo = warp::cookie::<String>("foo"); + + let req = warp::test::request().header("cookie", "foo=bar"); + assert_eq!(req.filter(&foo).await.unwrap(), "bar"); + + let req = warp::test::request().header("cookie", "abc=def; foo=baz"); + assert_eq!(req.filter(&foo).await.unwrap(), "baz"); + + let req = warp::test::request().header("cookie", "abc=def"); + assert!(!req.matches(&foo).await); + + let req = warp::test::request().header("cookie", "foobar=quux"); + assert!(!req.matches(&foo).await); +} + +#[tokio::test] +async fn optional() { + let foo = warp::cookie::optional::<String>("foo"); + + let req = warp::test::request().header("cookie", "foo=bar"); + assert_eq!(req.filter(&foo).await.unwrap().unwrap(), "bar"); + + let req = warp::test::request().header("cookie", "abc=def; foo=baz"); + assert_eq!(req.filter(&foo).await.unwrap().unwrap(), "baz"); + + let req = warp::test::request().header("cookie", "abc=def"); + assert!(req.matches(&foo).await); + + let req = warp::test::request().header("cookie", "foobar=quux"); + assert!(req.matches(&foo).await); +} + +#[tokio::test] +async fn missing() { + let _ = pretty_env_logger::try_init(); + + let cookie = warp::cookie::<String>("foo"); + + let res = warp::test::request() + .header("cookie", "not=here") + .reply(&cookie) + .await; + + assert_eq!(res.status(), 400); + assert_eq!(res.body(), "Missing request cookie \"foo\""); +} diff --git a/third_party/rust/warp/tests/cors.rs b/third_party/rust/warp/tests/cors.rs new file mode 100644 index 0000000000..977eab06fc --- /dev/null +++ b/third_party/rust/warp/tests/cors.rs @@ -0,0 +1,184 @@ +#![deny(warnings)] +use warp::{http::Method, Filter}; + +#[tokio::test] +async fn allow_methods() { + let cors = warp::cors().allow_methods(&[Method::GET, Method::POST, Method::DELETE]); + + let route = warp::any().map(warp::reply).with(cors); + + let res = warp::test::request() + .method("OPTIONS") + .header("origin", "warp") + .header("access-control-request-method", "DELETE") + .reply(&route) + .await; + + assert_eq!(res.status(), 200); + + let res = warp::test::request() + .method("OPTIONS") + .header("origin", "warp") + .header("access-control-request-method", "PUT") + .reply(&route) + .await; + + assert_eq!(res.status(), 403); +} + +#[tokio::test] +async fn origin_not_allowed() { + let cors = warp::cors() + .allow_methods(&[Method::DELETE]) + .allow_origin("https://hyper.rs"); + + let route = warp::any().map(warp::reply).with(cors); + + let res = warp::test::request() + .method("OPTIONS") + .header("origin", "https://warp.rs") + .header("access-control-request-method", "DELETE") + .reply(&route) + .await; + + assert_eq!(res.status(), 403); + + let res = warp::test::request() + .header("origin", "https://warp.rs") + .header("access-control-request-method", "DELETE") + .reply(&route) + .await; + + assert_eq!(res.status(), 403); +} + +#[tokio::test] +async fn headers_not_exposed() { + let cors = warp::cors() + .allow_any_origin() + .allow_methods(&[Method::GET]); + + let route = warp::any().map(warp::reply).with(cors); + + let res = warp::test::request() + .method("OPTIONS") + .header("origin", "https://warp.rs") + .header("access-control-request-method", "GET") + .reply(&route) + .await; + + assert_eq!( + res.headers().contains_key("access-control-expose-headers"), + false + ); + + let res = warp::test::request() + .method("GET") + .header("origin", "https://warp.rs") + .reply(&route) + .await; + + assert_eq!( + res.headers().contains_key("access-control-expose-headers"), + false + ); +} + +#[tokio::test] +async fn headers_not_allowed() { + let cors = warp::cors() + .allow_methods(&[Method::DELETE]) + .allow_headers(vec!["x-foo"]); + + let route = warp::any().map(warp::reply).with(cors); + + let res = warp::test::request() + .method("OPTIONS") + .header("origin", "https://warp.rs") + .header("access-control-request-headers", "x-bar") + .header("access-control-request-method", "DELETE") + .reply(&route) + .await; + + assert_eq!(res.status(), 403); +} + +#[tokio::test] +async fn success() { + let cors = warp::cors() + .allow_credentials(true) + .allow_headers(vec!["x-foo", "x-bar"]) + .allow_methods(&[Method::POST, Method::DELETE]) + .expose_header("x-header1") + .expose_headers(vec!["x-header2"]) + .max_age(30); + + let route = warp::any().map(warp::reply).with(cors); + + // preflight + let res = warp::test::request() + .method("OPTIONS") + .header("origin", "https://hyper.rs") + .header("access-control-request-headers", "x-bar, x-foo") + .header("access-control-request-method", "DELETE") + .reply(&route) + .await; + assert_eq!(res.status(), 200); + assert_eq!( + res.headers()["access-control-allow-origin"], + "https://hyper.rs" + ); + assert_eq!(res.headers()["access-control-allow-credentials"], "true"); + let allowed_headers = &res.headers()["access-control-allow-headers"]; + assert!(allowed_headers == "x-bar, x-foo" || allowed_headers == "x-foo, x-bar"); + let exposed_headers = &res.headers()["access-control-expose-headers"]; + assert!(exposed_headers == "x-header1, x-header2" || exposed_headers == "x-header2, x-header1"); + assert_eq!(res.headers()["access-control-max-age"], "30"); + let methods = &res.headers()["access-control-allow-methods"]; + assert!( + // HashSet randomly orders these... + methods == "DELETE, POST" || methods == "POST, DELETE", + "access-control-allow-methods: {:?}", + methods, + ); + + // cors request + let res = warp::test::request() + .method("DELETE") + .header("origin", "https://hyper.rs") + .header("x-foo", "hello") + .header("x-bar", "world") + .reply(&route) + .await; + assert_eq!(res.status(), 200); + assert_eq!( + res.headers()["access-control-allow-origin"], + "https://hyper.rs" + ); + assert_eq!(res.headers()["access-control-allow-credentials"], "true"); + assert_eq!(res.headers().get("access-control-max-age"), None); + assert_eq!(res.headers().get("access-control-allow-methods"), None); + let exposed_headers = &res.headers()["access-control-expose-headers"]; + assert!(exposed_headers == "x-header1, x-header2" || exposed_headers == "x-header2, x-header1"); +} + +#[tokio::test] +async fn with_log() { + let cors = warp::cors() + .allow_any_origin() + .allow_methods(&[Method::GET]); + + let route = warp::any() + .map(warp::reply) + .with(cors) + .with(warp::log("cors test")); + + let res = warp::test::request() + .method("OPTIONS") + .header("origin", "warp") + .header("access-control-request-method", "GET") + .reply(&route) + .await; + + assert_eq!(res.status(), 200); +} diff --git a/third_party/rust/warp/tests/ext.rs b/third_party/rust/warp/tests/ext.rs new file mode 100644 index 0000000000..0393d8893d --- /dev/null +++ b/third_party/rust/warp/tests/ext.rs @@ -0,0 +1,28 @@ +#![deny(warnings)] +use warp::Filter; + +#[derive(Clone, Debug, PartialEq)] +struct Ext1(i32); + +#[tokio::test] +async fn set_and_get() { + let ext = warp::ext::get::<Ext1>(); + + let extracted = warp::test::request() + .extension(Ext1(55)) + .filter(&ext) + .await + .unwrap(); + + assert_eq!(extracted, Ext1(55)); +} + +#[tokio::test] +async fn get_missing() { + let ext = warp::ext::get().map(|e: Ext1| e.0.to_string()); + + let res = warp::test::request().reply(&ext).await; + + assert_eq!(res.status(), 500); + assert_eq!(res.body(), "Missing request extension"); +} diff --git a/third_party/rust/warp/tests/filter.rs b/third_party/rust/warp/tests/filter.rs new file mode 100644 index 0000000000..5f69418847 --- /dev/null +++ b/third_party/rust/warp/tests/filter.rs @@ -0,0 +1,158 @@ +#![deny(warnings)] +use std::convert::Infallible; +use warp::Filter; + +#[tokio::test] +async fn flattens_tuples() { + let _ = pretty_env_logger::try_init(); + + let str1 = warp::any().map(|| "warp"); + let true1 = warp::any().map(|| true); + let unit1 = warp::any(); + + // just 1 value + let ext = warp::test::request().filter(&str1).await.unwrap(); + assert_eq!(ext, "warp"); + + // just 1 unit + let ext = warp::test::request().filter(&unit1).await.unwrap(); + assert_eq!(ext, ()); + + // combine 2 values + let and = str1.and(true1); + let ext = warp::test::request().filter(&and).await.unwrap(); + assert_eq!(ext, ("warp", true)); + + // combine 2 reversed + let and = true1.and(str1); + let ext = warp::test::request().filter(&and).await.unwrap(); + assert_eq!(ext, (true, "warp")); + + // combine 1 with unit + let and = str1.and(unit1); + let ext = warp::test::request().filter(&and).await.unwrap(); + assert_eq!(ext, "warp"); + + let and = unit1.and(str1); + let ext = warp::test::request().filter(&and).await.unwrap(); + assert_eq!(ext, "warp"); + + // combine 3 values + let and = str1.and(str1).and(true1); + let ext = warp::test::request().filter(&and).await.unwrap(); + assert_eq!(ext, ("warp", "warp", true)); + + // combine 2 with unit + let and = str1.and(unit1).and(true1); + let ext = warp::test::request().filter(&and).await.unwrap(); + assert_eq!(ext, ("warp", true)); + + let and = unit1.and(str1).and(true1); + let ext = warp::test::request().filter(&and).await.unwrap(); + assert_eq!(ext, ("warp", true)); + + let and = str1.and(true1).and(unit1); + let ext = warp::test::request().filter(&and).await.unwrap(); + assert_eq!(ext, ("warp", true)); + + // nested tuples + let str_true_unit = str1.and(true1).and(unit1); + let unit_str_true = unit1.and(str1).and(true1); + + let and = str_true_unit.and(unit_str_true); + let ext = warp::test::request().filter(&and).await.unwrap(); + assert_eq!(ext, ("warp", true, "warp", true)); + + let and = unit_str_true.and(unit1).and(str1).and(str_true_unit); + let ext = warp::test::request().filter(&and).await.unwrap(); + assert_eq!(ext, ("warp", true, "warp", "warp", true)); +} + +#[tokio::test] +async fn map() { + let _ = pretty_env_logger::try_init(); + + let ok = warp::any().map(warp::reply); + + let req = warp::test::request(); + let resp = req.reply(&ok).await; + assert_eq!(resp.status(), 200); +} + +#[tokio::test] +async fn or() { + let _ = pretty_env_logger::try_init(); + + // Or can be combined with an infallible filter + let a = warp::path::param::<u32>(); + let b = warp::any().map(|| 41i32); + let f = a.or(b); + + let _: Result<_, Infallible> = warp::test::request().filter(&f).await; +} + +#[tokio::test] +async fn or_else() { + let _ = pretty_env_logger::try_init(); + + let a = warp::path::param::<u32>(); + let f = a.or_else(|_| async { Ok::<_, warp::Rejection>((44u32,)) }); + + assert_eq!( + warp::test::request().path("/33").filter(&f).await.unwrap(), + 33, + ); + assert_eq!(warp::test::request().filter(&f).await.unwrap(), 44,); + + // OrElse can be combined with an infallible filter + let a = warp::path::param::<u32>(); + let f = a.or_else(|_| async { Ok::<_, Infallible>((44u32,)) }); + + let _: Result<_, Infallible> = warp::test::request().filter(&f).await; +} + +#[tokio::test] +async fn recover() { + let _ = pretty_env_logger::try_init(); + + let a = warp::path::param::<String>(); + let f = a.recover(|err| async move { Err::<String, _>(err) }); + + // not rejected + let resp = warp::test::request().path("/hi").reply(&f).await; + assert_eq!(resp.status(), 200); + assert_eq!(resp.body(), "hi"); + + // rejected, recovered, re-rejected + let resp = warp::test::request().reply(&f).await; + assert_eq!(resp.status(), 404); + + // Recover can be infallible + let f = a.recover(|_| async move { Ok::<_, Infallible>("shh") }); + + let _: Result<_, Infallible> = warp::test::request().filter(&f).await; +} + +#[tokio::test] +async fn unify() { + let _ = pretty_env_logger::try_init(); + + let a = warp::path::param::<u32>(); + let b = warp::path::param::<u32>(); + let f = a.or(b).unify(); + + let ex = warp::test::request().path("/1").filter(&f).await.unwrap(); + + assert_eq!(ex, 1); +} + +#[should_panic] +#[tokio::test] +async fn nested() { + let f = warp::any().and_then(|| async { + let p = warp::path::param::<u32>(); + warp::test::request().filter(&p).await + }); + + let _ = warp::test::request().filter(&f).await; +} diff --git a/third_party/rust/warp/tests/fs.rs b/third_party/rust/warp/tests/fs.rs new file mode 100644 index 0000000000..4faa933d5e --- /dev/null +++ b/third_party/rust/warp/tests/fs.rs @@ -0,0 +1,271 @@ +#![deny(warnings)] +use std::fs; + +#[tokio::test] +async fn file() { + let _ = pretty_env_logger::try_init(); + + let file = warp::fs::file("README.md"); + + let req = warp::test::request(); + let res = req.reply(&file).await; + + assert_eq!(res.status(), 200); + + let contents = fs::read("README.md").expect("fs::read README.md"); + assert_eq!(res.headers()["content-length"], contents.len().to_string()); + assert_eq!(res.headers()["accept-ranges"], "bytes"); + + let ct = &res.headers()["content-type"]; + assert!( + ct == "text/x-markdown" || ct == "text/markdown", + "content-type is not markdown: {:?}", + ct, + ); + + assert_eq!(res.body(), &*contents); +} + +#[tokio::test] +async fn dir() { + let _ = pretty_env_logger::try_init(); + + let file = warp::fs::dir("examples"); + + let req = warp::test::request().path("/todos.rs"); + let res = req.reply(&file).await; + + assert_eq!(res.status(), 200); + + let contents = fs::read("examples/todos.rs").expect("fs::read"); + assert_eq!(res.headers()["content-length"], contents.len().to_string()); + assert_eq!(res.headers()["content-type"], "text/x-rust"); + assert_eq!(res.headers()["accept-ranges"], "bytes"); + + assert_eq!(res.body(), &*contents); + + let malformed_req = warp::test::request().path("todos.rs"); + assert_eq!(malformed_req.reply(&file).await.status(), 404); +} + +#[tokio::test] +async fn dir_encoded() { + let _ = pretty_env_logger::try_init(); + + let file = warp::fs::dir("examples"); + + let req = warp::test::request().path("/todos%2ers"); + let res = req.reply(&file).await; + + assert_eq!(res.status(), 200); + + let contents = fs::read("examples/todos.rs").expect("fs::read"); + assert_eq!(res.headers()["content-length"], contents.len().to_string()); + + assert_eq!(res.body(), &*contents); +} + +#[tokio::test] +async fn dir_not_found() { + let _ = pretty_env_logger::try_init(); + + let file = warp::fs::dir("examples"); + + let req = warp::test::request().path("/definitely-not-found"); + let res = req.reply(&file).await; + + assert_eq!(res.status(), 404); +} + +#[tokio::test] +async fn dir_bad_path() { + let _ = pretty_env_logger::try_init(); + + let file = warp::fs::dir("examples"); + + let req = warp::test::request().path("/../README.md"); + let res = req.reply(&file).await; + + assert_eq!(res.status(), 404); +} + +#[tokio::test] +async fn dir_bad_encoded_path() { + let _ = pretty_env_logger::try_init(); + + let file = warp::fs::dir("examples"); + + let req = warp::test::request().path("/%2E%2e/README.md"); + let res = req.reply(&file).await; + + assert_eq!(res.status(), 404); +} + +#[tokio::test] +async fn dir_fallback_index_on_dir() { + let _ = pretty_env_logger::try_init(); + + let file = warp::fs::dir("examples"); + let req = warp::test::request().path("/dir"); + let res = req.reply(&file).await; + let contents = fs::read("examples/dir/index.html").expect("fs::read"); + assert_eq!(res.headers()["content-length"], contents.len().to_string()); + assert_eq!(res.status(), 200); + let req = warp::test::request().path("/dir/"); + let res = req.reply(&file).await; + assert_eq!(res.headers()["content-length"], contents.len().to_string()); + assert_eq!(res.status(), 200); +} + +#[tokio::test] +async fn not_modified() { + let _ = pretty_env_logger::try_init(); + + let file = warp::fs::file("README.md"); + + let req = warp::test::request(); + let body = fs::read("README.md").unwrap(); + let res1 = req.reply(&file).await; + assert_eq!(res1.status(), 200); + assert_eq!(res1.headers()["content-length"], body.len().to_string()); + + // if-modified-since + let res = warp::test::request() + .header("if-modified-since", &res1.headers()["last-modified"]) + .reply(&file) + .await; + assert_eq!(res.headers().get("content-length"), None); + assert_eq!(res.status(), 304); + assert_eq!(res.body(), ""); + + // clearly too old + let res = warp::test::request() + .header("if-modified-since", "Mon, 07 Nov 1994 01:00:00 GMT") + .reply(&file) + .await; + assert_eq!(res.status(), 200); + assert_eq!(res.body(), &body); + assert_eq!(res1.headers()["content-length"], body.len().to_string()); +} + +#[tokio::test] +async fn precondition() { + let _ = pretty_env_logger::try_init(); + + let file = warp::fs::file("README.md"); + + let req = warp::test::request(); + let res1 = req.reply(&file).await; + assert_eq!(res1.status(), 200); + + // if-unmodified-since + let res = warp::test::request() + .header("if-unmodified-since", &res1.headers()["last-modified"]) + .reply(&file) + .await; + assert_eq!(res.status(), 200); + + // clearly too old + let res = warp::test::request() + .header("if-unmodified-since", "Mon, 07 Nov 1994 01:00:00 GMT") + .reply(&file) + .await; + assert_eq!(res.status(), 412); + assert_eq!(res.body(), ""); +} + +#[tokio::test] +async fn byte_ranges() { + let _ = pretty_env_logger::try_init(); + + let contents = fs::read("README.md").expect("fs::read README.md"); + let file = warp::fs::file("README.md"); + + let res = warp::test::request() + .header("range", "bytes=100-200") + .reply(&file) + .await; + assert_eq!(res.status(), 206); + assert_eq!( + res.headers()["content-range"], + format!("bytes 100-200/{}", contents.len()) + ); + assert_eq!(res.headers()["content-length"], "101"); + assert_eq!(res.body(), &contents[100..=200]); + + // bad range + let res = warp::test::request() + .header("range", "bytes=100-10") + .reply(&file) + .await; + assert_eq!(res.status(), 416); + assert_eq!( + res.headers()["content-range"], + format!("bytes */{}", contents.len()) + ); + assert_eq!(res.headers().get("content-length"), None); + assert_eq!(res.body(), ""); + + // out of range + let res = warp::test::request() + .header("range", "bytes=100-100000") + .reply(&file) + .await; + assert_eq!(res.status(), 416); + assert_eq!( + res.headers()["content-range"], + format!("bytes */{}", contents.len()) + ); + assert_eq!(res.headers().get("content-length"), None); + assert_eq!(res.body(), ""); + + // if-range too old + let res = warp::test::request() + .header("range", "bytes=100-200") + .header("if-range", "Mon, 07 Nov 1994 01:00:00 GMT") + .reply(&file) + .await; + assert_eq!(res.status(), 200); + assert_eq!(res.headers()["content-length"], contents.len().to_string()); + assert_eq!(res.headers().get("content-range"), None); +} + +#[tokio::test] +async fn byte_ranges_with_excluded_file_size() { + let _ = pretty_env_logger::try_init(); + + let contents = fs::read("README.md").expect("fs::read README.md"); + let file = warp::fs::file("README.md"); + + // range including end of file (non-inclusive result) + let res = warp::test::request() + .header("range", format!("bytes=100-{}", contents.len())) + .reply(&file) + .await; + assert_eq!(res.status(), 206); + assert_eq!( + res.headers()["content-range"], + format!("bytes 100-{}/{}", contents.len() - 1, contents.len()) + ); + assert_eq!( + res.headers()["content-length"], + format!("{}", contents.len() - 100) + ); + assert_eq!(res.body(), &contents[100..=contents.len() - 1]); + + // range with 1 byte to end yields same result as above. (inclusive result) + let res = warp::test::request() + .header("range", format!("bytes=100-{}", contents.len() - 1)) + .reply(&file) + .await; + assert_eq!(res.status(), 206); + assert_eq!( + res.headers()["content-range"], + format!("bytes 100-{}/{}", contents.len() - 1, contents.len()) + ); + assert_eq!( + res.headers()["content-length"], + format!("{}", contents.len() - 100) + ); + assert_eq!(res.body(), &contents[100..=contents.len() - 1]); +} diff --git a/third_party/rust/warp/tests/header.rs b/third_party/rust/warp/tests/header.rs new file mode 100644 index 0000000000..ea882b44a1 --- /dev/null +++ b/third_party/rust/warp/tests/header.rs @@ -0,0 +1,71 @@ +#![deny(warnings)] +use warp::Filter; + +#[tokio::test] +async fn exact() { + let _ = pretty_env_logger::try_init(); + + let host = warp::header::exact("host", "localhost"); + + let req = warp::test::request().header("host", "localhost"); + + assert!(req.matches(&host).await); + + let req = warp::test::request(); + assert!(!req.matches(&host).await, "header missing"); + + let req = warp::test::request().header("host", "hyper.rs"); + assert!(!req.matches(&host).await, "header value different"); +} + +#[tokio::test] +async fn exact_rejections() { + let _ = pretty_env_logger::try_init(); + + let host = warp::header::exact("host", "localhost").map(warp::reply); + + let res = warp::test::request() + .header("host", "nope") + .reply(&host) + .await; + + assert_eq!(res.status(), 400); + assert_eq!(res.body(), "Invalid request header \"host\""); + + let res = warp::test::request() + .header("not-even-a-host", "localhost") + .reply(&host) + .await; + + assert_eq!(res.status(), 400); + assert_eq!(res.body(), "Missing request header \"host\""); +} + +#[tokio::test] +async fn optional() { + let _ = pretty_env_logger::try_init(); + + let con_len = warp::header::optional::<u64>("content-length"); + + let val = warp::test::request() + .filter(&con_len) + .await + .expect("missing header matches"); + assert_eq!(val, None); + + let val = warp::test::request() + .header("content-length", "5") + .filter(&con_len) + .await + .expect("existing header matches"); + + assert_eq!(val, Some(5)); + + assert!( + !warp::test::request() + .header("content-length", "boom") + .matches(&con_len) + .await, + "invalid optional header still rejects", + ); +} diff --git a/third_party/rust/warp/tests/host.rs b/third_party/rust/warp/tests/host.rs new file mode 100644 index 0000000000..2fb5f42475 --- /dev/null +++ b/third_party/rust/warp/tests/host.rs @@ -0,0 +1,87 @@ +#![deny(warnings)] +use warp::host::Authority; + +#[tokio::test] +async fn exact() { + let filter = warp::host::exact("known.com"); + + // no authority + let req = warp::test::request(); + assert!(req.filter(&filter).await.unwrap_err().is_not_found()); + + // specified in URI + let req = warp::test::request().path("http://known.com/about-us"); + assert!(req.filter(&filter).await.is_ok()); + + let req = warp::test::request().path("http://unknown.com/about-us"); + assert!(req.filter(&filter).await.unwrap_err().is_not_found()); + + // specified in Host header + let req = warp::test::request() + .header("host", "known.com") + .path("/about-us"); + assert!(req.filter(&filter).await.is_ok()); + + let req = warp::test::request() + .header("host", "unknown.com") + .path("/about-us"); + assert!(req.filter(&filter).await.unwrap_err().is_not_found()); + + // specified in both - matching + let req = warp::test::request() + .header("host", "known.com") + .path("http://known.com/about-us"); + assert!(req.filter(&filter).await.is_ok()); + + let req = warp::test::request() + .header("host", "unknown.com") + .path("http://unknown.com/about-us"); + assert!(req.filter(&filter).await.unwrap_err().is_not_found()); + + // specified in both - mismatch + let req = warp::test::request() + .header("host", "known.com") + .path("http://known2.com/about-us"); + assert!(req + .filter(&filter) + .await + .unwrap_err() + .find::<warp::reject::InvalidHeader>() + .is_some()); + + // bad host header - invalid chars + let req = warp::test::request() + .header("host", "ðŸ˜") + .path("http://known.com/about-us"); + assert!(req + .filter(&filter) + .await + .unwrap_err() + .find::<warp::reject::InvalidHeader>() + .is_some()); + + // bad host header - invalid format + let req = warp::test::request() + .header("host", "hello space.com") + .path("http://known.com//about-us"); + assert!(req + .filter(&filter) + .await + .unwrap_err() + .find::<warp::reject::InvalidHeader>() + .is_some()); +} + +#[tokio::test] +async fn optional() { + let filter = warp::host::optional(); + + let req = warp::test::request().header("host", "example.com"); + assert_eq!( + req.filter(&filter).await.unwrap(), + Some(Authority::from_static("example.com")) + ); + + let req = warp::test::request(); + assert_eq!(req.filter(&filter).await.unwrap(), None); +} diff --git a/third_party/rust/warp/tests/method.rs b/third_party/rust/warp/tests/method.rs new file mode 100644 index 0000000000..d03126cabd --- /dev/null +++ b/third_party/rust/warp/tests/method.rs @@ -0,0 +1,52 @@ +#![deny(warnings)] +use warp::Filter; + +#[tokio::test] +async fn method() { + let _ = pretty_env_logger::try_init(); + let get = warp::get().map(warp::reply); + + let req = warp::test::request(); + assert!(req.matches(&get).await); + + let req = warp::test::request().method("POST"); + assert!(!req.matches(&get).await); + + let req = warp::test::request().method("POST"); + let resp = req.reply(&get).await; + assert_eq!(resp.status(), 405); +} + +#[tokio::test] +async fn method_not_allowed_trumps_not_found() { + let _ = pretty_env_logger::try_init(); + let get = warp::get().and(warp::path("hello").map(warp::reply)); + let post = warp::post().and(warp::path("bye").map(warp::reply)); + + let routes = get.or(post); + + let req = warp::test::request().method("GET").path("/bye"); + + let resp = req.reply(&routes).await; + // GET was allowed, but only for /hello, so POST returning 405 is fine. + assert_eq!(resp.status(), 405); +} + +#[tokio::test] +async fn bad_request_trumps_method_not_allowed() { + let _ = pretty_env_logger::try_init(); + let get = warp::get() + .and(warp::path("hello")) + .and(warp::header::exact("foo", "bar")) + .map(warp::reply); + let post = warp::post().and(warp::path("bye")).map(warp::reply); + + let routes = get.or(post); + + let req = warp::test::request().method("GET").path("/hello"); + + let resp = req.reply(&routes).await; + // GET was allowed, but header rejects with 400, should not + // assume POST was the appropriate method. + assert_eq!(resp.status(), 400); +} diff --git a/third_party/rust/warp/tests/multipart.rs b/third_party/rust/warp/tests/multipart.rs new file mode 100644 index 0000000000..3172367bcb --- /dev/null +++ b/third_party/rust/warp/tests/multipart.rs @@ -0,0 +1,54 @@ +#![deny(warnings)] +use bytes::BufMut; +use futures_util::{TryFutureExt, TryStreamExt}; +use warp::{multipart, Filter}; + +#[tokio::test] +async fn form_fields() { + let _ = pretty_env_logger::try_init(); + + let route = multipart::form().and_then(|form: multipart::FormData| { + async { + // Collect the fields into (name, value): (String, Vec<u8>) + let part: Result<Vec<(String, Vec<u8>)>, warp::Rejection> = form + .and_then(|part| { + let name = part.name().to_string(); + let value = part.stream().try_fold(Vec::new(), |mut vec, data| { + vec.put(data); + async move { Ok(vec) } + }); + value.map_ok(move |vec| (name, vec)) + }) + .try_collect() + .await + .map_err(|e| { + panic!("multipart error: {:?}", e); + }); + part + } + }); + + let boundary = "--abcdef1234--"; + let body = format!( + "\ + --{0}\r\n\ + content-disposition: form-data; name=\"foo\"\r\n\r\n\ + bar\r\n\ + --{0}--\r\n\ + ", + boundary + ); + + let req = warp::test::request() + .method("POST") + .header("content-length", body.len()) + .header( + "content-type", + format!("multipart/form-data; boundary={}", boundary), + ) + .body(body); + + let vec = req.filter(&route).await.unwrap(); + assert_eq!(&vec[0].0, "foo"); + assert_eq!(&vec[0].1, b"bar"); +} diff --git a/third_party/rust/warp/tests/path.rs b/third_party/rust/warp/tests/path.rs new file mode 100644 index 0000000000..4f9302036e --- /dev/null +++ b/third_party/rust/warp/tests/path.rs @@ -0,0 +1,411 @@ +#![deny(warnings)] +#[macro_use] +extern crate warp; + +use futures_util::future; +use warp::Filter; + +#[tokio::test] +async fn path() { + let _ = pretty_env_logger::try_init(); + + let foo = warp::path("foo"); + let bar = warp::path(String::from("bar")); + let foo_bar = foo.and(bar.clone()); + + // /foo + let foo_req = || warp::test::request().path("/foo"); + + assert!(foo_req().matches(&foo).await); + assert!(!foo_req().matches(&bar).await); + assert!(!foo_req().matches(&foo_bar).await); + + // /foo/bar + let foo_bar_req = || warp::test::request().path("/foo/bar"); + + assert!(foo_bar_req().matches(&foo).await); + assert!(!foo_bar_req().matches(&bar).await); + assert!(foo_bar_req().matches(&foo_bar).await); +} + +#[tokio::test] +async fn param() { + let _ = pretty_env_logger::try_init(); + + let num = warp::path::param::<u32>(); + + let req = warp::test::request().path("/321"); + assert_eq!(req.filter(&num).await.unwrap(), 321); + + let s = warp::path::param::<String>(); + + let req = warp::test::request().path("/warp"); + assert_eq!(req.filter(&s).await.unwrap(), "warp"); + + // u32 doesn't extract a non-int + let req = warp::test::request().path("/warp"); + assert!(!req.matches(&num).await); + + let combo = num.map(|n| n + 5).and(s); + + let req = warp::test::request().path("/42/vroom"); + assert_eq!(req.filter(&combo).await.unwrap(), (47, "vroom".to_string())); + + // empty segments never match + let req = warp::test::request(); + assert!( + !req.matches(&s).await, + "param should never match an empty segment" + ); +} + +#[tokio::test] +async fn end() { + let _ = pretty_env_logger::try_init(); + + let foo = warp::path("foo"); + let end = warp::path::end(); + let foo_end = foo.and(end); + + assert!( + warp::test::request().path("/").matches(&end).await, + "end() matches /" + ); + + assert!( + warp::test::request() + .path("http://localhost:1234") + .matches(&end) + .await, + "end() matches /" + ); + + assert!( + warp::test::request() + .path("http://localhost:1234?q=2") + .matches(&end) + .await, + "end() matches empty path" + ); + + assert!( + warp::test::request() + .path("localhost:1234") + .matches(&end) + .await, + "end() matches authority-form" + ); + + assert!( + !warp::test::request().path("/foo").matches(&end).await, + "end() doesn't match /foo" + ); + + assert!( + warp::test::request().path("/foo").matches(&foo_end).await, + "path().and(end()) matches /foo" + ); + + assert!( + warp::test::request().path("/foo/").matches(&foo_end).await, + "path().and(end()) matches /foo/" + ); +} + +#[tokio::test] +async fn tail() { + let tail = warp::path::tail(); + + // matches full path + let ex = warp::test::request() + .path("/42/vroom") + .filter(&tail) + .await + .unwrap(); + assert_eq!(ex.as_str(), "42/vroom"); + + // matches index + let ex = warp::test::request().path("/").filter(&tail).await.unwrap(); + assert_eq!(ex.as_str(), ""); + + // doesn't include query + let ex = warp::test::request() + .path("/foo/bar?baz=quux") + .filter(&tail) + .await + .unwrap(); + assert_eq!(ex.as_str(), "foo/bar"); + + // doesn't include previously matched prefix + let and = warp::path("foo").and(tail); + let ex = warp::test::request() + .path("/foo/bar") + .filter(&and) + .await + .unwrap(); + assert_eq!(ex.as_str(), "bar"); + + // sets unmatched path index to end + let m = tail.and(warp::path("foo")); + assert!(!warp::test::request().path("/foo/bar").matches(&m).await); + + let m = tail.and(warp::path::end()); + assert!(warp::test::request().path("/foo/bar").matches(&m).await); + + let ex = warp::test::request() + .path("localhost") + .filter(&tail) + .await + .unwrap(); + assert_eq!(ex.as_str(), "/"); +} + +#[tokio::test] +async fn or() { + let _ = pretty_env_logger::try_init(); + + // /foo/bar OR /foo/baz + let foo = warp::path("foo"); + let bar = warp::path("bar"); + let baz = warp::path("baz"); + let p = foo.and(bar.or(baz)); + + // /foo/bar + let req = warp::test::request().path("/foo/bar"); + + assert!(req.matches(&p).await); + + // /foo/baz + let req = warp::test::request().path("/foo/baz"); + + assert!(req.matches(&p).await); + + // deeper nested ORs + // /foo/bar/baz OR /foo/baz/bar OR /foo/bar/bar + let p = foo + .and(bar.and(baz).map(|| panic!("shouldn't match"))) + .or(foo.and(baz.and(bar)).map(|| panic!("shouldn't match"))) + .or(foo.and(bar.and(bar))); + + // /foo/baz + let req = warp::test::request().path("/foo/baz/baz"); + assert!(!req.matches(&p).await); + + // /foo/bar/bar + let req = warp::test::request().path("/foo/bar/bar"); + assert!(req.matches(&p).await); +} + +#[tokio::test] +async fn or_else() { + let _ = pretty_env_logger::try_init(); + + let foo = warp::path("foo"); + let bar = warp::path("bar"); + + let p = foo.and(bar.or_else(|_| future::ok::<_, std::convert::Infallible>(()))); + + // /foo/bar + let req = warp::test::request().path("/foo/nope"); + + assert!(req.matches(&p).await); +} + +#[tokio::test] +async fn path_macro() { + let _ = pretty_env_logger::try_init(); + + let req = warp::test::request().path("/foo/bar"); + let p = path!("foo" / "bar"); + assert!(req.matches(&p).await); + + let req = warp::test::request().path("/foo/bar"); + let p = path!(String / "bar"); + assert_eq!(req.filter(&p).await.unwrap(), "foo"); + + let req = warp::test::request().path("/foo/bar"); + let p = path!("foo" / String); + assert_eq!(req.filter(&p).await.unwrap(), "bar"); + + // Requires path end + + let req = warp::test::request().path("/foo/bar/baz"); + let p = path!("foo" / "bar"); + assert!(!req.matches(&p).await); + + let req = warp::test::request().path("/foo/bar/baz"); + let p = path!("foo" / "bar").and(warp::path("baz")); + assert!(!req.matches(&p).await); + + // Prefix syntax + + let req = warp::test::request().path("/foo/bar/baz"); + let p = path!("foo" / "bar" / ..); + assert!(req.matches(&p).await); + + let req = warp::test::request().path("/foo/bar/baz"); + let p = path!("foo" / "bar" / ..).and(warp::path!("baz")); + assert!(req.matches(&p).await); + + // Empty + + let req = warp::test::request().path("/"); + let p = path!(); + assert!(req.matches(&p).await); + + let req = warp::test::request().path("/foo"); + let p = path!(); + assert!(!req.matches(&p).await); +} + +#[tokio::test] +async fn full_path() { + let full_path = warp::path::full(); + + let foo = warp::path("foo"); + let bar = warp::path("bar"); + let param = warp::path::param::<u32>(); + + // matches full request path + let ex = warp::test::request() + .path("/42/vroom") + .filter(&full_path) + .await + .unwrap(); + assert_eq!(ex.as_str(), "/42/vroom"); + + // matches index + let ex = warp::test::request() + .path("/") + .filter(&full_path) + .await + .unwrap(); + assert_eq!(ex.as_str(), "/"); + + // does not include query + let ex = warp::test::request() + .path("/foo/bar?baz=quux") + .filter(&full_path) + .await + .unwrap(); + assert_eq!(ex.as_str(), "/foo/bar"); + + // includes previously matched prefix + let and = foo.and(full_path); + let ex = warp::test::request() + .path("/foo/bar") + .filter(&and) + .await + .unwrap(); + assert_eq!(ex.as_str(), "/foo/bar"); + + // includes following matches + let and = full_path.and(foo); + let ex = warp::test::request() + .path("/foo/bar") + .filter(&and) + .await + .unwrap(); + assert_eq!(ex.as_str(), "/foo/bar"); + + // includes previously matched param + let and = foo.and(param).and(full_path); + let (_, ex) = warp::test::request() + .path("/foo/123") + .filter(&and) + .await + .unwrap(); + assert_eq!(ex.as_str(), "/foo/123"); + + // does not modify matching + let m = full_path.and(foo).and(bar); + assert!(warp::test::request().path("/foo/bar").matches(&m).await); + + // doesn't panic on authority-form + let ex = warp::test::request() + .path("localhost:1234") + .filter(&full_path) + .await + .unwrap(); + assert_eq!(ex.as_str(), "/"); +} + +#[tokio::test] +async fn peek() { + let peek = warp::path::peek(); + + let foo = warp::path("foo"); + let bar = warp::path("bar"); + let param = warp::path::param::<u32>(); + + // matches full request path + let ex = warp::test::request() + .path("/42/vroom") + .filter(&peek) + .await + .unwrap(); + assert_eq!(ex.as_str(), "42/vroom"); + + // matches index + let ex = warp::test::request().path("/").filter(&peek).await.unwrap(); + assert_eq!(ex.as_str(), ""); + + // does not include query + let ex = warp::test::request() + .path("/foo/bar?baz=quux") + .filter(&peek) + .await + .unwrap(); + assert_eq!(ex.as_str(), "foo/bar"); + + // does not include previously matched prefix + let and = foo.and(peek); + let ex = warp::test::request() + .path("/foo/bar") + .filter(&and) + .await + .unwrap(); + assert_eq!(ex.as_str(), "bar"); + + // includes following matches + let and = peek.and(foo); + let ex = warp::test::request() + .path("/foo/bar") + .filter(&and) + .await + .unwrap(); + assert_eq!(ex.as_str(), "foo/bar"); + + // does not include previously matched param + let and = foo.and(param).and(peek); + let (_, ex) = warp::test::request() + .path("/foo/123") + .filter(&and) + .await + .unwrap(); + assert_eq!(ex.as_str(), ""); + + // does not modify matching + let and = peek.and(foo).and(bar); + assert!(warp::test::request().path("/foo/bar").matches(&and).await); +} + +#[tokio::test] +async fn peek_segments() { + let peek = warp::path::peek(); + + // matches full request path + let ex = warp::test::request() + .path("/42/vroom") + .filter(&peek) + .await + .unwrap(); + + assert_eq!(ex.segments().collect::<Vec<_>>(), &["42", "vroom"]); + + // matches index + let ex = warp::test::request().path("/").filter(&peek).await.unwrap(); + + let segs = ex.segments().collect::<Vec<_>>(); + assert_eq!(segs, Vec::<&str>::new()); +} diff --git a/third_party/rust/warp/tests/query.rs b/third_party/rust/warp/tests/query.rs new file mode 100644 index 0000000000..a20f104e67 --- /dev/null +++ b/third_party/rust/warp/tests/query.rs @@ -0,0 +1,139 @@ +#![deny(warnings)] + +use serde_derive::Deserialize; +use std::collections::HashMap; +use warp::Filter; + +#[tokio::test] +async fn query() { + let as_map = warp::query::<HashMap<String, String>>(); + + let req = warp::test::request().path("/?foo=bar&baz=quux"); + + let extracted = req.filter(&as_map).await.unwrap(); + assert_eq!(extracted["foo"], "bar"); + assert_eq!(extracted["baz"], "quux"); +} + +#[tokio::test] +async fn query_struct() { + let as_struct = warp::query::<MyArgs>(); + + let req = warp::test::request().path("/?foo=bar&baz=quux"); + + let extracted = req.filter(&as_struct).await.unwrap(); + assert_eq!( + extracted, + MyArgs { + foo: Some("bar".into()), + baz: Some("quux".into()) + } + ); +} + +#[tokio::test] +async fn empty_query_struct() { + let as_struct = warp::query::<MyArgs>(); + + let req = warp::test::request().path("/?"); + + let extracted = req.filter(&as_struct).await.unwrap(); + assert_eq!( + extracted, + MyArgs { + foo: None, + baz: None + } + ); +} + +#[tokio::test] +async fn query_struct_no_values() { + let as_struct = warp::query::<MyArgs>(); + + let req = warp::test::request().path("/?foo&baz"); + + let extracted = req.filter(&as_struct).await.unwrap(); + assert_eq!( + extracted, + MyArgs { + foo: Some("".into()), + baz: Some("".into()) + } + ); +} + +#[tokio::test] +async fn missing_query_struct() { + let as_struct = warp::query::<MyArgs>(); + + let req = warp::test::request().path("/"); + + let extracted = req.filter(&as_struct).await.unwrap(); + assert_eq!( + extracted, + MyArgs { + foo: None, + baz: None + } + ); +} + +#[derive(Deserialize, Debug, Eq, PartialEq)] +struct MyArgs { + foo: Option<String>, + baz: Option<String>, +} + +#[tokio::test] +async fn required_query_struct() { + let as_struct = warp::query::<MyRequiredArgs>(); + + let req = warp::test::request().path("/?foo=bar&baz=quux"); + + let extracted = req.filter(&as_struct).await.unwrap(); + assert_eq!( + extracted, + MyRequiredArgs { + foo: "bar".into(), + baz: "quux".into() + } + ); +} + +#[tokio::test] +async fn missing_required_query_struct_partial() { + let as_struct = warp::query::<MyRequiredArgs>(); + + let req = warp::test::request().path("/?foo=something"); + + let extracted = req.filter(&as_struct).await; + assert!(extracted.is_err()) +} + +#[tokio::test] +async fn missing_required_query_struct_no_query() { + let as_struct = warp::query::<MyRequiredArgs>().map(|_| warp::reply()); + + let req = warp::test::request().path("/"); + + let res = req.reply(&as_struct).await; + assert_eq!(res.status(), 400); + assert_eq!(res.body(), "Invalid query string"); +} + +#[derive(Deserialize, Debug, Eq, PartialEq)] +struct MyRequiredArgs { + foo: String, + baz: String, +} + +#[tokio::test] +async fn raw_query() { + let as_raw = warp::query::raw(); + + let req = warp::test::request().path("/?foo=bar&baz=quux"); + + let extracted = req.filter(&as_raw).await.unwrap(); + assert_eq!(extracted, "foo=bar&baz=quux".to_owned()); +} diff --git a/third_party/rust/warp/tests/redirect.rs b/third_party/rust/warp/tests/redirect.rs new file mode 100644 index 0000000000..be7fd33e4a --- /dev/null +++ b/third_party/rust/warp/tests/redirect.rs @@ -0,0 +1,57 @@ +#![deny(warnings)] +use warp::{http::Uri, Filter}; + +#[tokio::test] +async fn redirect_uri() { + let over_there = warp::any().map(|| warp::redirect(Uri::from_static("/over-there"))); + + let req = warp::test::request(); + let resp = req.reply(&over_there).await; + + assert_eq!(resp.status(), 301); + assert_eq!(resp.headers()["location"], "/over-there"); +} + +#[tokio::test] +async fn redirect_found_uri() { + let over_there = warp::any().map(|| warp::redirect::found(Uri::from_static("/over-there"))); + + let req = warp::test::request(); + let resp = req.reply(&over_there).await; + + assert_eq!(resp.status(), 302); + assert_eq!(resp.headers()["location"], "/over-there"); +} + +#[tokio::test] +async fn redirect_see_other_uri() { + let over_there = warp::any().map(|| warp::redirect::see_other(Uri::from_static("/over-there"))); + + let req = warp::test::request(); + let resp = req.reply(&over_there).await; + + assert_eq!(resp.status(), 303); + assert_eq!(resp.headers()["location"], "/over-there"); +} + +#[tokio::test] +async fn redirect_temporary_uri() { + let over_there = warp::any().map(|| warp::redirect::temporary(Uri::from_static("/over-there"))); + + let req = warp::test::request(); + let resp = req.reply(&over_there).await; + + assert_eq!(resp.status(), 307); + assert_eq!(resp.headers()["location"], "/over-there"); +} + +#[tokio::test] +async fn redirect_permanent_uri() { + let over_there = warp::any().map(|| warp::redirect::permanent(Uri::from_static("/over-there"))); + + let req = warp::test::request(); + let resp = req.reply(&over_there).await; + + assert_eq!(resp.status(), 308); + assert_eq!(resp.headers()["location"], "/over-there"); +} diff --git a/third_party/rust/warp/tests/reply_with.rs b/third_party/rust/warp/tests/reply_with.rs new file mode 100644 index 0000000000..0766924b41 --- /dev/null +++ b/third_party/rust/warp/tests/reply_with.rs @@ -0,0 +1,64 @@ +#![deny(warnings)] +use warp::http::header::{HeaderMap, HeaderValue}; +use warp::Filter; + +#[tokio::test] +async fn header() { + let header = warp::reply::with::header("foo", "bar"); + + let no_header = warp::any().map(warp::reply).with(&header); + + let req = warp::test::request(); + let resp = req.reply(&no_header).await; + assert_eq!(resp.headers()["foo"], "bar"); + + let prev_header = warp::reply::with::header("foo", "sean"); + let yes_header = warp::any().map(warp::reply).with(prev_header).with(header); + + let req = warp::test::request(); + let resp = req.reply(&yes_header).await; + assert_eq!(resp.headers()["foo"], "bar", "replaces header"); +} + +#[tokio::test] +async fn headers() { + let mut headers = HeaderMap::new(); + headers.insert("server", HeaderValue::from_static("warp")); + headers.insert("foo", HeaderValue::from_static("bar")); + + let headers = warp::reply::with::headers(headers); + + let no_header = warp::any().map(warp::reply).with(&headers); + + let req = warp::test::request(); + let resp = req.reply(&no_header).await; + assert_eq!(resp.headers()["foo"], "bar"); + assert_eq!(resp.headers()["server"], "warp"); + + let prev_header = warp::reply::with::header("foo", "sean"); + let yes_header = warp::any().map(warp::reply).with(prev_header).with(headers); + + let req = warp::test::request(); + let resp = req.reply(&yes_header).await; + assert_eq!(resp.headers()["foo"], "bar", "replaces header"); +} + +#[tokio::test] +async fn default_header() { + let def_header = warp::reply::with::default_header("foo", "bar"); + + let no_header = warp::any().map(warp::reply).with(&def_header); + + let req = warp::test::request(); + let resp = req.reply(&no_header).await; + + assert_eq!(resp.headers()["foo"], "bar"); + + let header = warp::reply::with::header("foo", "sean"); + let yes_header = warp::any().map(warp::reply).with(header).with(def_header); + + let req = warp::test::request(); + let resp = req.reply(&yes_header).await; + + assert_eq!(resp.headers()["foo"], "sean", "doesn't replace header"); +} diff --git a/third_party/rust/warp/tests/tracing.rs b/third_party/rust/warp/tests/tracing.rs new file mode 100644 index 0000000000..0ae88fbc38 --- /dev/null +++ b/third_party/rust/warp/tests/tracing.rs @@ -0,0 +1,48 @@ +use warp::Filter; + +#[tokio::test] +async fn uses_tracing() { + // Setup a log subscriber (responsible to print to output) + let subscriber = tracing_subscriber::fmt::Subscriber::builder() + .with_env_filter("trace") + .without_time() + .finish(); + + // Set the previously created subscriber as the global subscriber + tracing::subscriber::set_global_default(subscriber).unwrap(); + // Redirect normal log messages to the tracing subscriber + tracing_log::LogTracer::init().unwrap(); + + // Start a span with some metadata (fields) + let span = tracing::info_span!("app", domain = "www.example.org"); + let _guard = span.enter(); + + log::info!("logged using log macro"); + + let ok = warp::any() + .map(|| { + tracing::info!("printed for every request"); + }) + .untuple_one() + .and(warp::path("aa")) + .map(|| { + tracing::info!("only printed when path '/aa' matches"); + }) + .untuple_one() + .map(warp::reply) + // Here we add the tracing logger which will ensure that all requests has a span with + // useful information about the request (method, url, version, remote_addr, etc.) + .with(warp::trace::request()); + + tracing::info!("logged using tracing macro"); + + // Send a request for / + let req = warp::test::request(); + let resp = req.reply(&ok); + assert_eq!(resp.await.status(), 404); + + // Send a request for /aa + let req = warp::test::request().path("/aa"); + let resp = req.reply(&ok); + assert_eq!(resp.await.status(), 200); +} diff --git a/third_party/rust/warp/tests/ws.rs b/third_party/rust/warp/tests/ws.rs new file mode 100644 index 0000000000..d5b60356e4 --- /dev/null +++ b/third_party/rust/warp/tests/ws.rs @@ -0,0 +1,288 @@ +#![deny(warnings)] + +use futures_util::{FutureExt, SinkExt, StreamExt}; +use serde_derive::Deserialize; +use warp::ws::Message; +use warp::Filter; + +#[tokio::test] +async fn upgrade() { + let _ = pretty_env_logger::try_init(); + + let route = warp::ws().map(|ws: warp::ws::Ws| ws.on_upgrade(|_| async {})); + + // From https://tools.ietf.org/html/rfc6455#section-1.2 + let key = "dGhlIHNhbXBsZSBub25jZQ=="; + let accept = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="; + + let resp = warp::test::request() + .header("connection", "upgrade") + .header("upgrade", "websocket") + .header("sec-websocket-version", "13") + .header("sec-websocket-key", key) + .reply(&route) + .await; + + assert_eq!(resp.status(), 101); + assert_eq!(resp.headers()["connection"], "upgrade"); + assert_eq!(resp.headers()["upgrade"], "websocket"); + assert_eq!(resp.headers()["sec-websocket-accept"], accept); + + let resp = warp::test::request() + .header("connection", "keep-alive, Upgrade") + .header("upgrade", "Websocket") + .header("sec-websocket-version", "13") + .header("sec-websocket-key", key) + .reply(&route) + .await; + + assert_eq!(resp.status(), 101); +} + +#[tokio::test] +async fn fail() { + let _ = pretty_env_logger::try_init(); + + let route = warp::any().map(warp::reply); + + warp::test::ws() + .handshake(route) + .await + .expect_err("handshake non-websocket route should fail"); +} + +#[tokio::test] +async fn text() { + let _ = pretty_env_logger::try_init(); + + let mut client = warp::test::ws() + .handshake(ws_echo()) + .await + .expect("handshake"); + + client.send_text("hello warp").await; + + let msg = client.recv().await.expect("recv"); + assert_eq!(msg.to_str(), Ok("hello warp")); +} + +#[tokio::test] +async fn binary() { + let _ = pretty_env_logger::try_init(); + + let mut client = warp::test::ws() + .handshake(ws_echo()) + .await + .expect("handshake"); + + client.send(warp::ws::Message::binary(&b"bonk"[..])).await; + let msg = client.recv().await.expect("recv"); + assert!(msg.is_binary()); + assert_eq!(msg.as_bytes(), &b"bonk"[..]); +} + +#[tokio::test] +async fn wsclient_sink_and_stream() { + let _ = pretty_env_logger::try_init(); + + let mut client = warp::test::ws() + .handshake(ws_echo()) + .await + .expect("handshake"); + + let message = warp::ws::Message::text("hello"); + SinkExt::send(&mut client, message.clone()).await.unwrap(); + let received_message = client.next().await.unwrap().unwrap(); + assert_eq!(message, received_message); +} + +#[tokio::test] +async fn close_frame() { + let _ = pretty_env_logger::try_init(); + + let route = warp::ws().map(|ws: warp::ws::Ws| { + ws.on_upgrade(|mut websocket| async move { + let msg = websocket.next().await.expect("item").expect("ok"); + let _ = msg.close_frame().expect("close frame"); + }) + }); + + let client = warp::test::ws().handshake(route).await.expect("handshake"); + drop(client); +} + +#[tokio::test] +async fn send_ping() { + let _ = pretty_env_logger::try_init(); + + let filter = warp::ws().map(|ws: warp::ws::Ws| { + ws.on_upgrade(|mut websocket| { + async move { + websocket.send(Message::ping("srv")).await.unwrap(); + // assume the client will pong back + let msg = websocket.next().await.expect("item").expect("ok"); + assert!(msg.is_pong()); + assert_eq!(msg.as_bytes(), &b"srv"[..]); + } + }) + }); + + let mut client = warp::test::ws().handshake(filter).await.expect("handshake"); + + let msg = client.recv().await.expect("recv"); + assert!(msg.is_ping()); + assert_eq!(msg.as_bytes(), &b"srv"[..]); + + client.recv_closed().await.expect("closed"); +} + +#[tokio::test] +async fn echo_pings() { + let _ = pretty_env_logger::try_init(); + + let mut client = warp::test::ws() + .handshake(ws_echo()) + .await + .expect("handshake"); + + client.send(Message::ping("clt")).await; + + // tungstenite sends the PONG first + let msg = client.recv().await.expect("recv"); + assert!(msg.is_pong()); + assert_eq!(msg.as_bytes(), &b"clt"[..]); + + // and then `ws_echo` sends us back the same PING + let msg = client.recv().await.expect("recv"); + assert!(msg.is_ping()); + assert_eq!(msg.as_bytes(), &b"clt"[..]); + + // and then our client would have sent *its* PONG + // and `ws_echo` would send *that* back too + let msg = client.recv().await.expect("recv"); + assert!(msg.is_pong()); + assert_eq!(msg.as_bytes(), &b"clt"[..]); +} + +#[tokio::test] +async fn pongs_only() { + let _ = pretty_env_logger::try_init(); + + let mut client = warp::test::ws() + .handshake(ws_echo()) + .await + .expect("handshake"); + + // construct a pong message and make sure it is correct + let msg = Message::pong("clt"); + assert!(msg.is_pong()); + assert_eq!(msg.as_bytes(), &b"clt"[..]); + + // send it to echo and wait for `ws_echo` to send it back + client.send(msg).await; + + let msg = client.recv().await.expect("recv"); + assert!(msg.is_pong()); + assert_eq!(msg.as_bytes(), &b"clt"[..]); +} + +#[tokio::test] +async fn closed() { + let _ = pretty_env_logger::try_init(); + + let route = + warp::ws().map(|ws: warp::ws::Ws| ws.on_upgrade(|websocket| websocket.close().map(|_| ()))); + + let mut client = warp::test::ws().handshake(route).await.expect("handshake"); + + client.recv_closed().await.expect("closed"); +} + +#[tokio::test] +async fn limit_message_size() { + let _ = pretty_env_logger::try_init(); + + let echo = warp::ws().map(|ws: warp::ws::Ws| { + ws.max_message_size(1024).on_upgrade(|websocket| { + // Just echo all messages back... + let (tx, rx) = websocket.split(); + rx.forward(tx).map(|result| { + assert!(result.is_err()); + assert_eq!( + format!("{}", result.unwrap_err()).as_str(), + "Space limit exceeded: Message too big: 0 + 1025 > 1024" + ); + }) + }) + }); + let mut client = warp::test::ws().handshake(echo).await.expect("handshake"); + + client.send(warp::ws::Message::binary(vec![0; 1025])).await; + client.send_text("hello warp").await; + assert!(client.recv().await.is_err()); +} + +#[tokio::test] +async fn limit_frame_size() { + let _ = pretty_env_logger::try_init(); + + let echo = warp::ws().map(|ws: warp::ws::Ws| { + ws.max_frame_size(1024).on_upgrade(|websocket| { + // Just echo all messages back... + let (tx, rx) = websocket.split(); + rx.forward(tx).map(|result| { + assert!(result.is_err()); + assert_eq!( + format!("{}", result.unwrap_err()).as_str(), + "Space limit exceeded: Message length too big: 1025 > 1024" + ); + }) + }) + }); + let mut client = warp::test::ws().handshake(echo).await.expect("handshake"); + + client.send(warp::ws::Message::binary(vec![0; 1025])).await; + client.send_text("hello warp").await; + assert!(client.recv().await.is_err()); +} + +#[derive(Deserialize)] +struct MyQuery { + hello: String, +} + +#[tokio::test] +async fn ws_with_query() { + let ws_filter = warp::path("my-ws") + .and(warp::query::<MyQuery>()) + .and(warp::ws()) + .map(|query: MyQuery, ws: warp::ws::Ws| { + assert_eq!(query.hello, "world"); + + ws.on_upgrade(|websocket| { + let (tx, rx) = websocket.split(); + rx.inspect(|i| log::debug!("ws recv: {:?}", i)) + .forward(tx) + .map(|_| ()) + }) + }); + + warp::test::ws() + .path("/my-ws?hello=world") + .handshake(ws_filter) + .await + .expect("handshake"); +} + +// Websocket filter that echoes all messages back. +fn ws_echo() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Copy { + warp::ws().map(|ws: warp::ws::Ws| { + ws.on_upgrade(|websocket| { + // Just echo all messages back... + let (tx, rx) = websocket.split(); + rx.inspect(|i| log::debug!("ws recv: {:?}", i)) + .forward(tx) + .map(|_| ()) + }) + }) +} |