use std::collections::HashMap;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct MockRequest {
pub method: String,
pub path: String,
pub headers: HashMap<String, String>,
pub body: Vec<u8>,
}
impl MockRequest {
pub fn new(method: impl Into<String>, path: impl Into<String>) -> Self {
Self {
method: method.into(),
path: path.into(),
headers: HashMap::new(),
body: vec![],
}
}
pub fn with_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.headers.insert(key.into(), value.into());
self
}
pub fn with_body(mut self, body: impl Into<Vec<u8>>) -> Self {
self.body = body.into();
self
}
}
impl Default for MockRequest {
fn default() -> Self {
Self::new("GET", "/")
}
}
#[derive(Debug, Clone)]
pub struct MockResponse {
pub status: u16,
pub headers: HashMap<String, String>,
pub body: Vec<u8>,
}
impl MockResponse {
pub fn new(status: u16) -> Self {
Self {
status,
headers: HashMap::new(),
body: vec![],
}
}
pub fn with_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.headers.insert(key.into(), value.into());
self
}
pub fn with_body(mut self, body: impl Into<Vec<u8>>) -> Self {
self.body = body.into();
self
}
pub fn json<T: serde::Serialize>(status: u16, value: &T) -> Result<Self, serde_json::Error> {
let body = serde_json::to_vec(value)?;
Ok(Self::new(status).with_body(body).with_header("Content-Type", "application/json"))
}
}
impl Default for MockResponse {
fn default() -> Self {
Self::new(200)
}
}
pub type MockHandler = Arc<dyn Fn(MockRequest) -> MockResponse + Send + Sync>;
#[derive(Clone, Default)]
pub struct MockRouter {
routes: HashMap<String, MockHandler>,
default_handler: Option<MockHandler>,
}
impl MockRouter {
pub fn new() -> Self {
Self::default()
}
pub fn add_route<F>(mut self, path: impl Into<String>, handler: F) -> Self
where
F: Fn(MockRequest) -> MockResponse + Send + Sync + 'static,
{
self.routes.insert(path.into(), Arc::new(handler));
self
}
pub fn with_default<F>(mut self, handler: F) -> Self
where
F: Fn(MockRequest) -> MockResponse + Send + Sync + 'static,
{
self.default_handler = Some(Arc::new(handler));
self
}
pub fn handle(&self, request: MockRequest) -> MockResponse {
if let Some(handler) = self.routes.get(&request.path) {
handler(request)
} else if let Some(default) = &self.default_handler {
default(request)
} else {
MockResponse::new(404).with_body("Not found")
}
}
}
#[derive(Clone)]
pub struct MockService {
router: Arc<MockRouter>,
pub name: String,
}
impl MockService {
pub fn new(name: impl Into<String>) -> Self {
Self {
router: Arc::new(MockRouter::new()),
name: name.into(),
}
}
pub fn add_route<F>(self, path: impl Into<String>, handler: F) -> Self
where
F: Fn(MockRequest) -> MockResponse + Send + Sync + 'static,
{
let mut router = (*self.router).clone();
router = router.add_route(path, handler);
Self {
router: Arc::new(router),
name: self.name,
}
}
pub fn handle(&self, request: MockRequest) -> MockResponse {
self.router.handle(request)
}
}
impl Default for MockService {
fn default() -> Self {
Self::new("mock-service")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mock_request_new() {
let req = MockRequest::new("GET", "/health");
assert_eq!(req.method, "GET");
assert_eq!(req.path, "/health");
}
#[test]
fn test_mock_request_with_header() {
let req = MockRequest::new("GET", "/health")
.with_header("Authorization", "Bearer token");
assert_eq!(req.headers.get("Authorization"), Some(&"Bearer token".to_string()));
}
#[test]
fn test_mock_response_new() {
let resp = MockResponse::new(200);
assert_eq!(resp.status, 200);
}
#[test]
fn test_mock_response_json() {
#[derive(serde::Serialize)]
struct Data {
message: String,
}
let data = Data {
message: "hello".to_string(),
};
let resp = MockResponse::json(200, &data).unwrap();
assert_eq!(resp.status, 200);
assert_eq!(resp.body, b"{\"message\":\"hello\"}");
}
#[test]
fn test_mock_router_add_route() {
let router = MockRouter::new()
.add_route("/health", |_| MockResponse::new(200))
.with_default(|_| MockResponse::new(404));
let resp = router.handle(MockRequest::new("GET", "/health"));
assert_eq!(resp.status, 200);
let resp = router.handle(MockRequest::new("GET", "/other"));
assert_eq!(resp.status, 404);
}
#[test]
fn test_mock_service_new() {
let service = MockService::new("test-service");
assert_eq!(service.name, "test-service");
}
#[test]
fn test_mock_service_add_route() {
let service = MockService::new("test-service")
.add_route("/health", |_| MockResponse::new(200));
let resp = service.handle(MockRequest::new("GET", "/health"));
assert_eq!(resp.status, 200);
}
}