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 axum::http::StatusCode;
218    use tower::ServiceExt;
219
220    #[tokio::test]
221    async fn test_verification_router_creation() {
222        let router = verification_router();
223        // Router should be created without panicking
224        assert!(true);
225    }
226}