1use std::collections::HashMap;
4use std::sync::Arc;
5
6use regex::Regex;
7use serde::Serialize;
8use tokio::sync::RwLock;
9
10#[derive(Clone)]
12pub struct MockHttp {
13 mocks: Arc<RwLock<Vec<MockHandler>>>,
14 requests: Arc<RwLock<Vec<RecordedRequest>>>,
15}
16
17struct MockHandler {
19 #[allow(dead_code)]
20 pattern: String,
21 regex: Regex,
22 handler: Arc<dyn Fn(&MockRequest) -> MockResponse + Send + Sync>,
23}
24
25#[derive(Debug, Clone)]
27pub struct RecordedRequest {
28 pub method: String,
30 pub url: String,
32 pub headers: HashMap<String, String>,
34 pub body: serde_json::Value,
36}
37
38#[derive(Debug, Clone)]
40pub struct MockRequest {
41 pub method: String,
43 pub path: String,
45 pub url: String,
47 pub headers: HashMap<String, String>,
49 pub body: serde_json::Value,
51}
52
53#[derive(Debug, Clone)]
55pub struct MockResponse {
56 pub status: u16,
58 pub headers: HashMap<String, String>,
60 pub body: serde_json::Value,
62}
63
64impl MockResponse {
65 pub fn json<T: Serialize>(body: T) -> Self {
67 Self {
68 status: 200,
69 headers: HashMap::from([("content-type".to_string(), "application/json".to_string())]),
70 body: serde_json::to_value(body).unwrap_or(serde_json::Value::Null),
71 }
72 }
73
74 pub fn error(status: u16, message: &str) -> Self {
76 Self {
77 status,
78 headers: HashMap::from([("content-type".to_string(), "application/json".to_string())]),
79 body: serde_json::json!({ "error": message }),
80 }
81 }
82
83 pub fn internal_error(message: &str) -> Self {
85 Self::error(500, message)
86 }
87
88 pub fn not_found(message: &str) -> Self {
90 Self::error(404, message)
91 }
92
93 pub fn unauthorized(message: &str) -> Self {
95 Self::error(401, message)
96 }
97
98 pub fn ok() -> Self {
100 Self::json(serde_json::json!({}))
101 }
102}
103
104impl MockHttp {
105 pub fn new() -> Self {
107 Self {
108 mocks: Arc::new(RwLock::new(Vec::new())),
109 requests: Arc::new(RwLock::new(Vec::new())),
110 }
111 }
112
113 pub fn add_mock<F>(&mut self, pattern: &str, handler: F)
115 where
116 F: Fn(&MockRequest) -> MockResponse + Send + Sync + 'static,
117 {
118 let regex_pattern = pattern
120 .replace('.', "\\.")
121 .replace('*', ".*")
122 .replace('?', ".");
123
124 let regex = Regex::new(&format!("^{}$", regex_pattern)).unwrap();
125
126 let mocks = self.mocks.clone();
128 tokio::task::block_in_place(|| {
129 let rt = tokio::runtime::Handle::try_current();
130 if let Ok(rt) = rt {
131 rt.block_on(async {
132 let mut mocks = mocks.write().await;
133 mocks.push(MockHandler {
134 pattern: pattern.to_string(),
135 regex,
136 handler: Arc::new(handler),
137 });
138 });
139 }
140 });
141 }
142
143 #[allow(unused_variables)]
145 pub fn add_mock_sync<F>(&self, pattern: &str, handler: F)
146 where
147 F: Fn(&MockRequest) -> MockResponse + Send + Sync + 'static,
148 {
149 let regex_pattern = pattern
150 .replace('.', "\\.")
151 .replace('*', ".*")
152 .replace('?', ".");
153
154 let _regex = Regex::new(&format!("^{}$", regex_pattern)).unwrap();
155
156 }
159
160 pub async fn execute(&self, request: MockRequest) -> MockResponse {
162 {
164 let mut requests = self.requests.write().await;
165 requests.push(RecordedRequest {
166 method: request.method.clone(),
167 url: request.url.clone(),
168 headers: request.headers.clone(),
169 body: request.body.clone(),
170 });
171 }
172
173 let mocks = self.mocks.read().await;
175 for mock in mocks.iter() {
176 if mock.regex.is_match(&request.url) || mock.regex.is_match(&request.path) {
177 return (mock.handler)(&request);
178 }
179 }
180
181 MockResponse::error(500, &format!("No mock found for {}", request.url))
183 }
184
185 pub async fn requests(&self) -> Vec<RecordedRequest> {
187 self.requests.read().await.clone()
188 }
189
190 pub async fn requests_to(&self, pattern: &str) -> Vec<RecordedRequest> {
192 let regex_pattern = pattern
193 .replace('.', "\\.")
194 .replace('*', ".*")
195 .replace('?', ".");
196 let regex = Regex::new(&format!("^{}$", regex_pattern)).unwrap();
197
198 self.requests
199 .read()
200 .await
201 .iter()
202 .filter(|r| regex.is_match(&r.url))
203 .cloned()
204 .collect()
205 }
206
207 pub async fn clear_requests(&self) {
209 self.requests.write().await.clear();
210 }
211
212 pub async fn clear_mocks(&self) {
214 self.mocks.write().await.clear();
215 }
216}
217
218impl Default for MockHttp {
219 fn default() -> Self {
220 Self::new()
221 }
222}
223
224type MockHandlerFn = Box<dyn Fn(&MockRequest) -> MockResponse + Send + Sync>;
226
227pub struct MockHttpBuilder {
229 mocks: Vec<(String, MockHandlerFn)>,
230}
231
232impl MockHttpBuilder {
233 pub fn new() -> Self {
235 Self { mocks: Vec::new() }
236 }
237
238 pub fn mock<F>(mut self, pattern: &str, handler: F) -> Self
240 where
241 F: Fn(&MockRequest) -> MockResponse + Send + Sync + 'static,
242 {
243 self.mocks.push((pattern.to_string(), Box::new(handler)));
244 self
245 }
246
247 pub fn build(self) -> MockHttp {
249 MockHttp::new()
251 }
252}
253
254impl Default for MockHttpBuilder {
255 fn default() -> Self {
256 Self::new()
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263
264 #[test]
265 fn test_mock_response_json() {
266 let response = MockResponse::json(serde_json::json!({"id": 123}));
267 assert_eq!(response.status, 200);
268 assert_eq!(response.body["id"], 123);
269 }
270
271 #[test]
272 fn test_mock_response_error() {
273 let response = MockResponse::error(404, "Not found");
274 assert_eq!(response.status, 404);
275 assert_eq!(response.body["error"], "Not found");
276 }
277
278 #[test]
279 fn test_mock_response_internal_error() {
280 let response = MockResponse::internal_error("Server error");
281 assert_eq!(response.status, 500);
282 }
283
284 #[test]
285 fn test_mock_response_not_found() {
286 let response = MockResponse::not_found("Resource not found");
287 assert_eq!(response.status, 404);
288 }
289
290 #[test]
291 fn test_mock_response_unauthorized() {
292 let response = MockResponse::unauthorized("Invalid token");
293 assert_eq!(response.status, 401);
294 }
295
296 #[tokio::test]
297 async fn test_mock_http_no_handler() {
298 let mock = MockHttp::new();
299 let request = MockRequest {
300 method: "GET".to_string(),
301 path: "/test".to_string(),
302 url: "https://example.com/test".to_string(),
303 headers: HashMap::new(),
304 body: serde_json::Value::Null,
305 };
306
307 let response = mock.execute(request).await;
308 assert_eq!(response.status, 500);
309 }
310
311 #[tokio::test]
312 async fn test_mock_http_records_requests() {
313 let mock = MockHttp::new();
314 let request = MockRequest {
315 method: "POST".to_string(),
316 path: "/api/users".to_string(),
317 url: "https://api.example.com/users".to_string(),
318 headers: HashMap::from([("authorization".to_string(), "Bearer token".to_string())]),
319 body: serde_json::json!({"name": "Test"}),
320 };
321
322 let _ = mock.execute(request).await;
323
324 let requests = mock.requests().await;
325 assert_eq!(requests.len(), 1);
326 assert_eq!(requests[0].method, "POST");
327 assert_eq!(requests[0].body["name"], "Test");
328 }
329
330 #[tokio::test]
331 async fn test_mock_http_clear_requests() {
332 let mock = MockHttp::new();
333 let request = MockRequest {
334 method: "GET".to_string(),
335 path: "/test".to_string(),
336 url: "https://example.com/test".to_string(),
337 headers: HashMap::new(),
338 body: serde_json::Value::Null,
339 };
340
341 let _ = mock.execute(request).await;
342 assert_eq!(mock.requests().await.len(), 1);
343
344 mock.clear_requests().await;
345 assert_eq!(mock.requests().await.len(), 0);
346 }
347}