diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 2e6fd566..17d57711 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -74,7 +74,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: --all --all-features + args: --all --all-features --all-targets deny-check: name: cargo-deny check diff --git a/examples/testing.rs b/examples/testing.rs new file mode 100644 index 00000000..cd4a7f63 --- /dev/null +++ b/examples/testing.rs @@ -0,0 +1,101 @@ +use awebframework::{prelude::*, routing::BoxRoute}; +use tower_http::trace::TraceLayer; + +#[tokio::main] +async fn main() { + tracing_subscriber::fmt::init(); + + let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 3000)); + + tracing::debug!("listening on {}", addr); + + hyper::Server::bind(&addr) + .serve(app().into_make_service()) + .await + .unwrap(); +} + +/// Having a function that produces our app makes it easy to call it from tests +/// without having to create an HTTP server. +#[allow(dead_code)] +fn app() -> BoxRoute { + route("/", get(|| async { "Hello, World!" })) + .route( + "/json", + post(|payload: extract::Json| async move { + response::Json(serde_json::json!({ "data": payload.0 })) + }), + ) + // We can still add middleware + .layer(TraceLayer::new_for_http()) + .boxed() +} + +#[cfg(test)] +mod tests { + use super::*; + use http::StatusCode; + use serde_json::{json, Value}; + use tower::ServiceExt; // for `app.oneshot()` + + #[tokio::test] + async fn hello_world() { + let app = app(); + + // `BoxRoute` implements `tower::Service>` so we can + // call it like any tower service, no need to run an HTTP server. + let response = app + .oneshot(Request::builder().uri("/").body(Body::empty()).unwrap()) + .await + .unwrap(); + + assert_eq!(response.status(), StatusCode::OK); + + let body = hyper::body::to_bytes(response.into_body()).await.unwrap(); + assert_eq!(&body[..], b"Hello, World!"); + } + + #[tokio::test] + async fn json() { + let app = app(); + + let response = app + .oneshot( + Request::builder() + .method(http::Method::POST) + .uri("/json") + .header(http::header::CONTENT_TYPE, "application/json") + .body(Body::from( + serde_json::to_vec(&json!([1, 2, 3, 4])).unwrap(), + )) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(response.status(), StatusCode::OK); + + let body = hyper::body::to_bytes(response.into_body()).await.unwrap(); + let body: Value = serde_json::from_slice(&body).unwrap(); + assert_eq!(body, json!({ "data": [1, 2, 3, 4] })); + } + + #[tokio::test] + async fn not_found() { + let app = app(); + + let response = app + .oneshot( + Request::builder() + .uri("/does-not-exist") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(response.status(), StatusCode::NOT_FOUND); + let body = hyper::body::to_bytes(response.into_body()).await.unwrap(); + assert!(body.is_empty()); + } +} diff --git a/src/extract/rejection.rs b/src/extract/rejection.rs index d900c9ba..1c462967 100644 --- a/src/extract/rejection.rs +++ b/src/extract/rejection.rs @@ -1,10 +1,8 @@ //! Rejection response types. -use http::StatusCode; -use tower::BoxError; - use super::IntoResponse; use crate::body::Body; +use tower::BoxError; macro_rules! define_rejection { ( @@ -355,9 +353,9 @@ pub struct TypedHeaderRejection { #[cfg(feature = "headers")] #[cfg_attr(docsrs, doc(cfg(feature = "headers")))] impl IntoResponse for TypedHeaderRejection { - fn into_response(self) -> http::Response { + fn into_response(self) -> http::Response { let mut res = format!("{} ({})", self.err, self.name).into_response(); - *res.status_mut() = StatusCode::BAD_REQUEST; + *res.status_mut() = http::StatusCode::BAD_REQUEST; res } }