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}