mockforge_ui/handlers/
verification.rs

1//! Verification API handlers for Admin UI
2//!
3//! Provides endpoints for request verification that can be used by the Admin UI
4//! to verify that specific requests were made (or not made).
5
6use axum::extract::State;
7use axum::response::Json;
8use mockforge_core::{
9    request_logger::get_global_logger,
10    verification::{
11        verify_at_least, verify_never, verify_requests, verify_sequence, VerificationCount,
12        VerificationRequest, VerificationResult,
13    },
14};
15use serde::{Deserialize, Serialize};
16
17use crate::handlers::AdminState;
18use crate::models::ApiResponse;
19
20/// Request body for verification endpoint
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct VerifyRequest {
23    /// Pattern to match requests
24    pub pattern: VerificationRequest,
25    /// Expected count assertion
26    pub expected: VerificationCount,
27}
28
29/// Request body for count endpoint
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct CountRequest {
32    /// Pattern to match requests
33    pub pattern: VerificationRequest,
34}
35
36/// Response for count endpoint
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct CountResponse {
39    /// Number of matching requests
40    pub count: usize,
41}
42
43/// Request body for sequence verification
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct SequenceRequest {
46    /// Patterns to match in sequence
47    pub patterns: Vec<VerificationRequest>,
48}
49
50/// Request body for at-least verification
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct AtLeastRequest {
53    /// Pattern to match requests
54    pub pattern: VerificationRequest,
55    /// Minimum count
56    pub min: usize,
57}
58
59/// Verify requests against a pattern and count assertion
60pub async fn verify(
61    State(_state): State<AdminState>,
62    axum::extract::Json(request): axum::extract::Json<VerifyRequest>,
63) -> Json<ApiResponse<VerificationResult>> {
64    let logger = match get_global_logger() {
65        Some(logger) => logger,
66        None => {
67            return Json(ApiResponse::error("Request logger not initialized".to_string()));
68        }
69    };
70
71    let result = verify_requests(logger, &request.pattern, request.expected).await;
72
73    if result.matched {
74        Json(ApiResponse::success(result))
75    } else {
76        Json(ApiResponse::error(result.error_message.unwrap_or_else(|| {
77            format!(
78                "Verification failed: expected {:?}, but found {} matching requests",
79                result.expected, result.count
80            )
81        })))
82    }
83}
84
85/// Get count of matching requests
86pub async fn count(
87    State(_state): State<AdminState>,
88    axum::extract::Json(request): axum::extract::Json<CountRequest>,
89) -> Json<ApiResponse<CountResponse>> {
90    let logger = match get_global_logger() {
91        Some(logger) => logger,
92        None => {
93            return Json(ApiResponse::error("Request logger not initialized".to_string()));
94        }
95    };
96
97    let count = logger.count_matching_requests(&request.pattern).await;
98
99    Json(ApiResponse::success(CountResponse { count }))
100}
101
102/// Verify that requests occurred in a specific sequence
103pub async fn verify_sequence_handler(
104    State(_state): State<AdminState>,
105    axum::extract::Json(request): axum::extract::Json<SequenceRequest>,
106) -> Json<ApiResponse<VerificationResult>> {
107    let logger = match get_global_logger() {
108        Some(logger) => logger,
109        None => {
110            return Json(ApiResponse::error("Request logger not initialized".to_string()));
111        }
112    };
113
114    let result = verify_sequence(logger, &request.patterns).await;
115
116    if result.matched {
117        Json(ApiResponse::success(result))
118    } else {
119        Json(ApiResponse::error(
120            result
121                .error_message
122                .unwrap_or_else(|| "Sequence verification failed".to_string()),
123        ))
124    }
125}
126
127/// Verify that a request was never made
128pub async fn verify_never_handler(
129    State(_state): State<AdminState>,
130    axum::extract::Json(pattern): axum::extract::Json<VerificationRequest>,
131) -> Json<ApiResponse<VerificationResult>> {
132    let logger = match get_global_logger() {
133        Some(logger) => logger,
134        None => {
135            return Json(ApiResponse::error("Request logger not initialized".to_string()));
136        }
137    };
138
139    let result = verify_never(logger, &pattern).await;
140
141    if result.matched {
142        Json(ApiResponse::success(result))
143    } else {
144        Json(ApiResponse::error(
145            result.error_message.unwrap_or_else(|| {
146                format!(
147                    "Verification failed: expected request to never occur, but found {} matching requests",
148                    result.count
149                )
150            }),
151        ))
152    }
153}
154
155/// Verify that a request was made at least N times
156pub async fn verify_at_least_handler(
157    State(_state): State<AdminState>,
158    axum::extract::Json(request): axum::extract::Json<AtLeastRequest>,
159) -> Json<ApiResponse<VerificationResult>> {
160    let logger = match get_global_logger() {
161        Some(logger) => logger,
162        None => {
163            return Json(ApiResponse::error("Request logger not initialized".to_string()));
164        }
165    };
166
167    let result = verify_at_least(logger, &request.pattern, request.min).await;
168
169    if result.matched {
170        Json(ApiResponse::success(result))
171    } else {
172        Json(ApiResponse::error(result.error_message.unwrap_or_else(|| {
173            format!(
174                "Verification failed: expected at least {} requests, but found {}",
175                request.min, result.count
176            )
177        })))
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    // ==================== VerifyRequest Tests ====================
186
187    #[test]
188    fn test_verify_request_creation() {
189        let pattern = VerificationRequest {
190            method: Some("GET".to_string()),
191            path: Some("/api/users".to_string()),
192            ..Default::default()
193        };
194
195        let request = VerifyRequest {
196            pattern,
197            expected: VerificationCount::Exactly(1),
198        };
199
200        assert!(request.pattern.method.is_some());
201        assert_eq!(request.pattern.method.unwrap(), "GET");
202    }
203
204    #[test]
205    fn test_verify_request_structure() {
206        let request = VerifyRequest {
207            pattern: VerificationRequest {
208                method: Some("POST".to_string()),
209                path: Some("/api/orders".to_string()),
210                ..Default::default()
211            },
212            expected: VerificationCount::AtLeast(2),
213        };
214
215        // Verify structure without serialization (avoids tagged enum issues)
216        assert_eq!(request.pattern.method, Some("POST".to_string()));
217        assert_eq!(request.pattern.path, Some("/api/orders".to_string()));
218    }
219
220    #[test]
221    fn test_verify_request_pattern_fields() {
222        let pattern = VerificationRequest {
223            method: Some("DELETE".to_string()),
224            path: Some("/api/items/123".to_string()),
225            ..Default::default()
226        };
227
228        let request = VerifyRequest {
229            pattern,
230            expected: VerificationCount::Exactly(1),
231        };
232
233        assert_eq!(request.pattern.method, Some("DELETE".to_string()));
234        assert_eq!(request.pattern.path, Some("/api/items/123".to_string()));
235    }
236
237    #[test]
238    fn test_verify_request_clone() {
239        let request = VerifyRequest {
240            pattern: VerificationRequest {
241                method: Some("PUT".to_string()),
242                path: None,
243                ..Default::default()
244            },
245            expected: VerificationCount::Never,
246        };
247
248        let cloned = request.clone();
249        assert_eq!(cloned.pattern.method, request.pattern.method);
250    }
251
252    // ==================== CountRequest Tests ====================
253
254    #[test]
255    fn test_count_request_creation() {
256        let request = CountRequest {
257            pattern: VerificationRequest {
258                method: Some("GET".to_string()),
259                path: Some("/health".to_string()),
260                ..Default::default()
261            },
262        };
263
264        assert_eq!(request.pattern.method, Some("GET".to_string()));
265    }
266
267    #[test]
268    fn test_count_request_serialization() {
269        let request = CountRequest {
270            pattern: VerificationRequest {
271                method: Some("POST".to_string()),
272                path: Some("/api/data".to_string()),
273                ..Default::default()
274            },
275        };
276
277        let json = serde_json::to_string(&request).unwrap();
278        assert!(json.contains("POST"));
279        assert!(json.contains("/api/data"));
280    }
281
282    #[test]
283    fn test_count_request_clone() {
284        let request = CountRequest {
285            pattern: VerificationRequest {
286                method: Some("GET".to_string()),
287                ..Default::default()
288            },
289        };
290
291        let cloned = request.clone();
292        assert_eq!(cloned.pattern.method, request.pattern.method);
293    }
294
295    // ==================== CountResponse Tests ====================
296
297    #[test]
298    fn test_count_response_creation() {
299        let response = CountResponse { count: 42 };
300        assert_eq!(response.count, 42);
301    }
302
303    #[test]
304    fn test_count_response_serialization() {
305        let response = CountResponse { count: 100 };
306        let json = serde_json::to_string(&response).unwrap();
307        assert!(json.contains("100"));
308    }
309
310    #[test]
311    fn test_count_response_deserialization() {
312        let json = r#"{"count": 25}"#;
313        let response: CountResponse = serde_json::from_str(json).unwrap();
314        assert_eq!(response.count, 25);
315    }
316
317    #[test]
318    fn test_count_response_clone() {
319        let response = CountResponse { count: 10 };
320        let cloned = response.clone();
321        assert_eq!(cloned.count, response.count);
322    }
323
324    #[test]
325    fn test_count_response_zero() {
326        let response = CountResponse { count: 0 };
327        assert_eq!(response.count, 0);
328    }
329
330    // ==================== SequenceRequest Tests ====================
331
332    #[test]
333    fn test_sequence_request_creation() {
334        let request = SequenceRequest {
335            patterns: vec![
336                VerificationRequest {
337                    method: Some("POST".to_string()),
338                    path: Some("/api/login".to_string()),
339                    ..Default::default()
340                },
341                VerificationRequest {
342                    method: Some("GET".to_string()),
343                    path: Some("/api/profile".to_string()),
344                    ..Default::default()
345                },
346            ],
347        };
348
349        assert_eq!(request.patterns.len(), 2);
350    }
351
352    #[test]
353    fn test_sequence_request_empty() {
354        let request = SequenceRequest { patterns: vec![] };
355        assert!(request.patterns.is_empty());
356    }
357
358    #[test]
359    fn test_sequence_request_serialization() {
360        let request = SequenceRequest {
361            patterns: vec![VerificationRequest {
362                method: Some("GET".to_string()),
363                ..Default::default()
364            }],
365        };
366
367        let json = serde_json::to_string(&request).unwrap();
368        assert!(json.contains("GET"));
369    }
370
371    #[test]
372    fn test_sequence_request_clone() {
373        let request = SequenceRequest {
374            patterns: vec![VerificationRequest {
375                method: Some("POST".to_string()),
376                ..Default::default()
377            }],
378        };
379
380        let cloned = request.clone();
381        assert_eq!(cloned.patterns.len(), request.patterns.len());
382    }
383
384    // ==================== AtLeastRequest Tests ====================
385
386    #[test]
387    fn test_at_least_request_creation() {
388        let request = AtLeastRequest {
389            pattern: VerificationRequest {
390                method: Some("GET".to_string()),
391                path: Some("/api/users".to_string()),
392                ..Default::default()
393            },
394            min: 5,
395        };
396
397        assert_eq!(request.min, 5);
398    }
399
400    #[test]
401    fn test_at_least_request_serialization() {
402        let request = AtLeastRequest {
403            pattern: VerificationRequest {
404                method: Some("POST".to_string()),
405                ..Default::default()
406            },
407            min: 10,
408        };
409
410        let json = serde_json::to_string(&request).unwrap();
411        assert!(json.contains("10"));
412    }
413
414    #[test]
415    fn test_at_least_request_pattern() {
416        let request = AtLeastRequest {
417            pattern: VerificationRequest {
418                method: Some("DELETE".to_string()),
419                ..Default::default()
420            },
421            min: 3,
422        };
423
424        assert_eq!(request.min, 3);
425        assert_eq!(request.pattern.method, Some("DELETE".to_string()));
426    }
427
428    #[test]
429    fn test_at_least_request_clone() {
430        let request = AtLeastRequest {
431            pattern: VerificationRequest::default(),
432            min: 1,
433        };
434
435        let cloned = request.clone();
436        assert_eq!(cloned.min, request.min);
437    }
438
439    #[test]
440    fn test_at_least_request_zero_min() {
441        let request = AtLeastRequest {
442            pattern: VerificationRequest::default(),
443            min: 0,
444        };
445
446        assert_eq!(request.min, 0);
447    }
448
449    // ==================== Debug Tests ====================
450
451    #[test]
452    fn test_verify_request_debug() {
453        let request = VerifyRequest {
454            pattern: VerificationRequest::default(),
455            expected: VerificationCount::Exactly(1),
456        };
457
458        let debug = format!("{:?}", request);
459        assert!(debug.contains("VerifyRequest"));
460    }
461
462    #[test]
463    fn test_count_request_debug() {
464        let request = CountRequest {
465            pattern: VerificationRequest::default(),
466        };
467
468        let debug = format!("{:?}", request);
469        assert!(debug.contains("CountRequest"));
470    }
471
472    #[test]
473    fn test_count_response_debug() {
474        let response = CountResponse { count: 5 };
475        let debug = format!("{:?}", response);
476        assert!(debug.contains("5"));
477    }
478
479    #[test]
480    fn test_sequence_request_debug() {
481        let request = SequenceRequest { patterns: vec![] };
482        let debug = format!("{:?}", request);
483        assert!(debug.contains("SequenceRequest"));
484    }
485
486    #[test]
487    fn test_at_least_request_debug() {
488        let request = AtLeastRequest {
489            pattern: VerificationRequest::default(),
490            min: 2,
491        };
492
493        let debug = format!("{:?}", request);
494        assert!(debug.contains("AtLeastRequest"));
495    }
496}