buster/api/tests/common/http/mock_server.rs

197 lines
5.8 KiB
Rust

use anyhow::Result;
use serde_json::Value;
use std::collections::HashMap;
/// Wrapper around mockito server for easier mock creation and tracking
pub struct MockServer {
server: mockito::Server,
mock_responses: HashMap<String, Vec<mockito::Mock>>,
}
impl MockServer {
/// Create a new mock server
pub async fn new() -> Result<Self> {
Ok(Self {
server: mockito::Server::new_async().await,
mock_responses: HashMap::new(),
})
}
/// Get the base URL for the mock server
pub fn url(&self) -> String {
self.server.url()
}
/// Mock a successful JSON response
pub fn mock_json<T: serde::Serialize>(
&mut self,
method: &str,
path: &str,
status: usize,
body: &T
) -> Result<&mockito::Mock> {
let body_str = serde_json::to_string(body)?;
let mock = self.server
.mock(method, path)
.with_status(status)
.with_header("content-type", "application/json")
.with_body(body_str)
.create();
let key = format!("{}:{}", method, path);
self.mock_responses.entry(key).or_insert_with(Vec::new).push(mock);
// Return reference to the created mock
let mocks = self.mock_responses.get(key.as_str()).unwrap();
Ok(&mocks[mocks.len() - 1])
}
/// Mock an error response
pub fn mock_error(
&mut self,
method: &str,
path: &str,
status: usize,
error_message: &str
) -> Result<&mockito::Mock> {
let body = serde_json::json!({
"error": error_message
});
self.mock_json(method, path, status, &body)
}
/// Mock a response with specific matcher for the request body
pub fn mock_with_body_match<M, T>(
&mut self,
method: &str,
path: &str,
body_matcher: M,
response_body: &T,
status: usize,
) -> Result<&mockito::Mock>
where
M: Into<mockito::Matcher>,
T: serde::Serialize,
{
let body_str = serde_json::to_string(response_body)?;
let mock = self.server
.mock(method, path)
.match_body(body_matcher)
.with_status(status)
.with_header("content-type", "application/json")
.with_body(body_str)
.create();
let key = format!("{}:{}", method, path);
self.mock_responses.entry(key).or_insert_with(Vec::new).push(mock);
let mocks = self.mock_responses.get(key.as_str()).unwrap();
Ok(&mocks[mocks.len() - 1])
}
/// Mock a response with custom headers
pub fn mock_with_headers<T: serde::Serialize>(
&mut self,
method: &str,
path: &str,
status: usize,
headers: &[(&str, &str)],
body: &T,
) -> Result<&mockito::Mock> {
let body_str = serde_json::to_string(body)?;
// Create a mock builder
let mut mock_builder = self.server
.mock(method, path)
.with_status(status);
// Add headers
for (name, value) in headers {
mock_builder = mock_builder.with_header(name, value);
}
// Add body and create mock
let mock = mock_builder
.with_body(body_str)
.create();
let key = format!("{}:{}", method, path);
self.mock_responses.entry(key).or_insert_with(Vec::new).push(mock);
let mocks = self.mock_responses.get(key.as_str()).unwrap();
Ok(&mocks[mocks.len() - 1])
}
/// Verify all registered mocks have been called
pub fn verify_all_mocks_called(&self) -> Result<()> {
for (endpoint, mocks) in &self.mock_responses {
for (i, mock) in mocks.iter().enumerate() {
match mock.matched() {
true => {}
false => return Err(anyhow::anyhow!("Mock not matched for endpoint '{}' (index: {})", endpoint, i)),
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use reqwest::Client;
#[tokio::test]
async fn test_mock_json() -> Result<()> {
let mut server = MockServer::new().await?;
// Create a mock response
let response_data = serde_json::json!({
"id": 123,
"name": "Test User"
});
let _mock = server.mock_json("GET", "/users/123", 200, &response_data)?;
// Make a request to the mock server
let client = Client::new();
let response = client.get(&format!("{}/users/123", server.url()))
.send()
.await?;
assert_eq!(response.status().as_u16(), 200);
let body: Value = response.json().await?;
assert_eq!(body["id"], 123);
assert_eq!(body["name"], "Test User");
// Verify the mock was called
server.verify_all_mocks_called()?;
Ok(())
}
#[tokio::test]
async fn test_mock_error() -> Result<()> {
let mut server = MockServer::new().await?;
// Create an error mock
let _mock = server.mock_error("GET", "/users/invalid", 404, "User not found")?;
// Make a request to the mock server
let client = Client::new();
let response = client.get(&format!("{}/users/invalid", server.url()))
.send()
.await?;
assert_eq!(response.status().as_u16(), 404);
let body: Value = response.json().await?;
assert_eq!(body["error"], "User not found");
// Verify the mock was called
server.verify_all_mocks_called()?;
Ok(())
}
}