From 0f1f28062d944bd22dde25d7b168c01d859b9ae2 Mon Sep 17 00:00:00 2001
From: David Pedersen <david.pdrsn@gmail.com>
Date: Wed, 3 Nov 2021 10:22:31 +0100
Subject: [PATCH] Reorganize tests (#456)

* Reorganize tests

This breaks up the large `crate::tests` module by moving some of the
tests into a place that makes more sense. For example tests of JSON
serialization are moved to the `crate::json` module. The remaining routing
tests have been moved to `crate::routing::tests`.

I generally prefer having tests close to the code they're testing. Makes
it easier to see how/if something is tested.

* Try pinning to older version of async-graphql

* Revert "Try pinning to older version of async-graphql"

This reverts commit 2e2cae7d12f5e433a16d6607497d587863f04384.

* don't test examples on 1.54 on CI

* move ci steps around a bit
---
 .github/workflows/CI.yml                  |  25 +-
 src/body/stream_body.rs                   |   6 +-
 src/error_handling/mod.rs                 |   2 +-
 src/extract/connect_info.rs               |   2 +-
 src/extract/content_length_limit.rs       |  57 ++++
 src/extract/extractor_middleware.rs       |  56 +++-
 src/extract/matched_path.rs               |  97 +++++++
 src/extract/mod.rs                        |  17 ++
 src/extract/path/mod.rs                   |  48 +++-
 src/extract/request_parts.rs              |   6 +-
 src/extract/typed_header.rs               |   2 +-
 src/handler/into_service.rs               |   2 +-
 src/handler/mod.rs                        |  32 ++-
 src/json.rs                               |  66 +++++
 src/lib.rs                                |   2 +-
 src/routing/into_make_service.rs          |   2 +-
 src/routing/method_not_allowed.rs         |   2 +-
 src/routing/mod.rs                        |   5 +-
 src/routing/route.rs                      |   2 +-
 src/routing/service_method_routing.rs     |   2 +-
 src/{ => routing}/tests/fallback.rs       |   0
 src/{ => routing}/tests/get_to_head.rs    |   0
 src/{ => routing}/tests/handle_error.rs   |   0
 src/{ => routing}/tests/merge.rs          |   0
 src/{ => routing}/tests/mod.rs            | 330 +---------------------
 src/{ => routing}/tests/nest.rs           |   0
 src/{tests/helpers.rs => test_helpers.rs} |  25 +-
 27 files changed, 427 insertions(+), 361 deletions(-)
 rename src/{ => routing}/tests/fallback.rs (100%)
 rename src/{ => routing}/tests/get_to_head.rs (100%)
 rename src/{ => routing}/tests/handle_error.rs (100%)
 rename src/{ => routing}/tests/merge.rs (100%)
 rename src/{ => routing}/tests/mod.rs (60%)
 rename src/{ => routing}/tests/nest.rs (100%)
 rename src/{tests/helpers.rs => test_helpers.rs} (86%)

diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index 2c307695..512af468 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -64,12 +64,11 @@ jobs:
       run: cargo hack check --each-feature --no-dev-deps --all
 
   test-versions:
-    # Test against the stable, beta, and nightly Rust toolchains on ubuntu-latest.
     needs: check
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        rust: [stable, beta, nightly, 1.54]
+        rust: [stable, beta, nightly]
     steps:
     - uses: actions/checkout@master
     - uses: actions-rs/toolchain@v1
@@ -84,6 +83,28 @@ jobs:
         command: test
         args: --all --all-features --all-targets
 
+  # some examples doesn't support 1.54 (such as async-graphql)
+  # so we only test axum itself on 1.54
+  test-msrv:
+    needs: check
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        rust: [1.54]
+    steps:
+    - uses: actions/checkout@master
+    - uses: actions-rs/toolchain@v1
+      with:
+        toolchain: ${{ matrix.rust }}
+        override: true
+        profile: minimal
+    - uses: Swatinem/rust-cache@v1
+    - name: Run tests
+      uses: actions-rs/cargo@v1
+      with:
+        command: test
+        args: -p axum --all-features --all-targets
+
   test-docs:
     needs: check
     runs-on: ubuntu-latest
diff --git a/src/body/stream_body.rs b/src/body/stream_body.rs
index b9e360d7..616cc7e9 100644
--- a/src/body/stream_body.rs
+++ b/src/body/stream_body.rs
@@ -132,7 +132,7 @@ fn stream_body_traits() {
 
     type EmptyStream = StreamBody<Empty<Result<Bytes, BoxError>>>;
 
-    crate::tests::assert_send::<EmptyStream>();
-    crate::tests::assert_sync::<EmptyStream>();
-    crate::tests::assert_unpin::<EmptyStream>();
+    crate::test_helpers::assert_send::<EmptyStream>();
+    crate::test_helpers::assert_sync::<EmptyStream>();
+    crate::test_helpers::assert_unpin::<EmptyStream>();
 }
diff --git a/src/error_handling/mod.rs b/src/error_handling/mod.rs
index 54456dad..f97bcdc0 100644
--- a/src/error_handling/mod.rs
+++ b/src/error_handling/mod.rs
@@ -203,7 +203,7 @@ pub mod future {
 
 #[test]
 fn traits() {
-    use crate::tests::*;
+    use crate::test_helpers::*;
 
     assert_send::<HandleError<(), (), NotSendSync>>();
     assert_sync::<HandleError<(), (), NotSendSync>>();
diff --git a/src/extract/connect_info.rs b/src/extract/connect_info.rs
index 18b419d8..9fcd18e0 100644
--- a/src/extract/connect_info.rs
+++ b/src/extract/connect_info.rs
@@ -32,7 +32,7 @@ pub struct IntoMakeServiceWithConnectInfo<S, C> {
 
 #[test]
 fn traits() {
-    use crate::tests::*;
+    use crate::test_helpers::*;
     assert_send::<IntoMakeServiceWithConnectInfo<(), NotSendSync>>();
 }
 
diff --git a/src/extract/content_length_limit.rs b/src/extract/content_length_limit.rs
index fc8cb8f4..fe8961d4 100644
--- a/src/extract/content_length_limit.rs
+++ b/src/extract/content_length_limit.rs
@@ -74,3 +74,60 @@ impl<T, const N: u64> Deref for ContentLengthLimit<T, N> {
         &self.0
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::{routing::post, test_helpers::*, Router};
+    use bytes::Bytes;
+    use http::StatusCode;
+    use serde::Deserialize;
+
+    #[tokio::test]
+    async fn body_with_length_limit() {
+        use std::iter::repeat;
+
+        #[derive(Debug, Deserialize)]
+        struct Input {
+            foo: String,
+        }
+
+        const LIMIT: u64 = 8;
+
+        let app = Router::new().route(
+            "/",
+            post(|_body: ContentLengthLimit<Bytes, LIMIT>| async {}),
+        );
+
+        let client = TestClient::new(app);
+        let res = client
+            .post("/")
+            .body(repeat(0_u8).take((LIMIT - 1) as usize).collect::<Vec<_>>())
+            .send()
+            .await;
+        assert_eq!(res.status(), StatusCode::OK);
+
+        let res = client
+            .post("/")
+            .body(repeat(0_u8).take(LIMIT as usize).collect::<Vec<_>>())
+            .send()
+            .await;
+        assert_eq!(res.status(), StatusCode::OK);
+
+        let res = client
+            .post("/")
+            .body(repeat(0_u8).take((LIMIT + 1) as usize).collect::<Vec<_>>())
+            .send()
+            .await;
+        assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE);
+
+        let res = client
+            .post("/")
+            .body(reqwest::Body::wrap_stream(futures_util::stream::iter(
+                vec![Ok::<_, std::io::Error>(bytes::Bytes::new())],
+            )))
+            .send()
+            .await;
+        assert_eq!(res.status(), StatusCode::LENGTH_REQUIRED);
+    }
+}
diff --git a/src/extract/extractor_middleware.rs b/src/extract/extractor_middleware.rs
index 5a68f802..e7aeee79 100644
--- a/src/extract/extractor_middleware.rs
+++ b/src/extract/extractor_middleware.rs
@@ -120,7 +120,7 @@ pub struct ExtractorMiddleware<S, E> {
 
 #[test]
 fn traits() {
-    use crate::tests::*;
+    use crate::test_helpers::*;
     assert_send::<ExtractorMiddleware<(), NotSendSync>>();
     assert_sync::<ExtractorMiddleware<(), NotSendSync>>();
 }
@@ -250,3 +250,57 @@ where
         }
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::{handler::Handler, routing::get, test_helpers::*, Router};
+    use http::StatusCode;
+
+    #[tokio::test]
+    async fn test_extractor_middleware() {
+        struct RequireAuth;
+
+        #[async_trait::async_trait]
+        impl<B> FromRequest<B> for RequireAuth
+        where
+            B: Send,
+        {
+            type Rejection = StatusCode;
+
+            async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
+                if let Some(auth) = req
+                    .headers()
+                    .expect("headers already extracted")
+                    .get("authorization")
+                    .and_then(|v| v.to_str().ok())
+                {
+                    if auth == "secret" {
+                        return Ok(Self);
+                    }
+                }
+
+                Err(StatusCode::UNAUTHORIZED)
+            }
+        }
+
+        async fn handler() {}
+
+        let app = Router::new().route(
+            "/",
+            get(handler.layer(extractor_middleware::<RequireAuth>())),
+        );
+
+        let client = TestClient::new(app);
+
+        let res = client.get("/").send().await;
+        assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
+
+        let res = client
+            .get("/")
+            .header(http::header::AUTHORIZATION, "secret")
+            .send()
+            .await;
+        assert_eq!(res.status(), StatusCode::OK);
+    }
+}
diff --git a/src/extract/matched_path.rs b/src/extract/matched_path.rs
index 80947612..7218393b 100644
--- a/src/extract/matched_path.rs
+++ b/src/extract/matched_path.rs
@@ -84,3 +84,100 @@ where
         Ok(matched_path)
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::{extract::Extension, handler::Handler, routing::get, test_helpers::*, Router};
+    use http::Request;
+    use std::task::{Context, Poll};
+    use tower_service::Service;
+
+    #[derive(Clone)]
+    struct SetMatchedPathExtension<S>(S);
+
+    impl<B, S> Service<Request<B>> for SetMatchedPathExtension<S>
+    where
+        S: Service<Request<B>>,
+    {
+        type Response = S::Response;
+        type Error = S::Error;
+        type Future = S::Future;
+
+        fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
+            self.0.poll_ready(cx)
+        }
+
+        fn call(&mut self, mut req: Request<B>) -> Self::Future {
+            let path = req
+                .extensions()
+                .get::<MatchedPath>()
+                .unwrap()
+                .as_str()
+                .to_string();
+            req.extensions_mut().insert(MatchedPathFromMiddleware(path));
+            self.0.call(req)
+        }
+    }
+
+    #[derive(Clone)]
+    struct MatchedPathFromMiddleware(String);
+
+    #[tokio::test]
+    async fn access_matched_path() {
+        let api = Router::new().route(
+            "/users/:id",
+            get(|path: MatchedPath| async move { path.as_str().to_string() }),
+        );
+
+        async fn handler(
+            path: MatchedPath,
+            Extension(MatchedPathFromMiddleware(path_from_middleware)): Extension<
+                MatchedPathFromMiddleware,
+            >,
+        ) -> String {
+            format!(
+                "extractor = {}, middleware = {}",
+                path.as_str(),
+                path_from_middleware
+            )
+        }
+
+        let app = Router::new()
+            .route(
+                "/:key",
+                get(|path: MatchedPath| async move { path.as_str().to_string() }),
+            )
+            .nest("/api", api)
+            .nest(
+                "/public",
+                Router::new().route("/assets/*path", get(handler)),
+            )
+            .nest("/foo", handler.into_service())
+            .layer(tower::layer::layer_fn(SetMatchedPathExtension));
+
+        let client = TestClient::new(app);
+
+        let res = client.get("/foo").send().await;
+        assert_eq!(res.text().await, "/:key");
+
+        let res = client.get("/api/users/123").send().await;
+        assert_eq!(res.text().await, "/api/users/:id");
+
+        let res = client.get("/public/assets/css/style.css").send().await;
+        assert_eq!(
+            res.text().await,
+            "extractor = /public/assets/*path, middleware = /public/assets/*path"
+        );
+
+        let res = client.get("/foo/bar/baz").send().await;
+        assert_eq!(
+            res.text().await,
+            format!(
+                "extractor = /foo/*{}, middleware = /foo/*{}",
+                crate::routing::NEST_TAIL_PARAM,
+                crate::routing::NEST_TAIL_PARAM,
+            ),
+        );
+    }
+}
diff --git a/src/extract/mod.rs b/src/extract/mod.rs
index 6640d74c..4c9c9ad7 100644
--- a/src/extract/mod.rs
+++ b/src/extract/mod.rs
@@ -356,3 +356,20 @@ pub(crate) fn has_content_type<B>(
 pub(crate) fn take_body<B>(req: &mut RequestParts<B>) -> Result<B, BodyAlreadyExtracted> {
     req.take_body().ok_or(BodyAlreadyExtracted)
 }
+
+#[cfg(test)]
+mod tests {
+    use crate::test_helpers::*;
+    use crate::{routing::get, Router};
+
+    #[tokio::test]
+    async fn consume_body() {
+        let app = Router::new().route("/", get(|body: String| async { body }));
+
+        let client = TestClient::new(app);
+        let res = client.get("/").body("foo").send().await;
+        let body = res.text().await;
+
+        assert_eq!(body, "foo");
+    }
+}
diff --git a/src/extract/path/mod.rs b/src/extract/path/mod.rs
index e74b977b..d7313050 100644
--- a/src/extract/path/mod.rs
+++ b/src/extract/path/mod.rs
@@ -173,11 +173,44 @@ where
 
 #[cfg(test)]
 mod tests {
+    use http::StatusCode;
+
     use super::*;
-    use crate::tests::*;
+    use crate::test_helpers::*;
     use crate::{routing::get, Router};
     use std::collections::HashMap;
 
+    #[tokio::test]
+    async fn extracting_url_params() {
+        let app = Router::new().route(
+            "/users/:id",
+            get(|Path(id): Path<i32>| async move {
+                assert_eq!(id, 42);
+            })
+            .post(|Path(params_map): Path<HashMap<String, i32>>| async move {
+                assert_eq!(params_map.get("id").unwrap(), &1337);
+            }),
+        );
+
+        let client = TestClient::new(app);
+
+        let res = client.get("/users/42").send().await;
+        assert_eq!(res.status(), StatusCode::OK);
+
+        let res = client.post("/users/1337").send().await;
+        assert_eq!(res.status(), StatusCode::OK);
+    }
+
+    #[tokio::test]
+    async fn extracting_url_params_multiple_times() {
+        let app = Router::new().route("/users/:id", get(|_: Path<i32>, _: Path<String>| async {}));
+
+        let client = TestClient::new(app);
+
+        let res = client.get("/users/42").send().await;
+        assert_eq!(res.status(), StatusCode::OK);
+    }
+
     #[tokio::test]
     async fn percent_decoding() {
         let app = Router::new().route(
@@ -235,4 +268,17 @@ mod tests {
         let res = client.get("/bar/baz/qux").send().await;
         assert_eq!(res.text().await, "/baz/qux");
     }
+
+    #[tokio::test]
+    async fn captures_dont_match_empty_segments() {
+        let app = Router::new().route("/:key", get(|| async {}));
+
+        let client = TestClient::new(app);
+
+        let res = client.get("/").send().await;
+        assert_eq!(res.status(), StatusCode::NOT_FOUND);
+
+        let res = client.get("/foo").send().await;
+        assert_eq!(res.status(), StatusCode::OK);
+    }
 }
diff --git a/src/extract/request_parts.rs b/src/extract/request_parts.rs
index c76d39c8..0a84dee2 100644
--- a/src/extract/request_parts.rs
+++ b/src/extract/request_parts.rs
@@ -238,8 +238,8 @@ impl fmt::Debug for BodyStream {
 
 #[test]
 fn body_stream_traits() {
-    crate::tests::assert_send::<BodyStream>();
-    crate::tests::assert_sync::<BodyStream>();
+    crate::test_helpers::assert_send::<BodyStream>();
+    crate::test_helpers::assert_sync::<BodyStream>();
 }
 
 /// Extractor that extracts the raw request body.
@@ -326,7 +326,7 @@ where
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::{body::Body, routing::post, tests::*, Router};
+    use crate::{body::Body, routing::post, test_helpers::*, Router};
     use http::StatusCode;
 
     #[tokio::test]
diff --git a/src/extract/typed_header.rs b/src/extract/typed_header.rs
index 7b4abb73..17c037e5 100644
--- a/src/extract/typed_header.rs
+++ b/src/extract/typed_header.rs
@@ -140,7 +140,7 @@ impl std::error::Error for TypedHeaderRejection {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::{response::IntoResponse, routing::get, tests::*, Router};
+    use crate::{response::IntoResponse, routing::get, test_helpers::*, Router};
 
     #[tokio::test]
     async fn typed_header() {
diff --git a/src/handler/into_service.rs b/src/handler/into_service.rs
index ffde1359..c49de9f1 100644
--- a/src/handler/into_service.rs
+++ b/src/handler/into_service.rs
@@ -19,7 +19,7 @@ pub struct IntoService<H, B, T> {
 
 #[test]
 fn traits() {
-    use crate::tests::*;
+    use crate::test_helpers::*;
     assert_send::<IntoService<(), NotSendSync, NotSendSync>>();
     assert_sync::<IntoService<(), NotSendSync, NotSendSync>>();
 }
diff --git a/src/handler/mod.rs b/src/handler/mod.rs
index bbaf6b9b..bbad6d9a 100644
--- a/src/handler/mod.rs
+++ b/src/handler/mod.rs
@@ -384,10 +384,30 @@ impl<S, T> Layered<S, T> {
     }
 }
 
-#[test]
-fn traits() {
-    use crate::routing::MethodRouter;
-    use crate::tests::*;
-    assert_send::<MethodRouter<(), NotSendSync, NotSendSync, ()>>();
-    assert_sync::<MethodRouter<(), NotSendSync, NotSendSync, ()>>();
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::test_helpers::*;
+    use http::StatusCode;
+
+    #[tokio::test]
+    async fn handler_into_service() {
+        async fn handle(body: String) -> impl IntoResponse {
+            format!("you said: {}", body)
+        }
+
+        let client = TestClient::new(handle.into_service());
+
+        let res = client.post("/").body("hi there!").send().await;
+        assert_eq!(res.status(), StatusCode::OK);
+        assert_eq!(res.text().await, "you said: hi there!");
+    }
+
+    #[test]
+    fn traits() {
+        use crate::routing::MethodRouter;
+        use crate::test_helpers::*;
+        assert_send::<MethodRouter<(), NotSendSync, NotSendSync, ()>>();
+        assert_sync::<MethodRouter<(), NotSendSync, NotSendSync, ()>>();
+    }
 }
diff --git a/src/json.rs b/src/json.rs
index 93f9963a..71d65fad 100644
--- a/src/json.rs
+++ b/src/json.rs
@@ -195,3 +195,69 @@ where
         res
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::{routing::post, test_helpers::*, Router};
+    use serde::Deserialize;
+    use serde_json::{json, Value};
+
+    #[tokio::test]
+    async fn deserialize_body() {
+        #[derive(Debug, Deserialize)]
+        struct Input {
+            foo: String,
+        }
+
+        let app = Router::new().route("/", post(|input: Json<Input>| async { input.0.foo }));
+
+        let client = TestClient::new(app);
+        let res = client.post("/").json(&json!({ "foo": "bar" })).send().await;
+        let body = res.text().await;
+
+        assert_eq!(body, "bar");
+    }
+
+    #[tokio::test]
+    async fn consume_body_to_json_requires_json_content_type() {
+        #[derive(Debug, Deserialize)]
+        struct Input {
+            foo: String,
+        }
+
+        let app = Router::new().route("/", post(|input: Json<Input>| async { input.0.foo }));
+
+        let client = TestClient::new(app);
+        let res = client.post("/").body(r#"{ "foo": "bar" }"#).send().await;
+
+        let status = res.status();
+        dbg!(res.text().await);
+
+        assert_eq!(status, StatusCode::BAD_REQUEST);
+    }
+
+    #[tokio::test]
+    async fn json_content_types() {
+        async fn valid_json_content_type(content_type: &str) -> bool {
+            println!("testing {:?}", content_type);
+
+            let app = Router::new().route("/", post(|Json(_): Json<Value>| async {}));
+
+            let res = TestClient::new(app)
+                .post("/")
+                .header("content-type", content_type)
+                .body("{}")
+                .send()
+                .await;
+
+            res.status() == StatusCode::OK
+        }
+
+        assert!(valid_json_content_type("application/json").await);
+        assert!(valid_json_content_type("application/json; charset=utf-8").await);
+        assert!(valid_json_content_type("application/json;charset=utf-8").await);
+        assert!(valid_json_content_type("application/cloudevents+json").await);
+        assert!(!valid_json_content_type("text/json").await);
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 486afc92..b582a529 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -325,7 +325,7 @@ pub mod response;
 pub mod routing;
 
 #[cfg(test)]
-mod tests;
+mod test_helpers;
 
 pub use add_extension::{AddExtension, AddExtensionLayer};
 #[doc(no_inline)]
diff --git a/src/routing/into_make_service.rs b/src/routing/into_make_service.rs
index 142cf9f8..fbc57c4a 100644
--- a/src/routing/into_make_service.rs
+++ b/src/routing/into_make_service.rs
@@ -50,7 +50,7 @@ mod tests {
 
     #[test]
     fn traits() {
-        use crate::tests::*;
+        use crate::test_helpers::*;
 
         assert_send::<IntoMakeService<Body>>();
     }
diff --git a/src/routing/method_not_allowed.rs b/src/routing/method_not_allowed.rs
index a1d47665..6f812b11 100644
--- a/src/routing/method_not_allowed.rs
+++ b/src/routing/method_not_allowed.rs
@@ -74,7 +74,7 @@ mod tests {
 
     #[test]
     fn traits() {
-        use crate::tests::*;
+        use crate::test_helpers::*;
 
         assert_send::<MethodNotAllowed<NotSendSync>>();
         assert_sync::<MethodNotAllowed<NotSendSync>>();
diff --git a/src/routing/mod.rs b/src/routing/mod.rs
index 4c5efadb..e856127d 100644
--- a/src/routing/mod.rs
+++ b/src/routing/mod.rs
@@ -37,6 +37,9 @@ mod not_found;
 mod route;
 mod strip_prefix;
 
+#[cfg(tests)]
+mod tests;
+
 pub use self::{
     into_make_service::IntoMakeService, method_filter::MethodFilter,
     method_not_allowed::MethodNotAllowed, route::Route,
@@ -574,6 +577,6 @@ where
 
 #[test]
 fn traits() {
-    use crate::tests::*;
+    use crate::test_helpers::*;
     assert_send::<Router<()>>();
 }
diff --git a/src/routing/route.rs b/src/routing/route.rs
index 9e7f661b..82fc1447 100644
--- a/src/routing/route.rs
+++ b/src/routing/route.rs
@@ -95,7 +95,7 @@ mod tests {
 
     #[test]
     fn traits() {
-        use crate::tests::*;
+        use crate::test_helpers::*;
         assert_send::<Route<()>>();
     }
 }
diff --git a/src/routing/service_method_routing.rs b/src/routing/service_method_routing.rs
index ccaf6c43..f26dde5c 100644
--- a/src/routing/service_method_routing.rs
+++ b/src/routing/service_method_routing.rs
@@ -553,7 +553,7 @@ where
 
 #[test]
 fn traits() {
-    use crate::tests::*;
+    use crate::test_helpers::*;
 
     assert_send::<MethodRouter<(), (), NotSendSync>>();
     assert_sync::<MethodRouter<(), (), NotSendSync>>();
diff --git a/src/tests/fallback.rs b/src/routing/tests/fallback.rs
similarity index 100%
rename from src/tests/fallback.rs
rename to src/routing/tests/fallback.rs
diff --git a/src/tests/get_to_head.rs b/src/routing/tests/get_to_head.rs
similarity index 100%
rename from src/tests/get_to_head.rs
rename to src/routing/tests/get_to_head.rs
diff --git a/src/tests/handle_error.rs b/src/routing/tests/handle_error.rs
similarity index 100%
rename from src/tests/handle_error.rs
rename to src/routing/tests/handle_error.rs
diff --git a/src/tests/merge.rs b/src/routing/tests/merge.rs
similarity index 100%
rename from src/tests/merge.rs
rename to src/routing/tests/merge.rs
diff --git a/src/tests/mod.rs b/src/routing/tests/mod.rs
similarity index 60%
rename from src/tests/mod.rs
rename to src/routing/tests/mod.rs
index 68bb5079..3a77ddb1 100644
--- a/src/tests/mod.rs
+++ b/src/routing/tests/mod.rs
@@ -1,7 +1,6 @@
-#![allow(clippy::blacklisted_name)]
-
 use crate::error_handling::HandleErrorLayer;
 use crate::extract::{Extension, MatchedPath};
+use crate::test_helpers::*;
 use crate::BoxError;
 use crate::{
     extract::{self, Path},
@@ -31,12 +30,9 @@ use tower::{service_fn, timeout::TimeoutLayer, ServiceBuilder, ServiceExt};
 use tower_http::auth::RequireAuthorizationLayer;
 use tower_service::Service;
 
-pub(crate) use helpers::*;
-
 mod fallback;
 mod get_to_head;
 mod handle_error;
-mod helpers;
 mod merge;
 mod nest;
 
@@ -73,105 +69,6 @@ async fn hello_world() {
     assert_eq!(body, "users#create");
 }
 
-#[tokio::test]
-async fn consume_body() {
-    let app = Router::new().route("/", get(|body: String| async { body }));
-
-    let client = TestClient::new(app);
-    let res = client.get("/").body("foo").send().await;
-    let body = res.text().await;
-
-    assert_eq!(body, "foo");
-}
-
-#[tokio::test]
-async fn deserialize_body() {
-    #[derive(Debug, Deserialize)]
-    struct Input {
-        foo: String,
-    }
-
-    let app = Router::new().route(
-        "/",
-        post(|input: extract::Json<Input>| async { input.0.foo }),
-    );
-
-    let client = TestClient::new(app);
-    let res = client.post("/").json(&json!({ "foo": "bar" })).send().await;
-    let body = res.text().await;
-
-    assert_eq!(body, "bar");
-}
-
-#[tokio::test]
-async fn consume_body_to_json_requires_json_content_type() {
-    #[derive(Debug, Deserialize)]
-    struct Input {
-        foo: String,
-    }
-
-    let app = Router::new().route(
-        "/",
-        post(|input: extract::Json<Input>| async { input.0.foo }),
-    );
-
-    let client = TestClient::new(app);
-    let res = client.post("/").body(r#"{ "foo": "bar" }"#).send().await;
-
-    let status = res.status();
-    dbg!(res.text().await);
-
-    assert_eq!(status, StatusCode::BAD_REQUEST);
-}
-
-#[tokio::test]
-async fn body_with_length_limit() {
-    use std::iter::repeat;
-
-    #[derive(Debug, Deserialize)]
-    struct Input {
-        foo: String,
-    }
-
-    const LIMIT: u64 = 8;
-
-    let app = Router::new().route(
-        "/",
-        post(|_body: extract::ContentLengthLimit<Bytes, LIMIT>| async {}),
-    );
-
-    let client = TestClient::new(app);
-    let res = client
-        .post("/")
-        .body(repeat(0_u8).take((LIMIT - 1) as usize).collect::<Vec<_>>())
-        .send()
-        .await;
-    assert_eq!(res.status(), StatusCode::OK);
-
-    let res = client
-        .post("/")
-        .body(repeat(0_u8).take(LIMIT as usize).collect::<Vec<_>>())
-        .send()
-        .await;
-    assert_eq!(res.status(), StatusCode::OK);
-
-    let res = client
-        .post("/")
-        .body(repeat(0_u8).take((LIMIT + 1) as usize).collect::<Vec<_>>())
-        .send()
-        .await;
-    assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE);
-
-    let res = client
-        .post("/")
-        .body(reqwest::Body::wrap_stream(futures_util::stream::iter(
-            vec![Ok::<_, std::io::Error>(bytes::Bytes::new())],
-        )))
-        .send()
-        .await;
-    assert_eq!(res.status(), StatusCode::LENGTH_REQUIRED);
-}
-
 #[tokio::test]
 async fn routing() {
     let app = Router::new()
@@ -209,42 +106,8 @@ async fn routing() {
 }
 
 #[tokio::test]
-async fn extracting_url_params() {
-    let app = Router::new().route(
-        "/users/:id",
-        get(|Path(id): Path<i32>| async move {
-            assert_eq!(id, 42);
-        })
-        .post(|Path(params_map): Path<HashMap<String, i32>>| async move {
-            assert_eq!(params_map.get("id").unwrap(), &1337);
-        }),
-    );
-
-    let client = TestClient::new(app);
-
-    let res = client.get("/users/42").send().await;
-    assert_eq!(res.status(), StatusCode::OK);
-
-    let res = client.post("/users/1337").send().await;
-    assert_eq!(res.status(), StatusCode::OK);
-}
-
-#[tokio::test]
-async fn extracting_url_params_multiple_times() {
-    let app = Router::new().route(
-        "/users/:id",
-        get(|_: extract::Path<i32>, _: extract::Path<String>| async {}),
-    );
-
-    let client = TestClient::new(app);
-
-    let res = client.get("/users/42").send().await;
-    assert_eq!(res.status(), StatusCode::OK);
-}
-
-#[tokio::test]
-async fn boxing() {
-    let app = Router::new()
+async fn router_type_doesnt_change() {
+    let app: Router = Router::new()
         .route(
             "/",
             on(MethodFilter::GET, |_: Request<Body>| async {
@@ -351,49 +214,6 @@ async fn service_in_bottom() {
     TestClient::new(app);
 }
 
-#[tokio::test]
-async fn test_extractor_middleware() {
-    struct RequireAuth;
-
-    #[async_trait::async_trait]
-    impl<B> extract::FromRequest<B> for RequireAuth
-    where
-        B: Send,
-    {
-        type Rejection = StatusCode;
-
-        async fn from_request(req: &mut extract::RequestParts<B>) -> Result<Self, Self::Rejection> {
-            if let Some(auth) = req
-                .headers()
-                .expect("headers already extracted")
-                .get("authorization")
-                .and_then(|v| v.to_str().ok())
-            {
-                if auth == "secret" {
-                    return Ok(Self);
-                }
-            }
-
-            Err(StatusCode::UNAUTHORIZED)
-        }
-    }
-
-    async fn handler() {}
-
-    let app = Router::new().route(
-        "/",
-        get(handler.layer(extract::extractor_middleware::<RequireAuth>())),
-    );
-
-    let client = TestClient::new(app);
-
-    let res = client.get("/").send().await;
-    assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
-
-    let res = client.get("/").header(AUTHORIZATION, "secret").send().await;
-    assert_eq!(res.status(), StatusCode::OK);
-}
-
 #[tokio::test]
 async fn wrong_method_handler() {
     let app = Router::new()
@@ -470,56 +290,6 @@ async fn multiple_methods_for_one_handler() {
     assert_eq!(res.status(), StatusCode::OK);
 }
 
-#[tokio::test]
-async fn handler_into_service() {
-    async fn handle(body: String) -> impl IntoResponse {
-        format!("you said: {}", body)
-    }
-
-    let client = TestClient::new(handle.into_service());
-
-    let res = client.post("/").body("hi there!").send().await;
-    assert_eq!(res.status(), StatusCode::OK);
-    assert_eq!(res.text().await, "you said: hi there!");
-}
-
-#[tokio::test]
-async fn captures_dont_match_empty_segments() {
-    let app = Router::new().route("/:key", get(|| async {}));
-
-    let client = TestClient::new(app);
-
-    let res = client.get("/").send().await;
-    assert_eq!(res.status(), StatusCode::NOT_FOUND);
-
-    let res = client.get("/foo").send().await;
-    assert_eq!(res.status(), StatusCode::OK);
-}
-
-#[tokio::test]
-async fn json_content_types() {
-    async fn valid_json_content_type(content_type: &str) -> bool {
-        println!("testing {:?}", content_type);
-
-        let app = Router::new().route("/", post(|Json(_): Json<Value>| async {}));
-
-        let res = TestClient::new(app)
-            .post("/")
-            .header("content-type", content_type)
-            .body("{}")
-            .send()
-            .await;
-
-        res.status() == StatusCode::OK
-    }
-
-    assert!(valid_json_content_type("application/json").await);
-    assert!(valid_json_content_type("application/json; charset=utf-8").await);
-    assert!(valid_json_content_type("application/json;charset=utf-8").await);
-    assert!(valid_json_content_type("application/cloudevents+json").await);
-    assert!(!valid_json_content_type("text/json").await);
-}
-
 #[tokio::test]
 async fn wildcard_sees_whole_url() {
     let app = Router::new().route("/api/*rest", get(|uri: Uri| async move { uri.to_string() }));
@@ -634,94 +404,6 @@ async fn wildcard_with_trailing_slash() {
     );
 }
 
-#[derive(Clone)]
-struct SetMatchedPathExtension<S>(S);
-
-impl<B, S> Service<Request<B>> for SetMatchedPathExtension<S>
-where
-    S: Service<Request<B>>,
-{
-    type Response = S::Response;
-    type Error = S::Error;
-    type Future = S::Future;
-
-    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
-        self.0.poll_ready(cx)
-    }
-
-    fn call(&mut self, mut req: Request<B>) -> Self::Future {
-        let path = req
-            .extensions()
-            .get::<MatchedPath>()
-            .unwrap()
-            .as_str()
-            .to_string();
-        req.extensions_mut().insert(MatchedPathFromMiddleware(path));
-        self.0.call(req)
-    }
-}
-
-#[derive(Clone)]
-struct MatchedPathFromMiddleware(String);
-
-#[tokio::test]
-async fn access_matched_path() {
-    let api = Router::new().route(
-        "/users/:id",
-        get(|path: MatchedPath| async move { path.as_str().to_string() }),
-    );
-
-    async fn handler(
-        path: MatchedPath,
-        Extension(MatchedPathFromMiddleware(path_from_middleware)): Extension<
-            MatchedPathFromMiddleware,
-        >,
-    ) -> String {
-        format!(
-            "extractor = {}, middleware = {}",
-            path.as_str(),
-            path_from_middleware
-        )
-    }
-
-    let app = Router::new()
-        .route(
-            "/:key",
-            get(|path: MatchedPath| async move { path.as_str().to_string() }),
-        )
-        .nest("/api", api)
-        .nest(
-            "/public",
-            Router::new().route("/assets/*path", get(handler)),
-        )
-        .nest("/foo", handler.into_service())
-        .layer(tower::layer::layer_fn(SetMatchedPathExtension));
-
-    let client = TestClient::new(app);
-
-    let res = client.get("/foo").send().await;
-    assert_eq!(res.text().await, "/:key");
-
-    let res = client.get("/api/users/123").send().await;
-    assert_eq!(res.text().await, "/api/users/:id");
-
-    let res = client.get("/public/assets/css/style.css").send().await;
-    assert_eq!(
-        res.text().await,
-        "extractor = /public/assets/*path, middleware = /public/assets/*path"
-    );
-
-    let res = client.get("/foo/bar/baz").send().await;
-    assert_eq!(
-        res.text().await,
-        format!(
-            "extractor = /foo/*{}, middleware = /foo/*{}",
-            crate::routing::NEST_TAIL_PARAM,
-            crate::routing::NEST_TAIL_PARAM,
-        ),
-    );
-}
-
 #[tokio::test]
 async fn static_and_dynamic_paths() {
     let app = Router::new()
@@ -801,9 +483,3 @@ async fn middleware_still_run_for_unmatched_requests() {
 async fn routing_to_router_panics() {
     TestClient::new(Router::new().route("/", Router::new()));
 }
-
-pub(crate) fn assert_send<T: Send>() {}
-pub(crate) fn assert_sync<T: Sync>() {}
-pub(crate) fn assert_unpin<T: Unpin>() {}
-
-pub(crate) struct NotSendSync(*const ());
diff --git a/src/tests/nest.rs b/src/routing/tests/nest.rs
similarity index 100%
rename from src/tests/nest.rs
rename to src/routing/tests/nest.rs
diff --git a/src/tests/helpers.rs b/src/test_helpers.rs
similarity index 86%
rename from src/tests/helpers.rs
rename to src/test_helpers.rs
index bcd3c73d..113f83dd 100644
--- a/src/tests/helpers.rs
+++ b/src/test_helpers.rs
@@ -1,16 +1,22 @@
+#![allow(clippy::blacklisted_name)]
+
 use crate::BoxError;
 use http::{
     header::{HeaderName, HeaderValue},
     Request, StatusCode,
 };
 use hyper::{Body, Server};
-use std::{
-    convert::TryFrom,
-    net::{SocketAddr, TcpListener},
-};
+use std::net::SocketAddr;
+use std::{convert::TryFrom, net::TcpListener};
 use tower::make::Shared;
 use tower_service::Service;
 
+pub(crate) fn assert_send<T: Send>() {}
+pub(crate) fn assert_sync<T: Sync>() {}
+pub(crate) fn assert_unpin<T: Unpin>() {}
+
+pub(crate) struct NotSendSync(*const ());
+
 pub(crate) struct TestClient {
     client: reqwest::Client,
     addr: SocketAddr,
@@ -53,12 +59,14 @@ impl TestClient {
         }
     }
 
+    #[allow(dead_code)]
     pub(crate) fn put(&self, url: &str) -> RequestBuilder {
         RequestBuilder {
             builder: self.client.put(format!("http://{}{}", self.addr, url)),
         }
     }
 
+    #[allow(dead_code)]
     pub(crate) fn patch(&self, url: &str) -> RequestBuilder {
         RequestBuilder {
             builder: self.client.patch(format!("http://{}{}", self.addr, url)),
@@ -71,8 +79,8 @@ pub(crate) struct RequestBuilder {
 }
 
 impl RequestBuilder {
-    pub(crate) async fn send(self) -> Response {
-        Response {
+    pub(crate) async fn send(self) -> TestResponse {
+        TestResponse {
             response: self.builder.send().await.unwrap(),
         }
     }
@@ -101,15 +109,16 @@ impl RequestBuilder {
     }
 }
 
-pub(crate) struct Response {
+pub(crate) struct TestResponse {
     response: reqwest::Response,
 }
 
-impl Response {
+impl TestResponse {
     pub(crate) async fn text(self) -> String {
         self.response.text().await.unwrap()
     }
 
+    #[allow(dead_code)]
     pub(crate) async fn json<T>(self) -> T
     where
         T: serde::de::DeserializeOwned,