blob: 1e609d1c510d91f91ef9c0d776b9448d9cd6fd3b [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Mock HttpsClient for use in testing.
//!
//! Usage:
//! ```
//! let mut https_client = HttpsClient::mock();
//! // Set up expected requests and responses (as many as needed, in the order
//! // they should happen).
//! https_client.expect(req, res);
//! // Use the https_client in-place of the normal https_client. As matching
//! // requests come in, the premade responses will be returned.
//! ```
use {
hyper::{Body, Request},
std::{collections::VecDeque, sync::Mutex},
};
#[derive(Debug)]
struct HttpsClientEvent {
req: Request<Body>,
res: http::Result<http::Response<Body>>,
}
#[derive(Debug)]
pub struct HttpsClient {
expected: Mutex<VecDeque<HttpsClientEvent>>,
}
impl HttpsClient {
/// Create a new mock https client.
///
/// Consider adding expected events with `.expect(req, res)`.
pub fn mock() -> Self {
Self { expected: Mutex::new(VecDeque::new()) }
}
/// Append an expected request and response.
///
/// Note that `res` is actually a Result<> type.
pub fn expect(&mut self, req: Request<Body>, res: http::Result<http::Response<Body>>) {
self.expected.lock().expect("locking").push_back(HttpsClientEvent { req, res });
}
pub async fn request(&self, req: Request<Body>) -> http::Result<http::Response<Body>> {
let expected = self.expected.lock().expect("locking").pop_front().unwrap_or_else(|| {
panic!(
"Error: received more https requests than expected. \
No response available for req: {:?}",
req
)
});
assert_eq!(
expected.req.uri(),
req.uri(),
"mock_https_client actual {:?}, expected {:?}, related response {:?}",
req,
expected.req,
expected.res
);
assert_eq!(expected.req.method(), req.method());
let expected_bytes =
hyper::body::to_bytes(expected.req.into_body()).await.expect("expected.req.into_body");
let req_bytes = hyper::body::to_bytes(req.into_body()).await.expect("req.into_body");
assert_eq!(expected_bytes, req_bytes);
expected.res
}
}
pub fn new_https_client() -> HttpsClient {
HttpsClient::mock()
}
#[cfg(test)]
mod tests {
use {super::*, hyper::Method};
#[fuchsia_async::run_singlethreaded(test)]
async fn test_mock() {
let mut https_client = HttpsClient::mock();
let req = Request::builder()
.method(Method::POST)
.uri("https://example.com")
.body(Body::from("alpha"))
.expect("Request::builder");
let builder = http::Response::builder().status(http::StatusCode::OK);
let res = builder.body(Body::from("beta"));
https_client.expect(req, res);
let req = Request::builder()
.method(Method::POST)
.uri("https://example.com")
.body(Body::from("alpha"))
.expect("Request::builder");
let res = https_client.request(req).await.expect("https_client.request");
assert_eq!(res.status(), http::StatusCode::OK);
let bytes = hyper::body::to_bytes(res.into_body()).await.expect("body::to_bytes");
assert_eq!(b"beta", &bytes[..]);
}
#[should_panic(expected = "actual Request")]
#[fuchsia_async::run_singlethreaded(test)]
async fn test_mock_fail() {
let mut https_client = HttpsClient::mock();
let req = Request::builder()
.method(Method::POST)
.uri("https://example.com")
.body(Body::from("alpha"))
.expect("Request::builder");
let builder = http::Response::builder().status(http::StatusCode::OK);
let res = builder.body(Body::from("beta"));
https_client.expect(req, res);
let req = Request::builder()
.method(Method::POST)
.uri("https://not-same.com")
.body(Body::from("alpha"))
.expect("Request::builder");
let res = https_client.request(req).await.expect("https_client.request");
assert_eq!(res.status(), http::StatusCode::OK);
}
#[test]
fn test_new_https_client() {
let https_client = new_https_client();
assert!(https_client.expected.lock().expect("locking").is_empty());
}
}