Skip to content

Commit 22d0d88

Browse files
authored
chore(volo-http): add more unit tests for server (#629)
Signed-off-by: Yu Li <liyu.yukiteru@bytedance.com>
1 parent 663fd5c commit 22d0d88

3 files changed

Lines changed: 264 additions & 1 deletion

File tree

volo-http/src/server/extract.rs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,4 +671,135 @@ mod extract_tests {
671671
assert_handler(option_cx_req);
672672
assert_handler(result_cx_req);
673673
}
674+
675+
#[cfg(any(feature = "form", feature = "json"))]
676+
fn simple_req(content_type: &'static str, body: &'static str) -> crate::request::Request {
677+
let mut req = crate::request::Request::new(Body::from(body));
678+
req.headers_mut().insert(
679+
http::header::CONTENT_TYPE,
680+
http::header::HeaderValue::from_static(content_type),
681+
);
682+
req
683+
}
684+
685+
#[cfg(feature = "form")]
686+
#[tokio::test]
687+
async fn extract_form() {
688+
use crate::server::test_helpers;
689+
690+
#[derive(Debug, PartialEq, Eq, serde::Deserialize)]
691+
struct TestForm {
692+
key1: String,
693+
key2: String,
694+
key3: String,
695+
}
696+
697+
const VALID_FORM: &str = "key1=value1&key2=value2&key3=value3";
698+
const INVALID_FORM: &str = "if (key && value) { print(key, value) }";
699+
700+
let test_form = serde_urlencoded::from_str(VALID_FORM).unwrap();
701+
702+
// simple content-type
703+
{
704+
let req = simple_req("application/x-www-form-urlencoded", VALID_FORM);
705+
let (parts, body) = req.into_parts();
706+
assert_eq!(
707+
super::Form::<TestForm>::from_request(&mut test_helpers::empty_cx(), parts, body,)
708+
.await
709+
.unwrap()
710+
.0,
711+
test_form,
712+
);
713+
}
714+
// content-type with charset
715+
{
716+
let req = simple_req(
717+
"application/x-www-form-urlencoded; charset=utf-8",
718+
VALID_FORM,
719+
);
720+
let (parts, body) = req.into_parts();
721+
assert_eq!(
722+
super::Form::<TestForm>::from_request(&mut test_helpers::empty_cx(), parts, body,)
723+
.await
724+
.unwrap()
725+
.0,
726+
test_form,
727+
);
728+
}
729+
// wrong content type
730+
{
731+
let req = simple_req("text/javascript", VALID_FORM);
732+
let (parts, body) = req.into_parts();
733+
super::Form::<TestForm>::from_request(&mut test_helpers::empty_cx(), parts, body)
734+
.await
735+
.unwrap_err();
736+
}
737+
// invalid form
738+
{
739+
let req = simple_req("application/x-www-form-urlencoded", INVALID_FORM);
740+
let (parts, body) = req.into_parts();
741+
super::Form::<TestForm>::from_request(&mut test_helpers::empty_cx(), parts, body)
742+
.await
743+
.unwrap_err();
744+
}
745+
}
746+
747+
#[cfg(feature = "json")]
748+
#[tokio::test]
749+
async fn extract_json() {
750+
use crate::server::test_helpers;
751+
752+
#[derive(Debug, PartialEq, Eq, serde::Deserialize)]
753+
struct TestJson {
754+
key1: String,
755+
key2: String,
756+
key3: String,
757+
}
758+
759+
const VALID_JSON: &str = r#"{"key1":"value1","key2":"value2", "key3": "value3"}"#;
760+
const INVALID_JSON: &str = "if (key && value) { print(key, value) }";
761+
762+
let test_json = crate::utils::json::deserialize(VALID_JSON.as_bytes()).unwrap();
763+
764+
// simple content-type
765+
{
766+
let req = simple_req("application/json", VALID_JSON);
767+
let (parts, body) = req.into_parts();
768+
assert_eq!(
769+
super::Json::<TestJson>::from_request(&mut test_helpers::empty_cx(), parts, body,)
770+
.await
771+
.unwrap()
772+
.0,
773+
test_json,
774+
);
775+
}
776+
// content-type with charset
777+
{
778+
let req = simple_req("application/json; charset=utf-8", VALID_JSON);
779+
let (parts, body) = req.into_parts();
780+
assert_eq!(
781+
super::Json::<TestJson>::from_request(&mut test_helpers::empty_cx(), parts, body,)
782+
.await
783+
.unwrap()
784+
.0,
785+
test_json,
786+
);
787+
}
788+
// wrong content type
789+
{
790+
let req = simple_req("text/javascript", VALID_JSON);
791+
let (parts, body) = req.into_parts();
792+
super::Json::<TestJson>::from_request(&mut test_helpers::empty_cx(), parts, body)
793+
.await
794+
.unwrap_err();
795+
}
796+
// invalid form
797+
{
798+
let req = simple_req("application/json", INVALID_JSON);
799+
let (parts, body) = req.into_parts();
800+
super::Json::<TestJson>::from_request(&mut test_helpers::empty_cx(), parts, body)
801+
.await
802+
.unwrap_err();
803+
}
804+
}
674805
}

volo-http/src/server/panic_handler.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,101 @@ pub fn always_internal_error<Cx, E>(
5555
pub fn fixed_payload<R>(payload: R) -> FixedPayload<R> {
5656
FixedPayload { payload }
5757
}
58+
59+
#[cfg(test)]
60+
mod panic_handler_tests {
61+
use std::{convert::Infallible, marker::PhantomData};
62+
63+
use http::status::StatusCode;
64+
use motore::{layer::Layer, service::Service};
65+
66+
use super::{always_internal_error, fixed_payload};
67+
use crate::{
68+
body::{Body, BodyConversion},
69+
request::Request,
70+
response::Response,
71+
server::{IntoResponse, test_helpers},
72+
};
73+
74+
struct PanicService<R = Response, E = Infallible> {
75+
_marker: PhantomData<fn(R, E)>,
76+
}
77+
78+
impl<R, E> PanicService<R, E> {
79+
const fn new() -> Self {
80+
Self {
81+
_marker: PhantomData,
82+
}
83+
}
84+
}
85+
86+
impl<Cx, Req, Resp, E> Service<Cx, Req> for PanicService<Resp, E>
87+
where
88+
Cx: Send,
89+
Req: Send,
90+
Resp: Send,
91+
{
92+
type Response = Resp;
93+
type Error = E;
94+
95+
async fn call(&self, _: &mut Cx, _: Req) -> Result<Self::Response, Self::Error> {
96+
panic!("oops")
97+
}
98+
}
99+
100+
#[tokio::test]
101+
async fn fixed_payload_test() {
102+
const ERR_MSG: &str = "oops";
103+
104+
// it's ok
105+
{
106+
let srv =
107+
volo::catch_panic::Layer::new(fixed_payload((StatusCode::NOT_FOUND, ERR_MSG)))
108+
.layer(test_helpers::DefaultService::<Response, Infallible>::new());
109+
let resp = srv
110+
.call(&mut test_helpers::empty_cx(), Request::<Body>::default())
111+
.await
112+
.into_response();
113+
assert_eq!(resp.status(), StatusCode::OK);
114+
}
115+
// it panics
116+
{
117+
let srv =
118+
volo::catch_panic::Layer::new(fixed_payload((StatusCode::NOT_FOUND, ERR_MSG)))
119+
.layer(PanicService::<Response, Infallible>::new());
120+
let resp = srv
121+
.call(&mut test_helpers::empty_cx(), Request::<Body>::default())
122+
.await
123+
.into_response();
124+
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
125+
assert_eq!(resp.into_string().await.unwrap(), ERR_MSG);
126+
}
127+
}
128+
129+
#[tokio::test]
130+
async fn internal_error_test() {
131+
// it's ok
132+
{
133+
let srv = volo::catch_panic::Layer::new(always_internal_error)
134+
.layer(test_helpers::DefaultService::<Response, Infallible>::new());
135+
let resp = srv
136+
.call(&mut test_helpers::empty_cx(), Request::<Body>::default())
137+
.await
138+
.into_response();
139+
assert_eq!(resp.status(), StatusCode::OK);
140+
}
141+
// it panics
142+
{
143+
let srv = volo::catch_panic::Layer::new(always_internal_error).layer(PanicService::<
144+
Response,
145+
Infallible,
146+
>::new(
147+
));
148+
let resp = srv
149+
.call(&mut test_helpers::empty_cx(), Request::<Body>::default())
150+
.await
151+
.into_response();
152+
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
153+
}
154+
}
155+
}

volo-http/src/server/test_helpers.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Test utilities for server of Volo-HTTP.
22
3-
use std::{fmt::Debug, marker::PhantomData};
3+
use std::{convert::Infallible, fmt::Debug, marker::PhantomData};
44

55
use http::method::Method;
66
use motore::{layer::Layer, service::Service};
@@ -15,6 +15,40 @@ use crate::{
1515
utils::test_helpers::mock_address,
1616
};
1717

18+
/// Default [`Service`] that always responds `Ok(R::default())`
19+
pub struct DefaultService<R = Response, E = Infallible> {
20+
_marker: PhantomData<fn(R, E)>,
21+
}
22+
23+
impl<R, E> DefaultService<R, E> {
24+
/// Create a new [`DefaultService`]
25+
pub const fn new() -> Self {
26+
Self {
27+
_marker: PhantomData,
28+
}
29+
}
30+
}
31+
32+
impl<R, E> Default for DefaultService<R, E> {
33+
fn default() -> Self {
34+
Self::new()
35+
}
36+
}
37+
38+
impl<Cx, Req, Resp, E> Service<Cx, Req> for DefaultService<Resp, E>
39+
where
40+
Cx: Send,
41+
Req: Send,
42+
Resp: Default + Send,
43+
{
44+
type Response = Resp;
45+
type Error = E;
46+
47+
async fn call(&self, _: &mut Cx, _: Req) -> Result<Self::Response, Self::Error> {
48+
Ok(Resp::default())
49+
}
50+
}
51+
1852
/// Wrap a [`Handler`] into a [`HandlerService`].
1953
///
2054
/// Since [`Handler`] is not exposed, the [`Handler::into_service`] cannot be called outside of

0 commit comments

Comments
 (0)