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