Files
axum/src/tests.rs
T

693 lines
18 KiB
Rust
Raw Normal View History

use crate::{handler::on, prelude::*, response::IntoResponse, routing::MethodFilter, service};
2021-06-09 08:14:20 +02:00
use bytes::Bytes;
2021-05-31 12:22:16 +02:00
use http::{Request, Response, StatusCode};
use hyper::{Body, Server};
use serde::Deserialize;
use serde_json::json;
2021-06-01 17:17:10 +02:00
use std::{
net::{SocketAddr, TcpListener},
time::Duration,
};
2021-06-19 12:50:33 +02:00
use tower::{make::Shared, service_fn, BoxError, Service, ServiceBuilder};
2021-06-06 22:41:52 +02:00
use tower_http::{compression::CompressionLayer, trace::TraceLayer};
2021-05-31 12:22:16 +02:00
#[tokio::test]
async fn hello_world() {
2021-06-04 01:00:48 +02:00
async fn root(_: Request<Body>) -> &'static str {
"Hello, World!"
}
async fn foo(_: Request<Body>) -> &'static str {
"foo"
}
async fn users_create(_: Request<Body>) -> &'static str {
"users#create"
}
let app = route("/", get(root).post(foo)).route("/users", post(users_create));
2021-05-31 12:22:16 +02:00
let addr = run_in_background(app).await;
2021-06-04 01:00:48 +02:00
let client = reqwest::Client::new();
2021-05-31 12:22:16 +02:00
2021-06-04 01:00:48 +02:00
let res = client.get(format!("http://{}", addr)).send().await.unwrap();
let body = res.text().await.unwrap();
2021-05-31 12:22:16 +02:00
assert_eq!(body, "Hello, World!");
2021-06-04 01:00:48 +02:00
let res = client
.post(format!("http://{}", addr))
.send()
.await
.unwrap();
let body = res.text().await.unwrap();
assert_eq!(body, "foo");
let res = client
.post(format!("http://{}/users", addr))
.send()
.await
.unwrap();
let body = res.text().await.unwrap();
assert_eq!(body, "users#create");
2021-05-31 12:22:16 +02:00
}
#[tokio::test]
async fn consume_body() {
2021-06-09 09:03:09 +02:00
let app = route("/", get(|body: String| async { body }));
2021-05-31 12:22:16 +02:00
let addr = run_in_background(app).await;
let client = reqwest::Client::new();
let res = client
.get(format!("http://{}", addr))
.body("foo")
.send()
.await
.unwrap();
let body = res.text().await.unwrap();
assert_eq!(body, "foo");
}
#[tokio::test]
async fn deserialize_body() {
#[derive(Debug, Deserialize)]
struct Input {
foo: String,
}
2021-06-04 01:00:48 +02:00
let app = route(
"/",
2021-06-09 09:03:09 +02:00
post(|input: extract::Json<Input>| async { input.0.foo }),
2021-06-04 01:00:48 +02:00
);
2021-05-31 12:22:16 +02:00
let addr = run_in_background(app).await;
let client = reqwest::Client::new();
let res = client
.post(format!("http://{}", addr))
.json(&json!({ "foo": "bar" }))
.send()
.await
.unwrap();
let body = res.text().await.unwrap();
assert_eq!(body, "bar");
}
#[tokio::test]
async fn consume_body_to_json_requires_json_content_type() {
#[derive(Debug, Deserialize)]
struct Input {
foo: String,
}
2021-06-04 01:00:48 +02:00
let app = route(
"/",
post(|_: Request<Body>, input: extract::Json<Input>| async { input.0.foo }),
);
2021-05-31 12:22:16 +02:00
let addr = run_in_background(app).await;
let client = reqwest::Client::new();
let res = client
.post(format!("http://{}", addr))
.body(r#"{ "foo": "bar" }"#)
.send()
.await
.unwrap();
2021-05-31 22:54:21 +02:00
let status = res.status();
dbg!(res.text().await.unwrap());
assert_eq!(status, StatusCode::BAD_REQUEST);
2021-05-31 12:22:16 +02:00
}
#[tokio::test]
async fn body_with_length_limit() {
use std::iter::repeat;
#[derive(Debug, Deserialize)]
struct Input {
foo: String,
}
const LIMIT: u64 = 8;
2021-06-04 01:00:48 +02:00
let app = route(
"/",
2021-06-09 09:03:09 +02:00
post(|_body: extract::ContentLengthLimit<Bytes, LIMIT>| async {}),
2021-06-04 01:00:48 +02:00
);
2021-05-31 12:22:16 +02:00
let addr = run_in_background(app).await;
let client = reqwest::Client::new();
let res = client
.post(format!("http://{}", addr))
.body(repeat(0_u8).take((LIMIT - 1) as usize).collect::<Vec<_>>())
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
let res = client
.post(format!("http://{}", addr))
.body(repeat(0_u8).take(LIMIT as usize).collect::<Vec<_>>())
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
let res = client
.post(format!("http://{}", addr))
.body(repeat(0_u8).take((LIMIT + 1) as usize).collect::<Vec<_>>())
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE);
let res = client
.post(format!("http://{}", addr))
.body(reqwest::Body::wrap_stream(futures_util::stream::iter(
vec![Ok::<_, std::io::Error>(bytes::Bytes::new())],
)))
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::LENGTH_REQUIRED);
}
2021-05-31 14:04:05 +02:00
#[tokio::test]
async fn routing() {
2021-06-04 01:00:48 +02:00
let app = route(
"/users",
get(|_: Request<Body>| async { "users#index" })
.post(|_: Request<Body>| async { "users#create" }),
)
.route("/users/:id", get(|_: Request<Body>| async { "users#show" }))
.route(
"/users/:id/action",
get(|_: Request<Body>| async { "users#action" }),
);
2021-05-31 14:04:05 +02:00
let addr = run_in_background(app).await;
let client = reqwest::Client::new();
let res = client.get(format!("http://{}", addr)).send().await.unwrap();
assert_eq!(res.status(), StatusCode::NOT_FOUND);
let res = client
.get(format!("http://{}/users", addr))
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await.unwrap(), "users#index");
let res = client
.post(format!("http://{}/users", addr))
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await.unwrap(), "users#create");
let res = client
.get(format!("http://{}/users/1", addr))
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await.unwrap(), "users#show");
let res = client
.get(format!("http://{}/users/1/action", addr))
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await.unwrap(), "users#action");
}
#[tokio::test]
async fn extracting_url_params() {
2021-06-04 01:00:48 +02:00
let app = route(
"/users/:id",
2021-06-09 09:03:09 +02:00
get(|params: extract::UrlParams<(i32,)>| async move {
let (id,) = params.0;
assert_eq!(id, 42);
})
.post(|params_map: extract::UrlParamsMap| async move {
assert_eq!(params_map.get("id").unwrap(), "1337");
assert_eq!(
params_map
.get_typed::<i32>("id")
.expect("missing")
.expect("failed to parse"),
1337
);
}),
2021-06-04 01:00:48 +02:00
);
2021-05-31 12:22:16 +02:00
2021-05-31 14:04:05 +02:00
let addr = run_in_background(app).await;
let client = reqwest::Client::new();
let res = client
.get(format!("http://{}/users/42", addr))
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
let res = client
.post(format!("http://{}/users/1337", addr))
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
}
2021-05-31 12:22:16 +02:00
#[tokio::test]
async fn extracting_url_params_multiple_times() {
let app = route(
"/users/:id",
get(
|_: extract::UrlParams<(i32,)>,
_: extract::UrlParamsMap,
_: extract::UrlParams<(i32,)>,
_: extract::UrlParamsMap| async {},
),
);
let addr = run_in_background(app).await;
let client = reqwest::Client::new();
let res = client
.get(format!("http://{}/users/42", addr))
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
}
2021-05-31 16:28:26 +02:00
#[tokio::test]
async fn boxing() {
2021-06-04 01:00:48 +02:00
let app = route(
"/",
2021-06-06 15:19:54 +02:00
on(MethodFilter::Get, |_: Request<Body>| async {
"hi from GET"
})
.on(MethodFilter::Post, |_: Request<Body>| async {
"hi from POST"
}),
2021-06-04 01:00:48 +02:00
)
2021-06-06 20:30:54 +02:00
.layer(tower_http::compression::CompressionLayer::new())
2021-06-04 01:00:48 +02:00
.boxed();
2021-05-31 16:28:26 +02:00
let addr = run_in_background(app).await;
let client = reqwest::Client::new();
2021-05-31 20:42:57 +02:00
let res = client.get(format!("http://{}", addr)).send().await.unwrap();
2021-05-31 16:28:26 +02:00
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await.unwrap(), "hi from GET");
let res = client
.post(format!("http://{}", addr))
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await.unwrap(), "hi from POST");
}
2021-05-31 12:22:16 +02:00
#[tokio::test]
async fn service_handlers() {
use crate::service::ServiceExt as _;
use tower_http::services::ServeFile;
2021-06-04 01:00:48 +02:00
let app = route(
"/echo",
service::post(
service_fn(|req: Request<Body>| async move {
Ok::<_, BoxError>(Response::new(req.into_body()))
})
.handle_error(|_error: BoxError| StatusCode::INTERNAL_SERVER_ERROR),
),
2021-06-04 01:00:48 +02:00
)
.route(
"/static/Cargo.toml",
2021-06-06 11:37:08 +02:00
service::on(
2021-06-04 01:00:48 +02:00
MethodFilter::Get,
ServeFile::new("Cargo.toml").handle_error(|error: std::io::Error| {
2021-06-01 17:17:10 +02:00
(StatusCode::INTERNAL_SERVER_ERROR, error.to_string())
}),
2021-06-04 01:00:48 +02:00
),
);
let addr = run_in_background(app).await;
let client = reqwest::Client::new();
2021-06-01 00:34:09 +02:00
let res = client
.post(format!("http://{}/echo", addr))
.body("foobar")
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await.unwrap(), "foobar");
let res = client
.get(format!("http://{}/static/Cargo.toml", addr))
.body("foobar")
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
assert!(res.text().await.unwrap().contains("edition ="));
}
2021-06-06 15:19:54 +02:00
#[tokio::test]
async fn routing_between_services() {
use std::convert::Infallible;
use tower::service_fn;
async fn handle(_: Request<Body>) -> &'static str {
"handler"
}
let app = route(
"/one",
service::get(service_fn(|_: Request<Body>| async {
Ok::<_, Infallible>(Response::new(Body::from("one get")))
}))
.post(service_fn(|_: Request<Body>| async {
Ok::<_, Infallible>(Response::new(Body::from("one post")))
}))
.on(
MethodFilter::Put,
service_fn(|_: Request<Body>| async {
Ok::<_, Infallible>(Response::new(Body::from("one put")))
}),
),
)
.route(
"/two",
service::on(MethodFilter::Get, handle.into_service()),
);
let addr = run_in_background(app).await;
let client = reqwest::Client::new();
let res = client
.get(format!("http://{}/one", addr))
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await.unwrap(), "one get");
let res = client
.post(format!("http://{}/one", addr))
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await.unwrap(), "one post");
let res = client
.put(format!("http://{}/one", addr))
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await.unwrap(), "one put");
let res = client
.get(format!("http://{}/two", addr))
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await.unwrap(), "handler");
}
#[tokio::test]
async fn middleware_on_single_route() {
use tower::ServiceBuilder;
use tower_http::{compression::CompressionLayer, trace::TraceLayer};
async fn handle(_: Request<Body>) -> &'static str {
"Hello, World!"
}
2021-06-04 01:00:48 +02:00
let app = route(
"/",
get(handle.layer(
ServiceBuilder::new()
.layer(TraceLayer::new_for_http())
.layer(CompressionLayer::new())
.into_inner(),
)),
);
let addr = run_in_background(app).await;
let res = reqwest::get(format!("http://{}", addr)).await.unwrap();
let body = res.text().await.unwrap();
assert_eq!(body, "Hello, World!");
}
2021-06-01 08:32:58 +02:00
2021-06-01 17:17:10 +02:00
#[tokio::test]
async fn handling_errors_from_layered_single_routes() {
async fn handle(_req: Request<Body>) -> &'static str {
tokio::time::sleep(Duration::from_secs(10)).await;
""
}
2021-06-04 01:00:48 +02:00
let app = route(
"/",
get(handle
2021-06-06 22:41:52 +02:00
.layer(
ServiceBuilder::new()
2021-06-06 22:43:53 +02:00
.timeout(Duration::from_millis(100))
2021-06-06 22:41:52 +02:00
.layer(TraceLayer::new_for_http())
.into_inner(),
)
2021-06-04 01:00:48 +02:00
.handle_error(|_error: BoxError| StatusCode::INTERNAL_SERVER_ERROR)),
);
2021-06-01 17:17:10 +02:00
let addr = run_in_background(app).await;
let res = reqwest::get(format!("http://{}", addr)).await.unwrap();
assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
2021-06-01 21:15:48 +02:00
#[tokio::test]
async fn layer_on_whole_router() {
async fn handle(_req: Request<Body>) -> &'static str {
tokio::time::sleep(Duration::from_secs(10)).await;
""
}
2021-06-04 01:00:48 +02:00
let app = route("/", get(handle))
.layer(
ServiceBuilder::new()
.layer(CompressionLayer::new())
.timeout(Duration::from_millis(100))
.into_inner(),
)
.handle_error(|_err: BoxError| StatusCode::INTERNAL_SERVER_ERROR);
2021-06-01 21:15:48 +02:00
let addr = run_in_background(app).await;
let res = reqwest::get(format!("http://{}", addr)).await.unwrap();
assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
2021-06-06 20:30:54 +02:00
#[tokio::test]
async fn disjunction() {
let api_routes = route(
"/users",
2021-06-09 09:03:09 +02:00
get(|| async { "users#index" }).post(|| async { "users#create" }),
2021-06-06 20:30:54 +02:00
)
.route(
"/users/:id",
2021-06-09 09:03:09 +02:00
get(|params: extract::UrlParamsMap| async move {
format!(
"{}: users#show ({})",
params.get("version").unwrap(),
params.get("id").unwrap()
)
}),
2021-06-06 20:30:54 +02:00
)
.route(
"/games/:id",
2021-06-09 09:03:09 +02:00
get(|params: extract::UrlParamsMap| async move {
format!(
"{}: games#show ({})",
params.get("version").unwrap(),
params.get("id").unwrap()
)
}),
2021-06-06 20:30:54 +02:00
);
2021-06-09 09:03:09 +02:00
let app = route("/", get(|| async { "hi" })).nest("/:version/api", api_routes);
2021-06-06 20:30:54 +02:00
let addr = run_in_background(app).await;
let client = reqwest::Client::new();
let res = client
.get(format!("http://{}/", addr))
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await.unwrap(), "hi");
let res = client
.get(format!("http://{}/v0/api/users", addr))
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await.unwrap(), "users#index");
let res = client
.get(format!("http://{}/v0/api/users/123", addr))
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await.unwrap(), "v0: users#show (123)");
let res = client
.get(format!("http://{}/v0/api/games/123", addr))
.send()
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await.unwrap(), "v0: games#show (123)");
}
2021-06-01 17:17:10 +02:00
#[tokio::test]
async fn typed_header() {
use extract::TypedHeader;
async fn handle(TypedHeader(user_agent): TypedHeader<headers::UserAgent>) -> impl IntoResponse {
user_agent.to_string()
}
let app = route("/", get(handle));
let addr = run_in_background(app).await;
let client = reqwest::Client::new();
let res = client
.get(format!("http://{}", addr))
.header("user-agent", "foobar")
.send()
.await
.unwrap();
let body = res.text().await.unwrap();
assert_eq!(body, "foobar");
let res = client.get(format!("http://{}", addr)).send().await.unwrap();
let body = res.text().await.unwrap();
assert_eq!(body, "invalid HTTP header (user-agent)");
}
2021-06-19 12:50:33 +02:00
#[tokio::test]
async fn different_request_body_types() {
use http_body::{Empty, Full};
use std::convert::Infallible;
use tower_http::map_request_body::MapRequestBodyLayer;
async fn handler(body: String) -> String {
body
}
async fn svc_handler<B>(req: Request<B>) -> Result<Response<Body>, Infallible>
where
B: http_body::Body,
B::Error: std::fmt::Debug,
{
let body = hyper::body::to_bytes(req.into_body()).await.unwrap();
Ok(Response::new(Body::from(body)))
}
let app = route("/", service::get(service_fn(svc_handler)))
.route(
"/foo",
get(handler.layer(MapRequestBodyLayer::new(|_| Full::<Bytes>::from("foo")))),
)
.layer(MapRequestBodyLayer::new(|_| Empty::<Bytes>::new()));
let addr = run_in_background(app).await;
let client = reqwest::Client::new();
let res = client
.get(format!("http://{}/", addr))
.send()
.await
.unwrap();
let body = res.text().await.unwrap();
assert_eq!(body, "");
let res = client
.get(format!("http://{}/foo", addr))
.send()
.await
.unwrap();
let body = res.text().await.unwrap();
assert_eq!(body, "foo");
}
2021-07-06 09:40:25 +02:00
#[tokio::test]
async fn service_in_bottom() {
async fn handler(_req: Request<hyper::Body>) -> Result<Response<hyper::Body>, hyper::Error> {
Ok(Response::new(hyper::Body::empty()))
}
let app = route("/", service::get(service_fn(handler)));
run_in_background(app).await;
}
2021-05-31 12:22:16 +02:00
/// Run a `tower::Service` in the background and get a URI for it.
2021-06-06 11:37:08 +02:00
async fn run_in_background<S, ResBody>(svc: S) -> SocketAddr
2021-05-31 12:22:16 +02:00
where
S: Service<Request<Body>, Response = Response<ResBody>> + Clone + Send + 'static,
ResBody: http_body::Body + Send + 'static,
ResBody::Data: Send,
ResBody::Error: Into<BoxError>,
S::Future: Send,
2021-06-04 01:00:48 +02:00
S::Error: Into<BoxError>,
2021-05-31 12:22:16 +02:00
{
let listener = TcpListener::bind("127.0.0.1:0").expect("Could not bind ephemeral socket");
let addr = listener.local_addr().unwrap();
println!("Listening on {}", addr);
let (tx, rx) = tokio::sync::oneshot::channel();
tokio::spawn(async move {
let server = Server::from_tcp(listener).unwrap().serve(Shared::new(svc));
tx.send(()).unwrap();
server.await.expect("server error");
});
rx.await.unwrap();
addr
}