Skip to main content

mockforge_sdk/
verification.rs

1//! Verification API for `MockForge` SDK
2//!
3//! Provides methods to verify that specific requests were made (or not made)
4//! during test execution.
5#![allow(async_fn_in_trait)]
6
7use crate::Error;
8use mockforge_core::{
9    request_logger::get_global_logger, verify_at_least, verify_never, verify_requests,
10    verify_sequence, VerificationCount, VerificationRequest, VerificationResult,
11};
12
13/// Extension trait for verification methods on `MockServer`
14pub trait Verification {
15    /// Verify requests against a pattern and count assertion
16    ///
17    /// # Example
18    ///
19    /// ```rust,no_run
20    /// use mockforge_sdk::MockServer;
21    /// use mockforge_sdk::verification::Verification;
22    /// use mockforge_core::verification::{VerificationRequest, VerificationCount};
23    ///
24    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
25    /// let mut server = MockServer::new().port(3000).start().await?;
26    ///
27    /// // Make some requests...
28    ///
29    /// let pattern = VerificationRequest {
30    ///     method: Some("GET".to_string()),
31    ///     path: Some("/api/users".to_string()),
32    ///     query_params: std::collections::HashMap::new(),
33    ///     headers: std::collections::HashMap::new(),
34    ///     body_pattern: None,
35    /// };
36    ///
37    /// let result = server.verify(&pattern, VerificationCount::Exactly(3)).await?;
38    /// assert!(result.matched, "Expected GET /api/users to be called exactly 3 times");
39    /// # Ok(())
40    /// # }
41    /// ```
42    async fn verify(
43        &self,
44        pattern: &VerificationRequest,
45        expected: VerificationCount,
46    ) -> Result<VerificationResult, Error>;
47
48    /// Verify that a request was never made
49    ///
50    /// # Example
51    ///
52    /// ```rust,no_run
53    /// use mockforge_sdk::MockServer;
54    /// use mockforge_sdk::verification::Verification;
55    /// use mockforge_core::verification::VerificationRequest;
56    ///
57    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
58    /// let mut server = MockServer::new().port(3000).start().await?;
59    ///
60    /// // Make some requests...
61    ///
62    /// let pattern = VerificationRequest {
63    ///     method: Some("DELETE".to_string()),
64    ///     path: Some("/api/users/1".to_string()),
65    ///     query_params: std::collections::HashMap::new(),
66    ///     headers: std::collections::HashMap::new(),
67    ///     body_pattern: None,
68    /// };
69    ///
70    /// let result = server.verify_never(&pattern).await?;
71    /// assert!(result.matched, "Expected DELETE /api/users/1 to never be called");
72    /// # Ok(())
73    /// # }
74    /// ```
75    async fn verify_never(
76        &self,
77        pattern: &VerificationRequest,
78    ) -> Result<VerificationResult, Error>;
79
80    /// Verify that a request was made at least N times
81    ///
82    /// # Example
83    ///
84    /// ```rust,no_run
85    /// use mockforge_sdk::MockServer;
86    /// use mockforge_sdk::verification::Verification;
87    /// use mockforge_core::verification::VerificationRequest;
88    ///
89    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
90    /// let mut server = MockServer::new().port(3000).start().await?;
91    ///
92    /// // Make some requests...
93    ///
94    /// let pattern = VerificationRequest {
95    ///     method: Some("POST".to_string()),
96    ///     path: Some("/api/orders".to_string()),
97    ///     query_params: std::collections::HashMap::new(),
98    ///     headers: std::collections::HashMap::new(),
99    ///     body_pattern: None,
100    /// };
101    ///
102    /// let result = server.verify_at_least(&pattern, 2).await?;
103    /// assert!(result.matched, "Expected POST /api/orders to be called at least 2 times");
104    /// # Ok(())
105    /// # }
106    /// ```
107    async fn verify_at_least(
108        &self,
109        pattern: &VerificationRequest,
110        min: usize,
111    ) -> Result<VerificationResult, Error>;
112
113    /// Verify that requests occurred in a specific sequence
114    ///
115    /// # Example
116    ///
117    /// ```rust,no_run
118    /// use mockforge_sdk::MockServer;
119    /// use mockforge_sdk::verification::Verification;
120    /// use mockforge_core::verification::VerificationRequest;
121    ///
122    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
123    /// let mut server = MockServer::new().port(3000).start().await?;
124    ///
125    /// // Make some requests in sequence...
126    ///
127    /// let patterns = vec![
128    ///     VerificationRequest {
129    ///         method: Some("POST".to_string()),
130    ///         path: Some("/api/users".to_string()),
131    ///         query_params: std::collections::HashMap::new(),
132    ///         headers: std::collections::HashMap::new(),
133    ///         body_pattern: None,
134    ///     },
135    ///     VerificationRequest {
136    ///         method: Some("GET".to_string()),
137    ///         path: Some("/api/users/1".to_string()),
138    ///         query_params: std::collections::HashMap::new(),
139    ///         headers: std::collections::HashMap::new(),
140    ///         body_pattern: None,
141    ///     },
142    /// ];
143    ///
144    /// let result = server.verify_sequence(&patterns).await?;
145    /// assert!(result.matched, "Expected requests to occur in sequence");
146    /// # Ok(())
147    /// # }
148    /// ```
149    async fn verify_sequence(
150        &self,
151        patterns: &[VerificationRequest],
152    ) -> Result<VerificationResult, Error>;
153}
154
155impl Verification for crate::server::MockServer {
156    async fn verify(
157        &self,
158        pattern: &VerificationRequest,
159        expected: VerificationCount,
160    ) -> Result<VerificationResult, Error> {
161        let logger = get_global_logger()
162            .ok_or_else(|| Error::General("Request logger not initialized".to_string()))?;
163
164        Ok(verify_requests(logger, pattern, expected).await)
165    }
166
167    async fn verify_never(
168        &self,
169        pattern: &VerificationRequest,
170    ) -> Result<VerificationResult, Error> {
171        let logger = get_global_logger()
172            .ok_or_else(|| Error::General("Request logger not initialized".to_string()))?;
173
174        Ok(verify_never(logger, pattern).await)
175    }
176
177    async fn verify_at_least(
178        &self,
179        pattern: &VerificationRequest,
180        min: usize,
181    ) -> Result<VerificationResult, Error> {
182        let logger = get_global_logger()
183            .ok_or_else(|| Error::General("Request logger not initialized".to_string()))?;
184
185        Ok(verify_at_least(logger, pattern, min).await)
186    }
187
188    async fn verify_sequence(
189        &self,
190        patterns: &[VerificationRequest],
191    ) -> Result<VerificationResult, Error> {
192        let logger = get_global_logger()
193            .ok_or_else(|| Error::General("Request logger not initialized".to_string()))?;
194
195        Ok(verify_sequence(logger, patterns).await)
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202    use std::collections::HashMap;
203
204    // Helper function to create a verification request
205    fn create_verification_request(method: &str, path: &str) -> VerificationRequest {
206        VerificationRequest {
207            method: Some(method.to_string()),
208            path: Some(path.to_string()),
209            query_params: HashMap::new(),
210            headers: HashMap::new(),
211            body_pattern: None,
212        }
213    }
214
215    #[test]
216    fn test_verification_request_creation() {
217        let request = create_verification_request("GET", "/api/users");
218        assert_eq!(request.method, Some("GET".to_string()));
219        assert_eq!(request.path, Some("/api/users".to_string()));
220        assert!(request.query_params.is_empty());
221        assert!(request.headers.is_empty());
222        assert!(request.body_pattern.is_none());
223    }
224
225    #[test]
226    fn test_verification_request_with_query_params() {
227        let mut query_params = HashMap::new();
228        query_params.insert("page".to_string(), "1".to_string());
229        query_params.insert("limit".to_string(), "10".to_string());
230
231        let request = VerificationRequest {
232            method: Some("GET".to_string()),
233            path: Some("/api/users".to_string()),
234            query_params,
235            headers: HashMap::new(),
236            body_pattern: None,
237        };
238
239        assert_eq!(request.query_params.len(), 2);
240        assert_eq!(request.query_params.get("page"), Some(&"1".to_string()));
241        assert_eq!(request.query_params.get("limit"), Some(&"10".to_string()));
242    }
243
244    #[test]
245    fn test_verification_request_with_headers() {
246        let mut headers = HashMap::new();
247        headers.insert("Authorization".to_string(), "Bearer token".to_string());
248        headers.insert("Content-Type".to_string(), "application/json".to_string());
249
250        let request = VerificationRequest {
251            method: Some("POST".to_string()),
252            path: Some("/api/users".to_string()),
253            query_params: HashMap::new(),
254            headers,
255            body_pattern: None,
256        };
257
258        assert_eq!(request.headers.len(), 2);
259        assert_eq!(request.headers.get("Authorization"), Some(&"Bearer token".to_string()));
260    }
261
262    #[test]
263    fn test_verification_request_with_body_pattern() {
264        let request = VerificationRequest {
265            method: Some("POST".to_string()),
266            path: Some("/api/users".to_string()),
267            query_params: HashMap::new(),
268            headers: HashMap::new(),
269            body_pattern: Some(r#"{"name":".*"}"#.to_string()),
270        };
271
272        assert_eq!(request.body_pattern, Some(r#"{"name":".*"}"#.to_string()));
273    }
274
275    #[test]
276    fn test_verification_count_exactly() {
277        let count = VerificationCount::Exactly(3);
278        match count {
279            VerificationCount::Exactly(n) => assert_eq!(n, 3),
280            _ => panic!("Expected Exactly variant"),
281        }
282    }
283
284    #[test]
285    fn test_verification_count_at_least() {
286        let count = VerificationCount::AtLeast(2);
287        match count {
288            VerificationCount::AtLeast(n) => assert_eq!(n, 2),
289            _ => panic!("Expected AtLeast variant"),
290        }
291    }
292
293    #[test]
294    fn test_verification_count_at_most() {
295        let count = VerificationCount::AtMost(5);
296        match count {
297            VerificationCount::AtMost(n) => assert_eq!(n, 5),
298            _ => panic!("Expected AtMost variant"),
299        }
300    }
301
302    #[test]
303    fn test_verification_count_never() {
304        let count = VerificationCount::Never;
305        match count {
306            VerificationCount::Never => (),
307            _ => panic!("Expected Never variant"),
308        }
309    }
310
311    #[tokio::test]
312    async fn test_verify_error_when_logger_not_initialized() {
313        let server = crate::server::MockServer::default();
314        let request = create_verification_request("GET", "/api/test");
315
316        // Without initializing the global logger, this should fail
317        let result = server.verify(&request, VerificationCount::Exactly(1)).await;
318
319        // The result depends on whether the global logger is initialized
320        // In test environments, it might be initialized by other tests
321        if result.is_err() {
322            match result {
323                Err(Error::General(msg)) => {
324                    assert!(msg.contains("Request logger not initialized"));
325                }
326                _ => panic!("Expected General error about logger"),
327            }
328        }
329    }
330
331    #[tokio::test]
332    async fn test_verify_never_error_when_logger_not_initialized() {
333        let server = crate::server::MockServer::default();
334        let request = create_verification_request("DELETE", "/api/users/1");
335
336        let result = server.verify_never(&request).await;
337
338        if result.is_err() {
339            match result {
340                Err(Error::General(msg)) => {
341                    assert!(msg.contains("Request logger not initialized"));
342                }
343                _ => panic!("Expected General error about logger"),
344            }
345        }
346    }
347
348    #[tokio::test]
349    async fn test_verify_at_least_error_when_logger_not_initialized() {
350        let server = crate::server::MockServer::default();
351        let request = create_verification_request("POST", "/api/orders");
352
353        let result = server.verify_at_least(&request, 2).await;
354
355        if result.is_err() {
356            match result {
357                Err(Error::General(msg)) => {
358                    assert!(msg.contains("Request logger not initialized"));
359                }
360                _ => panic!("Expected General error about logger"),
361            }
362        }
363    }
364
365    #[tokio::test]
366    async fn test_verify_sequence_error_when_logger_not_initialized() {
367        let server = crate::server::MockServer::default();
368        let patterns = [
369            create_verification_request("POST", "/api/users"),
370            create_verification_request("GET", "/api/users/1"),
371        ];
372
373        let result = server.verify_sequence(&patterns).await;
374
375        if result.is_err() {
376            match result {
377                Err(Error::General(msg)) => {
378                    assert!(msg.contains("Request logger not initialized"));
379                }
380                _ => panic!("Expected General error about logger"),
381            }
382        }
383    }
384
385    #[test]
386    fn test_verification_request_all_fields() {
387        let mut query_params = HashMap::new();
388        query_params.insert("id".to_string(), "123".to_string());
389
390        let mut headers = HashMap::new();
391        headers.insert("X-Custom".to_string(), "value".to_string());
392
393        let request = VerificationRequest {
394            method: Some("PUT".to_string()),
395            path: Some("/api/users/123".to_string()),
396            query_params,
397            headers,
398            body_pattern: Some(r#"{"name":"test"}"#.to_string()),
399        };
400
401        assert_eq!(request.method, Some("PUT".to_string()));
402        assert_eq!(request.path, Some("/api/users/123".to_string()));
403        assert_eq!(request.query_params.len(), 1);
404        assert_eq!(request.headers.len(), 1);
405        assert!(request.body_pattern.is_some());
406    }
407
408    #[test]
409    fn test_verification_request_minimal() {
410        let request = VerificationRequest {
411            method: None,
412            path: None,
413            query_params: HashMap::new(),
414            headers: HashMap::new(),
415            body_pattern: None,
416        };
417
418        assert!(request.method.is_none());
419        assert!(request.path.is_none());
420        assert!(request.query_params.is_empty());
421        assert!(request.headers.is_empty());
422        assert!(request.body_pattern.is_none());
423    }
424
425    #[test]
426    fn test_verification_sequence_empty() {
427        let patterns: Vec<VerificationRequest> = vec![];
428        assert_eq!(patterns.len(), 0);
429    }
430
431    #[test]
432    fn test_verification_sequence_single() {
433        let patterns = [create_verification_request("GET", "/api/test")];
434        assert_eq!(patterns.len(), 1);
435    }
436
437    #[test]
438    fn test_verification_sequence_multiple() {
439        let patterns = [
440            create_verification_request("POST", "/api/users"),
441            create_verification_request("GET", "/api/users/1"),
442            create_verification_request("PUT", "/api/users/1"),
443            create_verification_request("DELETE", "/api/users/1"),
444        ];
445        assert_eq!(patterns.len(), 4);
446    }
447
448    #[test]
449    fn test_verification_with_complex_pattern() {
450        let mut query_params = HashMap::new();
451        query_params.insert("filter".to_string(), "active".to_string());
452        query_params.insert("sort".to_string(), "name".to_string());
453
454        let mut headers = HashMap::new();
455        headers.insert("Accept".to_string(), "application/json".to_string());
456        headers.insert("X-API-Key".to_string(), "secret".to_string());
457
458        let request = VerificationRequest {
459            method: Some("GET".to_string()),
460            path: Some("/api/users".to_string()),
461            query_params,
462            headers,
463            body_pattern: None,
464        };
465
466        assert_eq!(request.method, Some("GET".to_string()));
467        assert_eq!(request.query_params.len(), 2);
468        assert_eq!(request.headers.len(), 2);
469    }
470}