add tests for WithRejection<Valid<...>>

This commit is contained in:
gengteng
2023-08-05 11:57:09 +08:00
parent 58d48b66b3
commit 7134197a55
3 changed files with 107 additions and 17 deletions

View File

@@ -30,6 +30,7 @@ impl<T: Validate, R> HasValidate for WithRejection<T, R> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::tests::{Rejection, ValidTest}; use crate::tests::{Rejection, ValidTest};
use crate::Valid;
use axum::http::StatusCode; use axum::http::StatusCode;
use axum_extra::extract::{Cached, WithRejection}; use axum_extra::extract::{Cached, WithRejection};
use reqwest::RequestBuilder; use reqwest::RequestBuilder;
@@ -52,7 +53,6 @@ mod tests {
} }
impl<T: ValidTest, R: Rejection> ValidTest for WithRejection<T, R> { impl<T: ValidTest, R: Rejection> ValidTest for WithRejection<T, R> {
// just use conflict to test
const ERROR_STATUS_CODE: StatusCode = R::STATUS_CODE; const ERROR_STATUS_CODE: StatusCode = R::STATUS_CODE;
fn set_valid_request(builder: RequestBuilder) -> RequestBuilder { fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
@@ -68,4 +68,28 @@ mod tests {
T::set_invalid_request(builder) T::set_invalid_request(builder)
} }
} }
impl<T: ValidTest, R> ValidTest for WithRejection<Valid<T>, R> {
// just use `418 I'm a teapot` to test
const ERROR_STATUS_CODE: StatusCode = StatusCode::IM_A_TEAPOT;
// If `WithRejection` is the outermost extractor,
// the error code returned will always be the one provided by WithRejection.
const INVALID_STATUS_CODE: StatusCode = StatusCode::IM_A_TEAPOT;
// If `WithRejection` is the outermost extractor,
// the returned body may not be in JSON format.
const JSON_SERIALIZABLE: bool = false;
fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
T::set_valid_request(builder)
}
fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
// invalid requests will cause the Valid extractor to fail.
T::set_invalid_request(builder)
}
fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
T::set_invalid_request(builder)
}
}
} }

View File

@@ -155,8 +155,13 @@ pub mod tests {
/// 3. For an invalid request according to Valid, the server should return VALIDATION_ERROR_STATUS as the error code. /// 3. For an invalid request according to Valid, the server should return VALIDATION_ERROR_STATUS as the error code.
/// ///
pub trait ValidTest { pub trait ValidTest {
/// Http status code when inner extractor failed /// The HTTP status code returned when inner extractor failed.
const ERROR_STATUS_CODE: StatusCode; const ERROR_STATUS_CODE: StatusCode;
/// The HTTP status code returned when the outer extractor fails.
/// Use crate::VALIDATION_ERROR_STATUS by default.
const INVALID_STATUS_CODE: StatusCode = crate::VALIDATION_ERROR_STATUS;
/// If the response body can be serialized into JSON format
const JSON_SERIALIZABLE: bool = true;
/// Build a valid request, the server should return `200 OK`. /// Build a valid request, the server should return `200 OK`.
fn set_valid_request(builder: RequestBuilder) -> RequestBuilder; fn set_valid_request(builder: RequestBuilder) -> RequestBuilder;
/// Build an invalid request according to the extractor, the server should return `Self::ERROR_STATUS_CODE` /// Build an invalid request according to the extractor, the server should return `Self::ERROR_STATUS_CODE`

View File

@@ -1,5 +1,6 @@
use crate::test::extra::{ParametersRejection, WithRejectionValidRejection};
use crate::tests::{ValidTest, ValidTestParameter}; use crate::tests::{ValidTest, ValidTestParameter};
use crate::{Valid, VALIDATION_ERROR_STATUS}; use crate::{HasValidate, Valid, VALIDATION_ERROR_STATUS};
use axum::extract::{Path, Query}; use axum::extract::{Path, Query};
use axum::routing::{get, post}; use axum::routing::{get, post};
use axum::{Form, Json, Router}; use axum::{Form, Json, Router};
@@ -47,6 +48,14 @@ impl ValidTestParameter for Parameters {
} }
} }
impl HasValidate for Parameters {
type Validate = Parameters;
fn get_validate(&self) -> &Self::Validate {
self
}
}
#[tokio::test] #[tokio::test]
async fn test_main() -> anyhow::Result<()> { async fn test_main() -> anyhow::Result<()> {
let router = Router::new() let router = Router::new()
@@ -67,6 +76,10 @@ async fn test_main() -> anyhow::Result<()> {
.route( .route(
extra::route::WITH_REJECTION, extra::route::WITH_REJECTION,
post(extra::extract_with_rejection), post(extra::extract_with_rejection),
)
.route(
extra::route::WITH_REJECTION_VALID,
post(extra::extract_with_rejection_valid),
); );
#[cfg(feature = "extra_query")] #[cfg(feature = "extra_query")]
@@ -182,16 +195,22 @@ async fn test_main() -> anyhow::Result<()> {
#[cfg(feature = "extra")] #[cfg(feature = "extra")]
{ {
use axum_extra::extract::{Cached, WithRejection}; use axum_extra::extract::{Cached, WithRejection};
use extra::TestRejection; use extra::ValidWithRejectionRejection;
test_executor test_executor
.execute::<Cached<Parameters>>(Method::POST, extra::route::CACHED) .execute::<Cached<Parameters>>(Method::POST, extra::route::CACHED)
.await?; .await?;
test_executor test_executor
.execute::<WithRejection<Parameters, TestRejection>>( .execute::<WithRejection<Parameters, ValidWithRejectionRejection>>(
Method::POST, Method::POST,
extra::route::WITH_REJECTION, extra::route::WITH_REJECTION,
) )
.await?; .await?;
test_executor
.execute::<WithRejection<Valid<Parameters>, WithRejectionValidRejection<ParametersRejection>>>(
Method::POST,
extra::route::WITH_REJECTION_VALID,
)
.await?;
} }
#[cfg(feature = "extra_query")] #[cfg(feature = "extra_query")]
@@ -290,12 +309,15 @@ impl TestExecutor {
let invalid_response = T::set_invalid_request(invalid_builder).send().await?; let invalid_response = T::set_invalid_request(invalid_builder).send().await?;
assert_eq!( assert_eq!(
invalid_response.status(), invalid_response.status(),
VALIDATION_ERROR_STATUS, T::INVALID_STATUS_CODE,
"Invalid '{}' test failed.", "Invalid '{}' test failed.",
type_name type_name
); );
#[cfg(feature = "into_json")] #[cfg(feature = "into_json")]
check_json(type_name, invalid_response).await; if T::JSON_SERIALIZABLE {
check_json(type_name, invalid_response).await;
}
println!("All '{}' tests passed.", type_name); println!("All '{}' tests passed.", type_name);
Ok(()) Ok(())
@@ -424,7 +446,7 @@ mod typed_header {
mod extra { mod extra {
use crate::test::{validate_again, Parameters}; use crate::test::{validate_again, Parameters};
use crate::tests::{Rejection, ValidTest, ValidTestParameter}; use crate::tests::{Rejection, ValidTest, ValidTestParameter};
use crate::Valid; use crate::{Valid, ValidRejection};
use axum::extract::FromRequestParts; use axum::extract::FromRequestParts;
use axum::http::request::Parts; use axum::http::request::Parts;
use axum::http::StatusCode; use axum::http::StatusCode;
@@ -435,6 +457,7 @@ mod extra {
pub mod route { pub mod route {
pub const CACHED: &str = "/cached"; pub const CACHED: &str = "/cached";
pub const WITH_REJECTION: &str = "/with_rejection"; pub const WITH_REJECTION: &str = "/with_rejection";
pub const WITH_REJECTION_VALID: &str = "/with_rejection_valid";
} }
pub const PARAMETERS_HEADER: &str = "parameters-header"; pub const PARAMETERS_HEADER: &str = "parameters-header";
pub const CACHED_REJECTION_STATUS: StatusCode = StatusCode::FORBIDDEN; pub const CACHED_REJECTION_STATUS: StatusCode = StatusCode::FORBIDDEN;
@@ -503,25 +526,27 @@ mod extra {
} }
} }
pub struct TestRejection { pub struct ValidWithRejectionRejection {
_inner: ParametersRejection, inner: ParametersRejection,
} }
impl Rejection for TestRejection { impl Rejection for ValidWithRejectionRejection {
const STATUS_CODE: StatusCode = StatusCode::CONFLICT; const STATUS_CODE: StatusCode = StatusCode::CONFLICT;
} }
impl IntoResponse for TestRejection { impl IntoResponse for ValidWithRejectionRejection {
fn into_response(self) -> Response { fn into_response(self) -> Response {
Self::STATUS_CODE.into_response() let mut response = self.inner.into_response();
*response.status_mut() = Self::STATUS_CODE;
response
} }
} }
// satisfy the `WithRejection`'s extractor trait bound // satisfy the `WithRejection`'s extractor trait bound
// R: From<E::Rejection> + IntoResponse // R: From<E::Rejection> + IntoResponse
impl From<ParametersRejection> for TestRejection { impl From<ParametersRejection> for ValidWithRejectionRejection {
fn from(_inner: ParametersRejection) -> Self { fn from(inner: ParametersRejection) -> Self {
Self { _inner } Self { inner }
} }
} }
@@ -532,7 +557,43 @@ mod extra {
} }
pub async fn extract_with_rejection( pub async fn extract_with_rejection(
Valid(WithRejection(parameters, _)): Valid<WithRejection<Parameters, TestRejection>>, Valid(WithRejection(parameters, _)): Valid<
WithRejection<Parameters, ValidWithRejectionRejection>,
>,
) -> StatusCode {
validate_again(parameters)
}
pub struct WithRejectionValidRejection<E> {
inner: ValidRejection<E>,
}
impl<E> From<ValidRejection<E>> for WithRejectionValidRejection<E> {
fn from(inner: ValidRejection<E>) -> Self {
Self { inner }
}
}
impl<E: IntoResponse> IntoResponse for WithRejectionValidRejection<E> {
fn into_response(self) -> Response {
match self.inner {
ValidRejection::Valid(v) => {
(StatusCode::IM_A_TEAPOT, v.to_string()).into_response()
}
ValidRejection::Inner(i) => {
let mut res = i.into_response();
*res.status_mut() = StatusCode::IM_A_TEAPOT;
res
}
}
}
}
pub async fn extract_with_rejection_valid(
WithRejection(Valid(parameters), _): WithRejection<
Valid<Parameters>,
WithRejectionValidRejection<ParametersRejection>,
>,
) -> StatusCode { ) -> StatusCode {
validate_again(parameters) validate_again(parameters)
} }