mockforge_http/
verification.rs

1//! HTTP verification API handlers for MockForge
2//!
3//! Provides REST endpoints for programmatic request verification,
4//! allowing test code to verify that specific requests were made (or not made).
5
6use axum::{http::StatusCode, response::IntoResponse, routing::post, Json, Router};
7use mockforge_core::{
8    request_logger::get_global_logger,
9    verification::{
10        verify_at_least, verify_never, verify_requests, verify_sequence, VerificationCount,
11        VerificationRequest, VerificationResult,
12    },
13};
14use serde::{Deserialize, Serialize};
15
16/// Request body for verification endpoint
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct VerifyRequest {
19    /// Pattern to match requests
20    pub pattern: VerificationRequest,
21    /// Expected count assertion
22    pub expected: VerificationCount,
23}
24
25/// Request body for count endpoint
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct CountRequest {
28    /// Pattern to match requests
29    pub pattern: VerificationRequest,
30}
31
32/// Response for count endpoint
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct CountResponse {
35    /// Number of matching requests
36    pub count: usize,
37}
38
39/// Request body for sequence verification
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct SequenceRequest {
42    /// Patterns to match in sequence
43    pub patterns: Vec<VerificationRequest>,
44}
45
46/// Shared state for verification API (currently empty, but kept for future extensibility)
47#[derive(Clone)]
48pub struct VerificationState;
49
50impl Default for VerificationState {
51    fn default() -> Self {
52        Self::new()
53    }
54}
55
56impl VerificationState {
57    /// Create a new verification state
58    pub fn new() -> Self {
59        Self
60    }
61}
62
63/// Create the verification API router
64pub fn verification_router() -> Router {
65    Router::new()
66        .route("/api/verification/verify", post(handle_verify))
67        .route("/api/verification/count", post(handle_count))
68        .route("/api/verification/sequence", post(handle_sequence))
69        .route("/api/verification/never", post(handle_never))
70        .route("/api/verification/at-least", post(handle_at_least))
71}
72
73/// Verify requests against a pattern and count assertion
74async fn handle_verify(Json(request): Json<VerifyRequest>) -> impl IntoResponse {
75    let logger = match get_global_logger() {
76        Some(logger) => logger,
77        None => {
78            return (
79                StatusCode::SERVICE_UNAVAILABLE,
80                Json(VerificationResult::failure(
81                    0,
82                    request.expected.clone(),
83                    Vec::new(),
84                    "Request logger not initialized".to_string(),
85                )),
86            )
87                .into_response();
88        }
89    };
90
91    let result = verify_requests(logger, &request.pattern, request.expected).await;
92
93    let status = if result.matched {
94        StatusCode::OK
95    } else {
96        StatusCode::EXPECTATION_FAILED
97    };
98
99    (status, Json(result)).into_response()
100}
101
102/// Get count of matching requests
103async fn handle_count(Json(request): Json<CountRequest>) -> impl IntoResponse {
104    let logger = match get_global_logger() {
105        Some(logger) => logger,
106        None => {
107            return (StatusCode::SERVICE_UNAVAILABLE, Json(CountResponse { count: 0 }))
108                .into_response();
109        }
110    };
111
112    let count = logger.count_matching_requests(&request.pattern).await;
113
114    (StatusCode::OK, Json(CountResponse { count })).into_response()
115}
116
117/// Verify that requests occurred in a specific sequence
118async fn handle_sequence(Json(request): Json<SequenceRequest>) -> impl IntoResponse {
119    let logger = match get_global_logger() {
120        Some(logger) => logger,
121        None => {
122            return (
123                StatusCode::SERVICE_UNAVAILABLE,
124                Json(VerificationResult::failure(
125                    0,
126                    VerificationCount::Exactly(request.patterns.len()),
127                    Vec::new(),
128                    "Request logger not initialized".to_string(),
129                )),
130            )
131                .into_response();
132        }
133    };
134
135    let result = verify_sequence(logger, &request.patterns).await;
136
137    let status = if result.matched {
138        StatusCode::OK
139    } else {
140        StatusCode::EXPECTATION_FAILED
141    };
142
143    (status, Json(result)).into_response()
144}
145
146/// Verify that a request was never made
147async fn handle_never(Json(request): Json<VerificationRequest>) -> impl IntoResponse {
148    let logger = match get_global_logger() {
149        Some(logger) => logger,
150        None => {
151            return (
152                StatusCode::SERVICE_UNAVAILABLE,
153                Json(VerificationResult::failure(
154                    0,
155                    VerificationCount::Never,
156                    Vec::new(),
157                    "Request logger not initialized".to_string(),
158                )),
159            )
160                .into_response();
161        }
162    };
163
164    let result = verify_never(logger, &request).await;
165
166    let status = if result.matched {
167        StatusCode::OK
168    } else {
169        StatusCode::EXPECTATION_FAILED
170    };
171
172    (status, Json(result)).into_response()
173}
174
175/// Verify that a request was made at least N times
176#[derive(Debug, Clone, Serialize, Deserialize)]
177struct AtLeastRequest {
178    /// Pattern to match requests
179    pub pattern: VerificationRequest,
180    /// Minimum count
181    pub min: usize,
182}
183
184async fn handle_at_least(Json(request): Json<AtLeastRequest>) -> impl IntoResponse {
185    let logger = match get_global_logger() {
186        Some(logger) => logger,
187        None => {
188            return (
189                StatusCode::SERVICE_UNAVAILABLE,
190                Json(VerificationResult::failure(
191                    0,
192                    VerificationCount::AtLeast(request.min),
193                    Vec::new(),
194                    "Request logger not initialized".to_string(),
195                )),
196            )
197                .into_response();
198        }
199    };
200
201    let result = verify_at_least(logger, &request.pattern, request.min).await;
202
203    let status = if result.matched {
204        StatusCode::OK
205    } else {
206        StatusCode::EXPECTATION_FAILED
207    };
208
209    (status, Json(result)).into_response()
210}
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215    use axum::body::Body;
216    use axum::http::Request;
217    use mockforge_core::verification::{VerificationCount, VerificationRequest};
218    use tower::ServiceExt;
219
220    // ==================== Router Tests ====================
221
222    #[tokio::test]
223    async fn test_verification_router_creation() {
224        let router = verification_router();
225        // Router should be created without panicking
226        assert!(std::mem::size_of_val(&router) > 0);
227    }
228
229    #[tokio::test]
230    async fn test_verification_router_has_verify_route() {
231        let router = verification_router();
232
233        let request = Request::builder()
234            .method("POST")
235            .uri("/api/verification/verify")
236            .header("Content-Type", "application/json")
237            .body(Body::empty())
238            .unwrap();
239
240        let response = router.oneshot(request).await.unwrap();
241        // Should return an error since body is empty (either 422 or 503)
242        assert!(response.status().is_client_error() || response.status().is_server_error());
243    }
244
245    #[tokio::test]
246    async fn test_verification_router_has_count_route() {
247        let router = verification_router();
248
249        let request = Request::builder()
250            .method("POST")
251            .uri("/api/verification/count")
252            .header("Content-Type", "application/json")
253            .body(Body::empty())
254            .unwrap();
255
256        let response = router.oneshot(request).await.unwrap();
257        // Should return an error since body is empty
258        assert!(response.status().is_client_error() || response.status().is_server_error());
259    }
260
261    #[tokio::test]
262    async fn test_verification_router_has_sequence_route() {
263        let router = verification_router();
264
265        let request = Request::builder()
266            .method("POST")
267            .uri("/api/verification/sequence")
268            .header("Content-Type", "application/json")
269            .body(Body::empty())
270            .unwrap();
271
272        let response = router.oneshot(request).await.unwrap();
273        // Should return an error since body is empty
274        assert!(response.status().is_client_error() || response.status().is_server_error());
275    }
276
277    #[tokio::test]
278    async fn test_verification_router_has_never_route() {
279        let router = verification_router();
280
281        let request = Request::builder()
282            .method("POST")
283            .uri("/api/verification/never")
284            .header("Content-Type", "application/json")
285            .body(Body::empty())
286            .unwrap();
287
288        let response = router.oneshot(request).await.unwrap();
289        // Should return an error since body is empty
290        assert!(response.status().is_client_error() || response.status().is_server_error());
291    }
292
293    #[tokio::test]
294    async fn test_verification_router_has_at_least_route() {
295        let router = verification_router();
296
297        let request = Request::builder()
298            .method("POST")
299            .uri("/api/verification/at-least")
300            .header("Content-Type", "application/json")
301            .body(Body::empty())
302            .unwrap();
303
304        let response = router.oneshot(request).await.unwrap();
305        // Should return an error since body is empty
306        assert!(response.status().is_client_error() || response.status().is_server_error());
307    }
308
309    // ==================== VerificationState Tests ====================
310
311    #[test]
312    fn test_verification_state_new() {
313        let state = VerificationState::new();
314        // State should be created successfully (size_of_val always >= 0 for usize)
315        let _ = std::mem::size_of_val(&state);
316    }
317
318    #[test]
319    fn test_verification_state_default() {
320        let state = VerificationState::default();
321        // size_of_val always >= 0 for usize
322        let _ = std::mem::size_of_val(&state);
323    }
324
325    #[test]
326    fn test_verification_state_clone() {
327        let state = VerificationState::new();
328        let _cloned = state.clone();
329        // Clone should succeed without panic
330    }
331
332    // ==================== VerifyRequest Tests ====================
333
334    #[test]
335    fn test_verify_request_creation() {
336        let pattern = VerificationRequest {
337            method: Some("GET".to_string()),
338            path: Some("/api/users".to_string()),
339            ..Default::default()
340        };
341
342        let verify_request = VerifyRequest {
343            pattern,
344            expected: VerificationCount::Exactly(1),
345        };
346
347        assert!(verify_request.pattern.method.is_some());
348        assert!(matches!(verify_request.expected, VerificationCount::Exactly(1)));
349    }
350
351    #[test]
352    fn test_verify_request_structure() {
353        let pattern = VerificationRequest {
354            method: Some("POST".to_string()),
355            path: Some("/api/create".to_string()),
356            ..Default::default()
357        };
358
359        let verify_request = VerifyRequest {
360            pattern,
361            expected: VerificationCount::AtLeast(2),
362        };
363
364        // Test that the structure is properly created
365        assert_eq!(verify_request.pattern.method, Some("POST".to_string()));
366        assert!(matches!(verify_request.expected, VerificationCount::AtLeast(2)));
367    }
368
369    #[test]
370    fn test_verify_request_clone() {
371        let pattern = VerificationRequest {
372            method: Some("GET".to_string()),
373            path: Some("/test".to_string()),
374            ..Default::default()
375        };
376
377        let verify_request = VerifyRequest {
378            pattern,
379            expected: VerificationCount::Exactly(5),
380        };
381
382        let cloned = verify_request.clone();
383        assert_eq!(cloned.pattern.method, verify_request.pattern.method);
384    }
385
386    // ==================== CountRequest Tests ====================
387
388    #[test]
389    fn test_count_request_creation() {
390        let pattern = VerificationRequest {
391            method: Some("DELETE".to_string()),
392            path: Some("/api/users/123".to_string()),
393            ..Default::default()
394        };
395
396        let count_request = CountRequest { pattern };
397        assert_eq!(count_request.pattern.method, Some("DELETE".to_string()));
398    }
399
400    #[test]
401    fn test_count_request_serialization() {
402        let count_request = CountRequest {
403            pattern: VerificationRequest {
404                method: Some("PUT".to_string()),
405                path: Some("/api/update".to_string()),
406                ..Default::default()
407            },
408        };
409
410        let json = serde_json::to_string(&count_request);
411        assert!(json.is_ok());
412    }
413
414    #[test]
415    fn test_count_request_clone() {
416        let count_request = CountRequest {
417            pattern: VerificationRequest {
418                method: Some("GET".to_string()),
419                path: Some("/test".to_string()),
420                ..Default::default()
421            },
422        };
423
424        let cloned = count_request.clone();
425        assert_eq!(cloned.pattern.method, Some("GET".to_string()));
426    }
427
428    // ==================== CountResponse Tests ====================
429
430    #[test]
431    fn test_count_response_creation() {
432        let response = CountResponse { count: 42 };
433        assert_eq!(response.count, 42);
434    }
435
436    #[test]
437    fn test_count_response_serialization() {
438        let response = CountResponse { count: 100 };
439        let json = serde_json::to_string(&response).unwrap();
440        assert!(json.contains("100"));
441    }
442
443    #[test]
444    fn test_count_response_deserialization() {
445        let json = r#"{"count":25}"#;
446        let response: CountResponse = serde_json::from_str(json).unwrap();
447        assert_eq!(response.count, 25);
448    }
449
450    #[test]
451    fn test_count_response_zero() {
452        let response = CountResponse { count: 0 };
453        assert_eq!(response.count, 0);
454    }
455
456    // ==================== SequenceRequest Tests ====================
457
458    #[test]
459    fn test_sequence_request_creation() {
460        let patterns = vec![
461            VerificationRequest {
462                method: Some("POST".to_string()),
463                path: Some("/api/login".to_string()),
464                ..Default::default()
465            },
466            VerificationRequest {
467                method: Some("GET".to_string()),
468                path: Some("/api/profile".to_string()),
469                ..Default::default()
470            },
471        ];
472
473        let sequence_request = SequenceRequest { patterns };
474        assert_eq!(sequence_request.patterns.len(), 2);
475    }
476
477    #[test]
478    fn test_sequence_request_empty() {
479        let sequence_request = SequenceRequest { patterns: vec![] };
480        assert!(sequence_request.patterns.is_empty());
481    }
482
483    #[test]
484    fn test_sequence_request_serialization() {
485        let sequence_request = SequenceRequest {
486            patterns: vec![VerificationRequest {
487                method: Some("GET".to_string()),
488                path: Some("/health".to_string()),
489                ..Default::default()
490            }],
491        };
492
493        let json = serde_json::to_string(&sequence_request);
494        assert!(json.is_ok());
495    }
496
497    // ==================== AtLeastRequest Tests ====================
498
499    #[test]
500    fn test_at_least_request_creation() {
501        let request = AtLeastRequest {
502            pattern: VerificationRequest {
503                method: Some("GET".to_string()),
504                path: Some("/api/data".to_string()),
505                ..Default::default()
506            },
507            min: 3,
508        };
509
510        assert_eq!(request.min, 3);
511        assert_eq!(request.pattern.method, Some("GET".to_string()));
512    }
513
514    #[test]
515    fn test_at_least_request_serialization() {
516        let request = AtLeastRequest {
517            pattern: VerificationRequest {
518                method: Some("POST".to_string()),
519                path: Some("/api/submit".to_string()),
520                ..Default::default()
521            },
522            min: 5,
523        };
524
525        let json = serde_json::to_string(&request);
526        assert!(json.is_ok());
527    }
528
529    #[test]
530    fn test_at_least_request_clone() {
531        let request = AtLeastRequest {
532            pattern: VerificationRequest {
533                method: Some("GET".to_string()),
534                path: Some("/test".to_string()),
535                ..Default::default()
536            },
537            min: 2,
538        };
539
540        let cloned = request.clone();
541        assert_eq!(cloned.min, 2);
542        assert_eq!(cloned.pattern.method, Some("GET".to_string()));
543    }
544}