refactor(app): `MockBody` convenience functions for test code (#3349)

* refactor(app): add `pending()` helper for mock body

in timeout tests, we construct mock bodies that are backed by a
`Pending<T>` future that will never yield any data.

this introduces a small `pending()` helper to the `MockBody` type used
in test code in the outbound proxy.

Signed-off-by: katelyn martin <kate@buoyant.io>

* refactor(app): add `error()` helper for mock body

in metrics tests, we construct mock bodies that will yield an error
when polled, to examine how telemetry measures certain edge cases.

this introduces a small `error()` helper to the `MockBody` type used
in test code in the outbound proxy.

Signed-off-by: katelyn martin <kate@buoyant.io>

* refactor(app): add `grpc_status()` helper for mock body

in metrics tests, we construct mock bodies that will yield an gRPC
status code in its trailers section
when polled, to examine how telemetry measures gRPC responses.

this refactors the `trailers()` function to more narrowly focus on the
test coverage needed, to help reduce boilerplate in test code.

Signed-off-by: katelyn martin <kate@buoyant.io>

---------

Signed-off-by: katelyn martin <kate@buoyant.io>
This commit is contained in:
katelyn martin 2024-11-20 13:54:01 -05:00 committed by GitHub
parent 4dc979fd31
commit 12905c5edf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 38 additions and 56 deletions

View File

@ -101,9 +101,7 @@ async fn http_request_statuses() {
tx.send_response(
http::Response::builder()
.status(200)
.body(BoxBody::new(MockBody::new(async {
Err("a spooky ghost".into())
})))
.body(BoxBody::new(MockBody::error("a spooky ghost")))
.unwrap(),
)
})
@ -150,11 +148,7 @@ async fn grpc_request_statuses_ok() {
|tx| {
tx.send_response(
http::Response::builder()
.body(BoxBody::new(MockBody::trailers(async move {
let mut trailers = http::HeaderMap::new();
trailers.insert("grpc-status", http::HeaderValue::from_static("0"));
Ok(Some(trailers))
})))
.body(BoxBody::new(MockBody::grpc_status(0)))
.unwrap(),
)
},
@ -193,11 +187,7 @@ async fn grpc_request_statuses_not_found() {
|tx| {
tx.send_response(
http::Response::builder()
.body(BoxBody::new(MockBody::trailers(async move {
let mut trailers = http::HeaderMap::new();
trailers.insert("grpc-status", http::HeaderValue::from_static("5"));
Ok(Some(trailers))
})))
.body(BoxBody::new(MockBody::grpc_status(5)))
.unwrap(),
)
},
@ -267,9 +257,7 @@ async fn grpc_request_statuses_error_body() {
|tx| {
tx.send_response(
http::Response::builder()
.body(BoxBody::new(MockBody::new(async {
Err("a spooky ghost".into())
})))
.body(BoxBody::new(MockBody::error("a spooky ghost")))
.unwrap(),
)
},

View File

@ -92,9 +92,7 @@ async fn http_request_statuses() {
tx.send_response(
http::Response::builder()
.status(200)
.body(BoxBody::new(MockBody::new(async {
Err("a spooky ghost".into())
})))
.body(BoxBody::new(MockBody::error("a spooky ghost")))
.unwrap(),
)
})
@ -264,11 +262,7 @@ async fn grpc_request_statuses_ok() {
|tx| {
tx.send_response(
http::Response::builder()
.body(BoxBody::new(MockBody::trailers(async move {
let mut trailers = http::HeaderMap::new();
trailers.insert("grpc-status", http::HeaderValue::from_static("0"));
Ok(Some(trailers))
})))
.body(BoxBody::new(MockBody::grpc_status(0)))
.unwrap(),
)
},
@ -310,11 +304,7 @@ async fn grpc_request_statuses_not_found() {
|tx| {
tx.send_response(
http::Response::builder()
.body(BoxBody::new(MockBody::trailers(async move {
let mut trailers = http::HeaderMap::new();
trailers.insert("grpc-status", http::HeaderValue::from_static("5"));
Ok(Some(trailers))
})))
.body(BoxBody::new(MockBody::grpc_status(5)))
.unwrap(),
)
},
@ -388,9 +378,7 @@ async fn grpc_request_statuses_error_body() {
|tx| {
tx.send_response(
http::Response::builder()
.body(BoxBody::new(MockBody::new(async {
Err("a spooky ghost".into())
})))
.body(BoxBody::new(MockBody::error("a spooky ghost")))
.unwrap(),
)
},

View File

@ -131,11 +131,7 @@ async fn mk_grpc_rsp(code: tonic::Code) -> Result<Response> {
Ok(http::Response::builder()
.version(::http::Version::HTTP_2)
.header("content-type", "application/grpc")
.body(BoxBody::new(MockBody::trailers(async move {
let mut trls = http::HeaderMap::default();
trls.insert("grpc-status", (code as u8).to_string().parse().unwrap());
Ok(Some(trls))
})))
.body(BoxBody::new(MockBody::grpc_status(code as u8)))
.unwrap())
}

View File

@ -74,9 +74,7 @@ async fn request_timeout_request_body() {
svc.clone(),
http::Request::builder()
.method("POST")
.body(BoxBody::new(MockBody::new(async move {
futures::future::pending().await
})))
.body(BoxBody::new(MockBody::pending()))
.unwrap(),
);
@ -119,9 +117,7 @@ async fn request_timeout_response_body() {
future::ok(
http::Response::builder()
.status(200)
.body(http::BoxBody::new(MockBody::new(async move {
futures::future::pending().await
})))
.body(BoxBody::new(MockBody::pending()))
.unwrap(),
),
)
@ -203,9 +199,7 @@ async fn response_timeout_response_body() {
info!("Serving a response that never completes");
Ok(http::Response::builder()
.status(200)
.body(http::BoxBody::new(MockBody::new(async move {
futures::future::pending().await
})))
.body(http::BoxBody::new(MockBody::pending()))
.unwrap())
})
.await;
@ -252,9 +246,7 @@ async fn response_timeout_ignores_request_body() {
info!("Serving a response that never completes");
Ok(http::Response::builder()
.status(200)
.body(http::BoxBody::new(MockBody::new(async move {
futures::future::pending().await
})))
.body(http::BoxBody::new(MockBody::pending()))
.unwrap())
})
.await;
@ -286,9 +278,7 @@ async fn idle_timeout_response_body() {
info!("Serving a response that never completes");
Ok(http::Response::builder()
.status(200)
.body(http::BoxBody::new(MockBody::new(async move {
futures::future::pending().await
})))
.body(http::BoxBody::new(MockBody::pending()))
.unwrap())
})
.await;

View File

@ -98,12 +98,32 @@ mod mock_body {
}
}
pub fn trailers(
trailers: impl Future<Output = Result<Option<http::HeaderMap>>> + Send + 'static,
) -> Self {
/// Returns a [`MockBody`] that never yields any data.
pub fn pending() -> Self {
let fut = futures::future::pending();
Self::new(fut)
}
/// Returns a [`MockBody`] that yields an error when polled.
pub fn error(msg: &'static str) -> Self {
let err = Err(msg.into());
let fut = futures::future::ready(err);
Self::new(fut)
}
/// Returns a [`MockBody`] that yields this gRPC code in its trailers section.
pub fn grpc_status(code: u8) -> Self {
let trailers = {
let mut trailers = http::HeaderMap::with_capacity(1);
let status = code.to_string().parse().unwrap();
trailers.insert("grpc-status", status);
trailers
};
let fut = futures::future::ready(Ok(Some(trailers)));
Self {
data: None,
trailers: Some(Box::pin(trailers)),
trailers: Some(Box::pin(fut)),
}
}
}