use std::collections::HashMap;
use crate::bytes::Bytes;
use crate::util::DetRng;
use crate::web::extract::Request;
use crate::web::response::Response;
use super::server::VirtualServer;
pub struct VirtualClient<'a> {
server: &'a VirtualServer,
}
impl<'a> VirtualClient<'a> {
#[must_use]
pub fn new(server: &'a VirtualServer) -> Self {
Self { server }
}
#[must_use]
pub fn get(&self, path: &str) -> Response {
self.server.handle(Request::new("GET", path))
}
pub fn post(&self, path: &str, body: impl Into<Bytes>) -> Response {
let mut req = Request::new("POST", path);
req.body = body.into();
req.headers.insert(
"content-type".to_string(),
"application/octet-stream".to_string(),
);
self.server.handle(req)
}
#[must_use]
pub fn post_json(&self, path: &str, json: &str) -> Response {
let mut req = Request::new("POST", path);
req.body = Bytes::from(json.to_string());
req.headers
.insert("content-type".to_string(), "application/json".to_string());
self.server.handle(req)
}
pub fn put(&self, path: &str, body: impl Into<Bytes>) -> Response {
let mut req = Request::new("PUT", path);
req.body = body.into();
self.server.handle(req)
}
#[must_use]
pub fn delete(&self, path: &str) -> Response {
self.server.handle(Request::new("DELETE", path))
}
pub fn get_batch(&self, paths: &[&str], rng: &mut DetRng) -> Vec<Response> {
let mut indices: Vec<usize> = (0..paths.len()).collect();
rng.shuffle(&mut indices);
let mut responses = vec![None; paths.len()];
for &idx in &indices {
responses[idx] = Some(self.get(paths[idx]));
}
responses
.into_iter()
.map(|r| r.expect("response should be present"))
.collect()
}
#[must_use]
pub fn send(&self, req: Request) -> Response {
self.server.handle(req)
}
#[must_use]
pub fn request(&'a self, method: &str, path: &str) -> RequestBuilder<'a> {
RequestBuilder {
client: self,
method: method.to_string(),
path: path.to_string(),
headers: HashMap::new(),
body: Bytes::new(),
}
}
}
pub struct RequestBuilder<'a> {
client: &'a VirtualClient<'a>,
method: String,
path: String,
headers: HashMap<String, String>,
body: Bytes,
}
impl RequestBuilder<'_> {
#[must_use]
pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.headers.insert(name.into(), value.into());
self
}
#[must_use]
pub fn body(mut self, body: impl Into<Bytes>) -> Self {
self.body = body.into();
self
}
#[must_use]
pub fn json(mut self, json: &str) -> Self {
self.body = Bytes::from(json.to_string());
self.headers
.insert("content-type".to_string(), "application/json".to_string());
self
}
#[must_use]
pub fn send(self) -> Response {
let mut req = Request::new(&self.method, &self.path);
req.headers = self.headers;
req.body = self.body;
self.client.send(req)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::web::handler::FnHandler;
use crate::web::response::StatusCode;
use crate::web::router::{Router, get, post};
fn test_server() -> VirtualServer {
let router = Router::new()
.route("/hello", get(FnHandler::new(|| "world")))
.route("/echo", post(FnHandler::new(|| StatusCode::CREATED)));
VirtualServer::new(router)
}
#[test]
fn client_get() {
let server = test_server();
let client = VirtualClient::new(&server);
let resp = client.get("/hello");
assert_eq!(resp.status, StatusCode::OK);
}
#[test]
fn client_post() {
let server = test_server();
let client = VirtualClient::new(&server);
let resp = client.post("/echo", b"data".to_vec());
assert_eq!(resp.status, StatusCode::CREATED);
}
#[test]
fn client_delete() {
let server = test_server();
let client = VirtualClient::new(&server);
let resp = client.delete("/hello");
assert_eq!(resp.status, StatusCode::METHOD_NOT_ALLOWED);
}
#[test]
fn client_get_batch_deterministic() {
let router = Router::new()
.route("/a", get(FnHandler::new(|| "a")))
.route("/b", get(FnHandler::new(|| "b")))
.route("/c", get(FnHandler::new(|| "c")));
let server = VirtualServer::new(router);
let client = VirtualClient::new(&server);
let mut rng1 = DetRng::new(42);
let mut rng2 = DetRng::new(42);
let batch1 = client.get_batch(&["/a", "/b", "/c"], &mut rng1);
let batch2 = client.get_batch(&["/a", "/b", "/c"], &mut rng2);
assert_eq!(batch1.len(), batch2.len());
for (r1, r2) in batch1.iter().zip(batch2.iter()) {
assert_eq!(r1.status, r2.status);
assert_eq!(r1.body, r2.body);
}
assert_eq!(server.request_count(), 6);
}
#[test]
fn client_request_builder() {
let server = test_server();
let client = VirtualClient::new(&server);
let resp = client
.request("GET", "/hello")
.header("x-custom", "value")
.send();
assert_eq!(resp.status, StatusCode::OK);
}
#[test]
fn client_request_builder_json() {
let server = test_server();
let client = VirtualClient::new(&server);
let resp = client
.request("POST", "/echo")
.json(r#"{"key":"value"}"#)
.send();
assert_eq!(resp.status, StatusCode::CREATED);
}
}