Add internal macro to make tests of nest easier to write (#1694)

This commit is contained in:
David Pedersen 2023-01-14 15:12:01 +01:00 committed by GitHub
parent 607a20dfac
commit 1be25d9496
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 230 additions and 159 deletions

View file

@ -11,6 +11,10 @@ readme = "README.md"
repository = "https://github.com/tokio-rs/axum"
version = "0.3.1" # remember to also bump the version that axum and axum-extra depends on
[features]
default = []
__private = ["syn/visit-mut"]
[lib]
proc-macro = true

View file

@ -0,0 +1,45 @@
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{parse::Parse, parse_quote, visit_mut::VisitMut, ItemFn};
pub(crate) fn expand(_attr: Attrs, mut item_fn: ItemFn) -> TokenStream {
item_fn.attrs.push(parse_quote!(#[tokio::test]));
let nest_service_fn = replace_nest_with_nest_service(item_fn.clone());
quote! {
#item_fn
#nest_service_fn
}
}
pub(crate) struct Attrs;
impl Parse for Attrs {
fn parse(_input: syn::parse::ParseStream) -> syn::Result<Self> {
Ok(Self)
}
}
fn replace_nest_with_nest_service(mut item_fn: ItemFn) -> Option<ItemFn> {
item_fn.sig.ident = format_ident!("{}_with_nest_service", item_fn.sig.ident);
let mut visitor = NestToNestService::default();
syn::visit_mut::visit_item_fn_mut(&mut visitor, &mut item_fn);
(visitor.count > 0).then(|| item_fn)
}
#[derive(Default)]
struct NestToNestService {
count: usize,
}
impl VisitMut for NestToNestService {
fn visit_expr_method_call_mut(&mut self, i: &mut syn::ExprMethodCall) {
if i.method == "nest" && i.args.len() == 2 {
i.method = parse_quote!(nest_service);
self.count += 1;
}
}
}

View file

@ -48,6 +48,8 @@ use quote::{quote, ToTokens};
use syn::{parse::Parse, Type};
mod attr_parsing;
#[cfg(feature = "__private")]
mod axum_test;
mod debug_handler;
mod from_ref;
mod from_request;
@ -555,6 +557,22 @@ pub fn debug_handler(_attr: TokenStream, input: TokenStream) -> TokenStream {
return expand_attr_with(_attr, input, debug_handler::expand);
}
/// Private API: Do no use this!
///
/// Attribute macro to be placed on test functions that'll generate two functions:
///
/// 1. One identical to the function it was placed on.
/// 2. One where calls to `Router::nest` has been replaced with `Router::nest_service`
///
/// This makes it easy to that `nest` and `nest_service` behaves in the same way, without having to
/// manually write identical tests for both methods.
#[cfg(feature = "__private")]
#[proc_macro_attribute]
#[doc(hidden)]
pub fn __private_axum_test(_attr: TokenStream, input: TokenStream) -> TokenStream {
expand_attr_with(_attr, input, axum_test::expand)
}
/// Derive an implementation of [`axum_extra::routing::TypedPath`].
///
/// See that trait for more details.

View file

@ -68,6 +68,7 @@ rustversion = "1.0.9"
[dev-dependencies]
anyhow = "1.0"
axum-macros = { path = "../axum-macros", version = "0.3.1", features = ["__private"] }
futures = "0.3"
quickcheck = "1.0"
quickcheck_macros = "1.0"

View file

@ -148,7 +148,7 @@ mod tests {
use crate::{routing::get, Router, Server};
use std::net::{SocketAddr, TcpListener};
#[tokio::test]
#[crate::test]
async fn socket_addr() {
async fn handler(ConnectInfo(addr): ConnectInfo<SocketAddr>) -> String {
format!("{}", addr)
@ -175,7 +175,7 @@ mod tests {
assert!(body.starts_with("127.0.0.1:"));
}
#[tokio::test]
#[crate::test]
async fn custom() {
#[derive(Clone, Debug)]
struct MyConnectInfo {

View file

@ -90,7 +90,7 @@ mod tests {
TestClient::new(Router::new().route("/", get(host_as_body)))
}
#[tokio::test]
#[crate::test]
async fn host_header() {
let original_host = "some-domain:123";
let host = test_client()
@ -103,7 +103,7 @@ mod tests {
assert_eq!(host, original_host);
}
#[tokio::test]
#[crate::test]
async fn x_forwarded_host_header() {
let original_host = "some-domain:456";
let host = test_client()
@ -116,7 +116,7 @@ mod tests {
assert_eq!(host, original_host);
}
#[tokio::test]
#[crate::test]
async fn x_forwarded_host_precedence_over_host_header() {
let x_forwarded_host_header = "some-domain:456";
let host_header = "some-domain:123";
@ -131,7 +131,7 @@ mod tests {
assert_eq!(host, x_forwarded_host_header);
}
#[tokio::test]
#[crate::test]
async fn uri_host() {
let host = test_client().get("/").send().await.text().await;
assert!(host.contains("127.0.0.1"));

View file

@ -177,7 +177,7 @@ mod tests {
};
use http::{Request, StatusCode};
#[tokio::test]
#[crate::test]
async fn extracting_on_handler() {
let app = Router::new().route(
"/:a",
@ -190,7 +190,7 @@ mod tests {
assert_eq!(res.text().await, "/:a");
}
#[tokio::test]
#[crate::test]
async fn extracting_on_handler_in_nested_router() {
let app = Router::new().nest(
"/:a",
@ -206,7 +206,7 @@ mod tests {
assert_eq!(res.text().await, "/:a/:b");
}
#[tokio::test]
#[crate::test]
async fn extracting_on_handler_in_deeply_nested_router() {
let app = Router::new().nest(
"/:a",
@ -225,7 +225,7 @@ mod tests {
assert_eq!(res.text().await, "/:a/:b/:c");
}
#[tokio::test]
#[crate::test]
async fn cannot_extract_nested_matched_path_in_middleware() {
async fn extract_matched_path<B>(
matched_path: Option<MatchedPath>,
@ -245,7 +245,7 @@ mod tests {
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
#[crate::test]
async fn cannot_extract_nested_matched_path_in_middleware_via_extension() {
async fn assert_no_matched_path<B>(req: Request<B>) -> Request<B> {
assert!(req.extensions().get::<MatchedPath>().is_none());
@ -262,7 +262,7 @@ mod tests {
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
#[crate::test]
async fn can_extract_nested_matched_path_in_middleware_on_nested_router() {
async fn extract_matched_path<B>(matched_path: MatchedPath, req: Request<B>) -> Request<B> {
assert_eq!(matched_path.as_str(), "/:a/:b");
@ -282,7 +282,7 @@ mod tests {
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
#[crate::test]
async fn can_extract_nested_matched_path_in_middleware_on_nested_router_via_extension() {
async fn extract_matched_path<B>(req: Request<B>) -> Request<B> {
let matched_path = req.extensions().get::<MatchedPath>().unwrap();
@ -303,7 +303,7 @@ mod tests {
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
#[crate::test]
async fn extracting_on_nested_handler() {
async fn handler(path: Option<MatchedPath>) {
assert!(path.is_none());

View file

@ -102,7 +102,7 @@ pub(super) fn has_content_type(headers: &HeaderMap, expected_content_type: &mime
mod tests {
use crate::{routing::get, test_helpers::*, Router};
#[tokio::test]
#[crate::test]
async fn consume_body() {
let app = Router::new().route("/", get(|body: String| async { body }));

View file

@ -250,7 +250,7 @@ mod tests {
use super::*;
use crate::{body::Body, response::IntoResponse, routing::post, test_helpers::*, Router};
#[tokio::test]
#[crate::test]
async fn content_type_with_encoding() {
const BYTES: &[u8] = "<!doctype html><title>🦀</title>".as_bytes();
const FILE_NAME: &str = "index.html";

View file

@ -440,7 +440,7 @@ mod tests {
use serde::Deserialize;
use std::collections::HashMap;
#[tokio::test]
#[crate::test]
async fn extracting_url_params() {
let app = Router::new().route(
"/users/:id",
@ -461,7 +461,7 @@ mod tests {
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
#[crate::test]
async fn extracting_url_params_multiple_times() {
let app = Router::new().route("/users/:id", get(|_: Path<i32>, _: Path<String>| async {}));
@ -471,7 +471,7 @@ mod tests {
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
#[crate::test]
async fn percent_decoding() {
let app = Router::new().route(
"/:key",
@ -485,7 +485,7 @@ mod tests {
assert_eq!(res.text().await, "one two");
}
#[tokio::test]
#[crate::test]
async fn supports_128_bit_numbers() {
let app = Router::new()
.route(
@ -506,7 +506,7 @@ mod tests {
assert_eq!(res.text().await, "123");
}
#[tokio::test]
#[crate::test]
async fn wildcard() {
let app = Router::new()
.route(
@ -529,7 +529,7 @@ mod tests {
assert_eq!(res.text().await, "baz/qux");
}
#[tokio::test]
#[crate::test]
async fn captures_dont_match_empty_segments() {
let app = Router::new().route("/:key", get(|| async {}));
@ -542,7 +542,7 @@ mod tests {
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
#[crate::test]
async fn str_reference_deserialize() {
struct Param(String);
impl<'de> serde::Deserialize<'de> for Param {
@ -567,7 +567,7 @@ mod tests {
assert_eq!(res.text().await, "foo bar");
}
#[tokio::test]
#[crate::test]
async fn two_path_extractors() {
let app = Router::new().route("/:a/:b", get(|_: Path<String>, _: Path<String>| async {}));
@ -582,7 +582,7 @@ mod tests {
);
}
#[tokio::test]
#[crate::test]
async fn deserialize_into_vec_of_tuples() {
let app = Router::new().route(
"/:a/:b",
@ -603,7 +603,7 @@ mod tests {
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
#[crate::test]
async fn type_that_uses_deserialize_any() {
use time::Date;
@ -694,7 +694,7 @@ mod tests {
assert_eq!(res.text().await, "struct: 2023-01-01 2023-01-02 2023-01-03");
}
#[tokio::test]
#[crate::test]
async fn wrong_number_of_parameters_json() {
use serde_json::Value;

View file

@ -91,7 +91,7 @@ mod tests {
assert_eq!(Query::<T>::from_request(req, &()).await.unwrap().0, value);
}
#[tokio::test]
#[crate::test]
async fn test_query() {
#[derive(Debug, PartialEq, Deserialize)]
struct Pagination {
@ -127,7 +127,7 @@ mod tests {
.await;
}
#[tokio::test]
#[crate::test]
async fn correct_rejection_status_code() {
#[derive(Deserialize)]
#[allow(dead_code)]

View file

@ -92,21 +92,21 @@ mod tests {
assert_eq!(RawForm::from_request(req, &()).await.unwrap().0, body);
}
#[tokio::test]
#[crate::test]
async fn test_from_query() {
check_query("http://example.com/test", b"").await;
check_query("http://example.com/test?page=0&size=10", b"page=0&size=10").await;
}
#[tokio::test]
#[crate::test]
async fn test_from_body() {
check_body(b"").await;
check_body(b"username=user&password=secure%20password").await;
}
#[tokio::test]
#[crate::test]
async fn test_incorrect_content_type() {
let req = Request::post("http://example.com/test")
.body(Full::<Bytes>::from(Bytes::from("page=0&size=10")))

View file

@ -226,7 +226,7 @@ mod tests {
use crate::{extract::Extension, routing::get, test_helpers::*, Router};
use http::{Method, StatusCode};
#[tokio::test]
#[crate::test]
async fn extract_request_parts() {
#[derive(Clone)]
struct Ext;

View file

@ -814,7 +814,7 @@ mod tests {
use http::{Request, Version};
use tower::ServiceExt;
#[tokio::test]
#[crate::test]
async fn rejects_http_1_0_requests() {
let svc = get(|ws: Result<WebSocketUpgrade, WebSocketUpgradeRejection>| {
let rejection = ws.unwrap_err();

View file

@ -149,7 +149,7 @@ mod tests {
assert_eq!(Form::<T>::from_request(req, &()).await.unwrap().0, value);
}
#[tokio::test]
#[crate::test]
async fn test_form_query() {
check_query(
"http://example.com/test",
@ -179,7 +179,7 @@ mod tests {
.await;
}
#[tokio::test]
#[crate::test]
async fn test_form_body() {
check_body(Pagination {
size: None,
@ -200,7 +200,7 @@ mod tests {
.await;
}
#[tokio::test]
#[crate::test]
async fn test_incorrect_content_type() {
let req = Request::builder()
.uri("http://example.com/test")

View file

@ -352,7 +352,7 @@ mod tests {
timeout::TimeoutLayer,
};
#[tokio::test]
#[crate::test]
async fn handler_into_service() {
async fn handle(body: String) -> impl IntoResponse {
format!("you said: {}", body)
@ -365,7 +365,7 @@ mod tests {
assert_eq!(res.text().await, "you said: hi there!");
}
#[tokio::test]
#[crate::test]
async fn with_layer_that_changes_request_body_and_state() {
async fn handle(State(state): State<&'static str>) -> &'static str {
state

View file

@ -226,7 +226,7 @@ mod tests {
use serde::Deserialize;
use serde_json::{json, Value};
#[tokio::test]
#[crate::test]
async fn deserialize_body() {
#[derive(Debug, Deserialize)]
struct Input {
@ -242,7 +242,7 @@ mod tests {
assert_eq!(body, "bar");
}
#[tokio::test]
#[crate::test]
async fn consume_body_to_json_requires_json_content_type() {
#[derive(Debug, Deserialize)]
struct Input {
@ -259,7 +259,7 @@ mod tests {
assert_eq!(status, StatusCode::UNSUPPORTED_MEDIA_TYPE);
}
#[tokio::test]
#[crate::test]
async fn json_content_types() {
async fn valid_json_content_type(content_type: &str) -> bool {
println!("testing {:?}", content_type);
@ -283,7 +283,7 @@ mod tests {
assert!(!valid_json_content_type("text/json").await);
}
#[tokio::test]
#[crate::test]
async fn invalid_json_syntax() {
let app = Router::new().route("/", post(|_: Json<serde_json::Value>| async {}));
@ -314,7 +314,7 @@ mod tests {
y: i32,
}
#[tokio::test]
#[crate::test]
async fn invalid_json_data() {
let app = Router::new().route("/", post(|_: Json<Foo>| async {}));

View file

@ -493,3 +493,6 @@ pub use axum_core::{BoxError, Error, RequestExt, RequestPartsExt};
pub use axum_macros::debug_handler;
pub use self::service_ext::ServiceExt;
#[cfg(test)]
use axum_macros::__private_axum_test as test;

View file

@ -309,7 +309,7 @@ mod tests {
use http::{header, request::Parts, StatusCode};
use tower_http::limit::RequestBodyLimitLayer;
#[tokio::test]
#[crate::test]
async fn test_from_extractor() {
#[derive(Clone)]
struct Secret(&'static str);

View file

@ -372,7 +372,7 @@ mod tests {
use http::{HeaderMap, StatusCode};
use tower::ServiceExt;
#[tokio::test]
#[crate::test]
async fn basic() {
async fn insert_header<B>(mut req: Request<B>, next: Next<B>) -> impl IntoResponse {
req.headers_mut()

View file

@ -385,7 +385,7 @@ mod tests {
use crate::{routing::get, test_helpers::TestClient, Router};
use http::{HeaderMap, StatusCode};
#[tokio::test]
#[crate::test]
async fn works() {
async fn add_header<B>(mut req: Request<B>) -> Request<B> {
req.headers_mut().insert("x-foo", "foo".parse().unwrap());
@ -410,7 +410,7 @@ mod tests {
assert_eq!(res.text().await, "foo");
}
#[tokio::test]
#[crate::test]
async fn works_for_short_circutting() {
async fn add_header<B>(_req: Request<B>) -> Result<Request<B>, (StatusCode, &'static str)> {
Err((StatusCode::INTERNAL_SERVER_ERROR, "something went wrong"))

View file

@ -346,7 +346,7 @@ mod tests {
use super::*;
use crate::{test_helpers::TestClient, Router};
#[tokio::test]
#[crate::test]
async fn works() {
async fn add_header<B>(mut res: Response<B>) -> Response<B> {
res.headers_mut().insert("x-foo", "foo".parse().unwrap());

View file

@ -508,7 +508,7 @@ mod tests {
assert_eq!(&*leading_space.finalize(), b"data: foobar\n\n");
}
#[tokio::test]
#[crate::test]
async fn basic() {
let app = Router::new().route(
"/",

View file

@ -1280,7 +1280,7 @@ mod tests {
use tower::{timeout::TimeoutLayer, Service, ServiceBuilder, ServiceExt};
use tower_http::{auth::RequireAuthorizationLayer, services::fs::ServeDir};
#[tokio::test]
#[crate::test]
async fn method_not_allowed_by_default() {
let mut svc = MethodRouter::new();
let (status, _, body) = call(Method::GET, &mut svc).await;
@ -1288,7 +1288,7 @@ mod tests {
assert!(body.is_empty());
}
#[tokio::test]
#[crate::test]
async fn get_service_fn() {
async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
Ok(Response::new(Body::from("ok")))
@ -1301,7 +1301,7 @@ mod tests {
assert_eq!(body, "ok");
}
#[tokio::test]
#[crate::test]
async fn get_handler() {
let mut svc = MethodRouter::new().get(ok);
let (status, _, body) = call(Method::GET, &mut svc).await;
@ -1309,7 +1309,7 @@ mod tests {
assert_eq!(body, "ok");
}
#[tokio::test]
#[crate::test]
async fn get_accepts_head() {
let mut svc = MethodRouter::new().get(ok);
let (status, _, body) = call(Method::HEAD, &mut svc).await;
@ -1317,7 +1317,7 @@ mod tests {
assert!(body.is_empty());
}
#[tokio::test]
#[crate::test]
async fn head_takes_precedence_over_get() {
let mut svc = MethodRouter::new().head(created).get(ok);
let (status, _, body) = call(Method::HEAD, &mut svc).await;
@ -1325,7 +1325,7 @@ mod tests {
assert!(body.is_empty());
}
#[tokio::test]
#[crate::test]
async fn merge() {
let mut svc = get(ok).merge(post(ok));
@ -1336,7 +1336,7 @@ mod tests {
assert_eq!(status, StatusCode::OK);
}
#[tokio::test]
#[crate::test]
async fn layer() {
let mut svc = MethodRouter::new()
.get(|| async { std::future::pending::<()>().await })
@ -1351,7 +1351,7 @@ mod tests {
assert_eq!(status, StatusCode::UNAUTHORIZED);
}
#[tokio::test]
#[crate::test]
async fn route_layer() {
let mut svc = MethodRouter::new()
.get(|| async { std::future::pending::<()>().await })
@ -1392,7 +1392,7 @@ mod tests {
crate::Server::bind(&"0.0.0.0:0".parse().unwrap()).serve(app.into_make_service());
}
#[tokio::test]
#[crate::test]
async fn sets_allow_header() {
let mut svc = MethodRouter::new().put(ok).patch(ok);
let (status, headers, _) = call(Method::GET, &mut svc).await;
@ -1400,7 +1400,7 @@ mod tests {
assert_eq!(headers[ALLOW], "PUT,PATCH");
}
#[tokio::test]
#[crate::test]
async fn sets_allow_header_get_head() {
let mut svc = MethodRouter::new().get(ok).head(ok);
let (status, headers, _) = call(Method::PUT, &mut svc).await;
@ -1408,7 +1408,7 @@ mod tests {
assert_eq!(headers[ALLOW], "GET,HEAD");
}
#[tokio::test]
#[crate::test]
async fn empty_allow_header_by_default() {
let mut svc = MethodRouter::new();
let (status, headers, _) = call(Method::PATCH, &mut svc).await;
@ -1416,7 +1416,7 @@ mod tests {
assert_eq!(headers[ALLOW], "");
}
#[tokio::test]
#[crate::test]
async fn allow_header_when_merging() {
let a = put(ok).patch(ok);
let b = get(ok).head(ok);
@ -1427,7 +1427,7 @@ mod tests {
assert_eq!(headers[ALLOW], "PUT,PATCH,GET,HEAD");
}
#[tokio::test]
#[crate::test]
async fn allow_header_any() {
let mut svc = any(ok);
@ -1436,7 +1436,7 @@ mod tests {
assert!(!headers.contains_key(ALLOW));
}
#[tokio::test]
#[crate::test]
async fn allow_header_with_fallback() {
let mut svc = MethodRouter::new()
.get(ok)
@ -1447,7 +1447,7 @@ mod tests {
assert_eq!(headers[ALLOW], "GET,HEAD");
}
#[tokio::test]
#[crate::test]
async fn allow_header_with_fallback_that_sets_allow() {
async fn fallback(method: Method) -> Response {
if method == Method::POST {
@ -1475,7 +1475,7 @@ mod tests {
assert_eq!(headers[ALLOW], "GET,POST");
}
#[tokio::test]
#[crate::test]
#[should_panic(
expected = "Overlapping method route. Cannot add two method routes that both handle `GET`"
)]
@ -1483,7 +1483,7 @@ mod tests {
let _: MethodRouter<()> = get(ok).get(ok);
}
#[tokio::test]
#[crate::test]
#[should_panic(
expected = "Overlapping method route. Cannot add two method routes that both handle `POST`"
)]
@ -1491,17 +1491,17 @@ mod tests {
let _: MethodRouter<()> = post_service(ok.into_service()).post_service(ok.into_service());
}
#[tokio::test]
#[crate::test]
async fn get_head_does_not_overlap() {
let _: MethodRouter<()> = get(ok).head(ok);
}
#[tokio::test]
#[crate::test]
async fn head_get_does_not_overlap() {
let _: MethodRouter<()> = head(ok).get(ok);
}
#[tokio::test]
#[crate::test]
async fn accessing_state() {
let mut svc = MethodRouter::new()
.get(|State(state): State<&'static str>| async move { state })
@ -1513,7 +1513,7 @@ mod tests {
assert_eq!(text, "state");
}
#[tokio::test]
#[crate::test]
async fn fallback_accessing_state() {
let mut svc = MethodRouter::new()
.fallback(|State(state): State<&'static str>| async move { state })
@ -1525,7 +1525,7 @@ mod tests {
assert_eq!(text, "state");
}
#[tokio::test]
#[crate::test]
async fn merge_accessing_state() {
let one = get(|State(state): State<&'static str>| async move { state });
let two = post(|State(state): State<&'static str>| async move { state });

View file

@ -3,7 +3,7 @@ use tower::ServiceExt;
use super::*;
use crate::middleware::{map_request, map_response};
#[tokio::test]
#[crate::test]
async fn basic() {
let app = Router::new()
.route("/foo", get(|| async {}))
@ -18,7 +18,7 @@ async fn basic() {
assert_eq!(res.text().await, "fallback");
}
#[tokio::test]
#[crate::test]
async fn nest() {
let app = Router::new()
.nest("/foo", Router::new().route("/bar", get(|| async {})))
@ -33,7 +33,7 @@ async fn nest() {
assert_eq!(res.text().await, "fallback");
}
#[tokio::test]
#[crate::test]
async fn or() {
let one = Router::new().route("/one", get(|| async {}));
let two = Router::new().route("/two", get(|| async {}));
@ -50,7 +50,7 @@ async fn or() {
assert_eq!(res.text().await, "fallback");
}
#[tokio::test]
#[crate::test]
async fn fallback_accessing_state() {
let app = Router::new()
.fallback(|State(state): State<&'static str>| async move { state })
@ -71,7 +71,7 @@ async fn outer_fallback() -> impl IntoResponse {
(StatusCode::NOT_FOUND, "outer")
}
#[tokio::test]
#[crate::test]
async fn nested_router_inherits_fallback() {
let inner = Router::new();
let app = Router::new().nest("/foo", inner).fallback(outer_fallback);
@ -83,7 +83,7 @@ async fn nested_router_inherits_fallback() {
assert_eq!(res.text().await, "outer");
}
#[tokio::test]
#[crate::test]
async fn doesnt_inherit_fallback_if_overriden() {
let inner = Router::new().fallback(inner_fallback);
let app = Router::new().nest("/foo", inner).fallback(outer_fallback);
@ -95,7 +95,7 @@ async fn doesnt_inherit_fallback_if_overriden() {
assert_eq!(res.text().await, "inner");
}
#[tokio::test]
#[crate::test]
async fn deeply_nested_inherit_from_top() {
let app = Router::new()
.nest("/foo", Router::new().nest("/bar", Router::new()))
@ -108,7 +108,7 @@ async fn deeply_nested_inherit_from_top() {
assert_eq!(res.text().await, "outer");
}
#[tokio::test]
#[crate::test]
async fn deeply_nested_inherit_from_middle() {
let app = Router::new().nest(
"/foo",
@ -124,7 +124,7 @@ async fn deeply_nested_inherit_from_middle() {
assert_eq!(res.text().await, "outer");
}
#[tokio::test]
#[crate::test]
async fn with_middleware_on_inner_fallback() {
async fn never_called<B>(_: Request<B>) -> Request<B> {
panic!("should never be called")
@ -140,7 +140,7 @@ async fn with_middleware_on_inner_fallback() {
assert_eq!(res.text().await, "outer");
}
#[tokio::test]
#[crate::test]
async fn also_inherits_default_layered_fallback() {
async fn set_header<B>(mut res: Response<B>) -> Response<B> {
res.headers_mut()
@ -162,7 +162,7 @@ async fn also_inherits_default_layered_fallback() {
assert_eq!(res.text().await, "outer");
}
#[tokio::test]
#[crate::test]
async fn fallback_inherited_into_nested_router_service() {
let inner = Router::new()
.route(
@ -182,7 +182,7 @@ async fn fallback_inherited_into_nested_router_service() {
assert_eq!(res.text().await, "outer");
}
#[tokio::test]
#[crate::test]
async fn fallback_inherited_into_nested_opaque_service() {
let inner = Router::new()
.route(

View file

@ -6,7 +6,7 @@ mod for_handlers {
use super::*;
use http::HeaderMap;
#[tokio::test]
#[crate::test]
async fn get_handles_head() {
let app = Router::new().route(
"/",
@ -41,7 +41,7 @@ mod for_services {
use super::*;
use crate::routing::get_service;
#[tokio::test]
#[crate::test]
async fn get_handles_head() {
let app = Router::new().route(
"/",

View file

@ -29,7 +29,7 @@ impl<R> Service<R> for Svc {
}
}
#[tokio::test]
#[crate::test]
async fn handler() {
let app = Router::new().route(
"/",
@ -48,7 +48,7 @@ async fn handler() {
assert_eq!(res.status(), StatusCode::REQUEST_TIMEOUT);
}
#[tokio::test]
#[crate::test]
async fn handler_multiple_methods_first() {
let app = Router::new().route(
"/",
@ -68,7 +68,7 @@ async fn handler_multiple_methods_first() {
assert_eq!(res.status(), StatusCode::REQUEST_TIMEOUT);
}
#[tokio::test]
#[crate::test]
async fn handler_multiple_methods_middle() {
let app = Router::new().route(
"/",
@ -91,7 +91,7 @@ async fn handler_multiple_methods_middle() {
assert_eq!(res.status(), StatusCode::REQUEST_TIMEOUT);
}
#[tokio::test]
#[crate::test]
async fn handler_multiple_methods_last() {
let app = Router::new().route(
"/",

View file

@ -3,7 +3,7 @@ use crate::{error_handling::HandleErrorLayer, extract::OriginalUri, response::In
use serde_json::{json, Value};
use tower::{limit::ConcurrencyLimitLayer, timeout::TimeoutLayer};
#[tokio::test]
#[crate::test]
async fn basic() {
let one = Router::new()
.route("/foo", get(|| async {}))
@ -26,7 +26,7 @@ async fn basic() {
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}
#[tokio::test]
#[crate::test]
async fn multiple_ors_balanced_differently() {
let one = Router::new().route("/one", get(|| async { "one" }));
let two = Router::new().route("/two", get(|| async { "two" }));
@ -71,7 +71,7 @@ async fn multiple_ors_balanced_differently() {
}
}
#[tokio::test]
#[crate::test]
async fn nested_or() {
let bar = Router::new().route("/bar", get(|| async { "bar" }));
let baz = Router::new().route("/baz", get(|| async { "baz" }));
@ -87,7 +87,7 @@ async fn nested_or() {
assert_eq!(client.get("/foo/baz").send().await.text().await, "baz");
}
#[tokio::test]
#[crate::test]
async fn or_with_route_following() {
let one = Router::new().route("/one", get(|| async { "one" }));
let two = Router::new().route("/two", get(|| async { "two" }));
@ -105,7 +105,7 @@ async fn or_with_route_following() {
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
#[crate::test]
async fn layer() {
let one = Router::new().route("/foo", get(|| async {}));
let two = Router::new()
@ -122,7 +122,7 @@ async fn layer() {
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
#[crate::test]
async fn layer_and_handle_error() {
let one = Router::new().route("/foo", get(|| async {}));
let two = Router::new()
@ -142,7 +142,7 @@ async fn layer_and_handle_error() {
assert_eq!(res.status(), StatusCode::REQUEST_TIMEOUT);
}
#[tokio::test]
#[crate::test]
async fn nesting() {
let one = Router::new().route("/foo", get(|| async {}));
let two = Router::new().nest("/bar", Router::new().route("/baz", get(|| async {})));
@ -154,7 +154,7 @@ async fn nesting() {
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
#[crate::test]
async fn boxed() {
let one = Router::new().route("/foo", get(|| async {}));
let two = Router::new().route("/bar", get(|| async {}));
@ -166,7 +166,7 @@ async fn boxed() {
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
#[crate::test]
async fn many_ors() {
let app = Router::new()
.route("/r1", get(|| async {}))
@ -188,7 +188,7 @@ async fn many_ors() {
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}
#[tokio::test]
#[crate::test]
async fn services() {
use crate::routing::get_service;
@ -227,7 +227,7 @@ async fn all_the_uris(
}))
}
#[tokio::test]
#[crate::test]
async fn nesting_and_seeing_the_right_uri() {
let one = Router::new().nest("/foo/", Router::new().route("/bar", get(all_the_uris)));
let two = Router::new().route("/foo", get(all_the_uris));
@ -257,7 +257,7 @@ async fn nesting_and_seeing_the_right_uri() {
);
}
#[tokio::test]
#[crate::test]
async fn nesting_and_seeing_the_right_uri_at_more_levels_of_nesting() {
let one = Router::new().nest(
"/foo/",
@ -290,7 +290,7 @@ async fn nesting_and_seeing_the_right_uri_at_more_levels_of_nesting() {
);
}
#[tokio::test]
#[crate::test]
async fn nesting_and_seeing_the_right_uri_ors_with_nesting() {
let one = Router::new().nest(
"/one",
@ -335,7 +335,7 @@ async fn nesting_and_seeing_the_right_uri_ors_with_nesting() {
);
}
#[tokio::test]
#[crate::test]
async fn nesting_and_seeing_the_right_uri_ors_with_multi_segment_uris() {
let one = Router::new().nest(
"/one",
@ -368,7 +368,7 @@ async fn nesting_and_seeing_the_right_uri_ors_with_multi_segment_uris() {
);
}
#[tokio::test]
#[crate::test]
async fn middleware_that_return_early() {
let private = Router::new()
.route("/", get(|| async {}))

View file

@ -29,7 +29,7 @@ mod handle_error;
mod merge;
mod nest;
#[tokio::test]
#[crate::test]
async fn hello_world() {
async fn root(_: Request<Body>) -> &'static str {
"Hello, World!"
@ -62,7 +62,7 @@ async fn hello_world() {
assert_eq!(body, "users#create");
}
#[tokio::test]
#[crate::test]
async fn routing() {
let app = Router::new()
.route(
@ -98,7 +98,7 @@ async fn routing() {
assert_eq!(res.text().await, "users#action");
}
#[tokio::test]
#[crate::test]
async fn router_type_doesnt_change() {
let app: Router = Router::new()
.route(
@ -123,7 +123,7 @@ async fn router_type_doesnt_change() {
assert_eq!(res.text().await, "hi from POST");
}
#[tokio::test]
#[crate::test]
async fn routing_between_services() {
use std::convert::Infallible;
use tower::service_fn;
@ -169,7 +169,7 @@ async fn routing_between_services() {
assert_eq!(res.text().await, "handler");
}
#[tokio::test]
#[crate::test]
async fn middleware_on_single_route() {
use tower::ServiceBuilder;
use tower_http::{compression::CompressionLayer, trace::TraceLayer};
@ -196,7 +196,7 @@ async fn middleware_on_single_route() {
assert_eq!(body, "Hello, World!");
}
#[tokio::test]
#[crate::test]
async fn service_in_bottom() {
async fn handler(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
Ok(Response::new(hyper::Body::empty()))
@ -207,7 +207,7 @@ async fn service_in_bottom() {
TestClient::new(app);
}
#[tokio::test]
#[crate::test]
async fn wrong_method_handler() {
let app = Router::new()
.route("/", get(|| async {}).post(|| async {}))
@ -230,7 +230,7 @@ async fn wrong_method_handler() {
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}
#[tokio::test]
#[crate::test]
async fn wrong_method_service() {
#[derive(Clone)]
struct Svc;
@ -270,7 +270,7 @@ async fn wrong_method_service() {
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}
#[tokio::test]
#[crate::test]
async fn multiple_methods_for_one_handler() {
async fn root(_: Request<Body>) -> &'static str {
"Hello, World!"
@ -287,7 +287,7 @@ async fn multiple_methods_for_one_handler() {
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
#[crate::test]
async fn wildcard_sees_whole_url() {
let app = Router::new().route("/api/*rest", get(|uri: Uri| async move { uri.to_string() }));
@ -297,7 +297,7 @@ async fn wildcard_sees_whole_url() {
assert_eq!(res.text().await, "/api/foo/bar");
}
#[tokio::test]
#[crate::test]
async fn middleware_applies_to_routes_above() {
let app = Router::new()
.route("/one", get(std::future::pending::<()>))
@ -319,7 +319,7 @@ async fn middleware_applies_to_routes_above() {
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
#[crate::test]
async fn not_found_for_extra_trailing_slash() {
let app = Router::new().route("/foo", get(|| async {}));
@ -332,7 +332,7 @@ async fn not_found_for_extra_trailing_slash() {
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
#[crate::test]
async fn not_found_for_missing_trailing_slash() {
let app = Router::new().route("/foo/", get(|| async {}));
@ -342,7 +342,7 @@ async fn not_found_for_missing_trailing_slash() {
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}
#[tokio::test]
#[crate::test]
async fn with_and_without_trailing_slash() {
let app = Router::new()
.route("/foo", get(|| async { "without tsr" }))
@ -360,7 +360,7 @@ async fn with_and_without_trailing_slash() {
}
// for https://github.com/tokio-rs/axum/issues/420
#[tokio::test]
#[crate::test]
async fn wildcard_doesnt_match_just_trailing_slash() {
let app = Router::new().route(
"/x/*path",
@ -380,7 +380,7 @@ async fn wildcard_doesnt_match_just_trailing_slash() {
assert_eq!(res.text().await, "foo/bar");
}
#[tokio::test]
#[crate::test]
async fn static_and_dynamic_paths() {
let app = Router::new()
.route(
@ -398,14 +398,14 @@ async fn static_and_dynamic_paths() {
assert_eq!(res.text().await, "static");
}
#[tokio::test]
#[crate::test]
#[should_panic(expected = "Paths must start with a `/`. Use \"/\" for root routes")]
async fn empty_route() {
let app = Router::new().route("", get(|| async {}));
TestClient::new(app);
}
#[tokio::test]
#[crate::test]
async fn middleware_still_run_for_unmatched_requests() {
#[derive(Clone)]
struct CountMiddleware<S>(S);
@ -445,7 +445,7 @@ async fn middleware_still_run_for_unmatched_requests() {
assert_eq!(COUNT.load(Ordering::SeqCst), 2);
}
#[tokio::test]
#[crate::test]
#[should_panic(expected = "\
Invalid route: `Router::route_service` cannot be used with `Router`s. \
Use `Router::nest` instead\
@ -454,7 +454,7 @@ async fn routing_to_router_panics() {
TestClient::new(Router::new().route_service("/", Router::new()));
}
#[tokio::test]
#[crate::test]
async fn route_layer() {
let app = Router::new()
.route("/foo", get(|| async {}))
@ -482,7 +482,7 @@ async fn route_layer() {
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
#[crate::test]
async fn different_methods_added_in_different_routes() {
let app = Router::new()
.route("/", get(|| async { "GET" }))
@ -499,7 +499,7 @@ async fn different_methods_added_in_different_routes() {
assert_eq!(body, "POST");
}
#[tokio::test]
#[crate::test]
#[should_panic(expected = "Cannot merge two `Router`s that both have a fallback")]
async fn merging_routers_with_fallbacks_panics() {
async fn fallback() {}
@ -525,7 +525,7 @@ fn merging_with_overlapping_method_routes() {
app.clone().merge(app);
}
#[tokio::test]
#[crate::test]
async fn merging_routers_with_same_paths_but_different_methods() {
let one = Router::new().route("/", get(|| async { "GET" }));
let two = Router::new().route("/", post(|| async { "POST" }));
@ -541,7 +541,7 @@ async fn merging_routers_with_same_paths_but_different_methods() {
assert_eq!(body, "POST");
}
#[tokio::test]
#[crate::test]
async fn head_content_length_through_hyper_server() {
let app = Router::new()
.route("/", get(|| async { "foo" }))
@ -558,7 +558,7 @@ async fn head_content_length_through_hyper_server() {
assert!(res.text().await.is_empty());
}
#[tokio::test]
#[crate::test]
async fn head_content_length_through_hyper_server_that_hits_fallback() {
let app = Router::new().fallback(|| async { "foo" });
@ -568,7 +568,7 @@ async fn head_content_length_through_hyper_server_that_hits_fallback() {
assert_eq!(res.headers()["content-length"], "3");
}
#[tokio::test]
#[crate::test]
async fn head_with_middleware_applied() {
use tower_http::compression::{predicate::SizeAbove, CompressionLayer};
@ -601,14 +601,14 @@ async fn head_with_middleware_applied() {
assert!(!res.headers().contains_key("content-length"));
}
#[tokio::test]
#[crate::test]
#[should_panic(expected = "Paths must start with a `/`")]
async fn routes_must_start_with_slash() {
let app = Router::new().route(":foo", get(|| async {}));
TestClient::new(app);
}
#[tokio::test]
#[crate::test]
async fn body_limited_by_default() {
let app = Router::new()
.route("/bytes", post(|_: Bytes| async {}))
@ -636,7 +636,7 @@ async fn body_limited_by_default() {
}
}
#[tokio::test]
#[crate::test]
async fn disabling_the_default_limit() {
let app = Router::new()
.route("/", post(|_: Bytes| async {}))
@ -652,7 +652,7 @@ async fn disabling_the_default_limit() {
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
#[crate::test]
async fn limited_body_with_content_length() {
const LIMIT: usize = 3;
@ -674,7 +674,7 @@ async fn limited_body_with_content_length() {
assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE);
}
#[tokio::test]
#[crate::test]
async fn changing_the_default_limit() {
let new_limit = 2;
@ -699,7 +699,7 @@ async fn changing_the_default_limit() {
assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE);
}
#[tokio::test]
#[crate::test]
async fn limited_body_with_streaming_body() {
const LIMIT: usize = 3;
@ -731,7 +731,7 @@ async fn limited_body_with_streaming_body() {
assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE);
}
#[tokio::test]
#[crate::test]
async fn extract_state() {
#[derive(Clone)]
struct AppState {
@ -767,7 +767,7 @@ async fn extract_state() {
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
#[crate::test]
async fn explicitly_set_state() {
let app = Router::new()
.route_service(
@ -781,7 +781,7 @@ async fn explicitly_set_state() {
assert_eq!(res.text().await, "foo");
}
#[tokio::test]
#[crate::test]
async fn layer_response_into_response() {
fn map_response<B>(_res: Response<B>) -> Result<Response<B>, impl IntoResponse> {
let headers = [("x-foo", "bar")];

View file

@ -3,7 +3,7 @@ use crate::{body::boxed, extract::Extension};
use std::collections::HashMap;
use tower_http::services::ServeDir;
#[tokio::test]
#[crate::test]
async fn nesting_apps() {
let api_routes = Router::new()
.route(
@ -58,7 +58,7 @@ async fn nesting_apps() {
assert_eq!(res.text().await, "v0: games#show (123)");
}
#[tokio::test]
#[crate::test]
async fn wrong_method_nest() {
let nested_app = Router::new().route("/", get(|| async {}));
let app = Router::new().nest("/", nested_app);
@ -76,7 +76,7 @@ async fn wrong_method_nest() {
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}
#[tokio::test]
#[crate::test]
async fn nesting_router_at_root() {
let nested = Router::new().route("/foo", get(|uri: Uri| async move { uri.to_string() }));
let app = Router::new().nest("/", nested);
@ -94,7 +94,7 @@ async fn nesting_router_at_root() {
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}
#[tokio::test]
#[crate::test]
async fn nesting_router_at_empty_path() {
let nested = Router::new().route("/foo", get(|uri: Uri| async move { uri.to_string() }));
let app = Router::new().nest("", nested);
@ -112,7 +112,7 @@ async fn nesting_router_at_empty_path() {
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}
#[tokio::test]
#[crate::test]
async fn nesting_handler_at_root() {
let app = Router::new().nest_service("/", get(|uri: Uri| async move { uri.to_string() }));
@ -131,7 +131,7 @@ async fn nesting_handler_at_root() {
assert_eq!(res.text().await, "/foo/bar");
}
#[tokio::test]
#[crate::test]
async fn nested_url_extractor() {
let app = Router::new().nest(
"/foo",
@ -157,7 +157,7 @@ async fn nested_url_extractor() {
assert_eq!(res.text().await, "/qux");
}
#[tokio::test]
#[crate::test]
async fn nested_url_original_extractor() {
let app = Router::new().nest(
"/foo",
@ -177,7 +177,7 @@ async fn nested_url_original_extractor() {
assert_eq!(res.text().await, "/foo/bar/baz");
}
#[tokio::test]
#[crate::test]
async fn nested_service_sees_stripped_uri() {
let app = Router::new().nest(
"/foo",
@ -200,7 +200,7 @@ async fn nested_service_sees_stripped_uri() {
assert_eq!(res.text().await, "/baz");
}
#[tokio::test]
#[crate::test]
async fn nest_static_file_server() {
let app = Router::new().nest_service(
"/static",
@ -218,7 +218,7 @@ async fn nest_static_file_server() {
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
#[crate::test]
async fn nested_multiple_routes() {
let app = Router::new()
.nest(
@ -244,7 +244,7 @@ fn nested_at_root_with_other_routes() {
.route("/", get(|| async {}));
}
#[tokio::test]
#[crate::test]
async fn multiple_top_level_nests() {
let app = Router::new()
.nest(
@ -262,13 +262,13 @@ async fn multiple_top_level_nests() {
assert_eq!(client.get("/two/route").send().await.text().await, "two");
}
#[tokio::test]
#[crate::test]
#[should_panic(expected = "Invalid route: nested routes cannot contain wildcards (*)")]
async fn nest_cannot_contain_wildcards() {
Router::<(), Body>::new().nest("/one/*rest", Router::new());
}
#[tokio::test]
#[crate::test]
async fn outer_middleware_still_see_whole_url() {
#[derive(Clone)]
struct SetUriExtension<S>(S);
@ -319,7 +319,7 @@ async fn outer_middleware_still_see_whole_url() {
assert_eq!(client.get("/one/two").send().await.text().await, "/one/two");
}
#[tokio::test]
#[crate::test]
async fn nest_at_capture() {
let api_routes = Router::new().route(
"/:b",
@ -335,7 +335,7 @@ async fn nest_at_capture() {
assert_eq!(res.text().await, "a=foo b=bar");
}
#[tokio::test]
#[crate::test]
async fn nest_with_and_without_trailing() {
let app = Router::new().nest_service("/foo", get(|| async {}));
@ -351,7 +351,7 @@ async fn nest_with_and_without_trailing() {
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
#[crate::test]
async fn nesting_with_root_inner_router() {
let app = Router::new().nest(
"/foo",
@ -371,7 +371,7 @@ async fn nesting_with_root_inner_router() {
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
#[crate::test]
async fn fallback_on_inner() {
let app = Router::new()
.nest(
@ -399,7 +399,7 @@ macro_rules! nested_route_test {
// the route we expect to be able to call
expected = $expected_path:literal $(,)?
) => {
#[tokio::test]
#[crate::test]
async fn $name() {
let inner = Router::new().route($route_path, get(|| async {}));
let app = Router::new().nest($nested_path, inner);

View file

@ -170,7 +170,7 @@ mod tests {
use super::*;
use crate::{response::IntoResponse, routing::get, test_helpers::*, Router};
#[tokio::test]
#[crate::test]
async fn typed_header() {
async fn handle(
TypedHeader(user_agent): TypedHeader<headers::UserAgent>,