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::{
7    extract::State,
8    http::StatusCode,
9    response::IntoResponse,
10    routing::{get, post},
11    Json, Router,
12};
13use mockforge_core::{
14    request_logger::get_global_logger,
15    verification::{
16        verify_at_least, verify_never, verify_requests, verify_sequence, VerificationCount,
17        VerificationRequest, VerificationResult,
18    },
19};
20use serde::{Deserialize, Serialize};
21use std::sync::Arc;
22
23/// Request body for verification endpoint
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct VerifyRequest {
26    /// Pattern to match requests
27    pub pattern: VerificationRequest,
28    /// Expected count assertion
29    pub expected: VerificationCount,
30}
31
32/// Request body for count endpoint
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct CountRequest {
35    /// Pattern to match requests
36    pub pattern: VerificationRequest,
37}
38
39/// Response for count endpoint
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct CountResponse {
42    /// Number of matching requests
43    pub count: usize,
44}
45
46/// Request body for sequence verification
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct SequenceRequest {
49    /// Patterns to match in sequence
50    pub patterns: Vec<VerificationRequest>,
51}
52
53/// Shared state for verification API (currently empty, but kept for future extensibility)
54#[derive(Clone)]
55pub struct VerificationState;
56
57impl VerificationState {
58    /// Create a new verification state
59    pub fn new() -> Self {
60        Self
61    }
62}
63
64/// Create the verification API router
65pub fn verification_router() -> Router {
66    Router::new()
67        .route("/api/verification/verify", post(handle_verify))
68        .route("/api/verification/count", post(handle_count))
69        .route("/api/verification/sequence", post(handle_sequence))
70        .route("/api/verification/never", post(handle_never))
71        .route("/api/verification/at-least", post(handle_at_least))
72}
73
74/// Verify requests against a pattern and count assertion
75async fn handle_verify(Json(request): Json<VerifyRequest>) -> impl IntoResponse {
76    let logger = match get_global_logger() {
77        Some(logger) => logger,
78        None => {
79            return (
80                StatusCode::SERVICE_UNAVAILABLE,
81                Json(VerificationResult::failure(
82                    0,
83                    request.expected.clone(),
84                    Vec::new(),
85                    "Request logger not initialized".to_string(),
86                )),
87            )
88                .into_response();
89        }
90    };
91
92    let result = verify_requests(logger, &request.pattern, request.expected).await;
93
94    let status = if result.matched {
95        StatusCode::OK
96    } else {
97        StatusCode::EXPECTATION_FAILED
98    };
99
100    (status, Json(result)).into_response()
101}
102
103/// Get count of matching requests
104async fn handle_count(Json(request): Json<CountRequest>) -> impl IntoResponse {
105    let logger = match get_global_logger() {
106        Some(logger) => logger,
107        None => {
108            return (StatusCode::SERVICE_UNAVAILABLE, Json(CountResponse { count: 0 }))
109                .into_response();
110        }
111    };
112
113    let count = logger.count_matching_requests(&request.pattern).await;
114
115    (StatusCode::OK, Json(CountResponse { count })).into_response()
116}
117
118/// Verify that requests occurred in a specific sequence
119async fn handle_sequence(Json(request): Json<SequenceRequest>) -> impl IntoResponse {
120    let logger = match get_global_logger() {
121        Some(logger) => logger,
122        None => {
123            return (
124                StatusCode::SERVICE_UNAVAILABLE,
125                Json(VerificationResult::failure(
126                    0,
127                    VerificationCount::Exactly(request.patterns.len()),
128                    Vec::new(),
129                    "Request logger not initialized".to_string(),
130                )),
131            )
132                .into_response();
133        }
134    };
135
136    let result = verify_sequence(logger, &request.patterns).await;
137
138    let status = if result.matched {
139        StatusCode::OK
140    } else {
141        StatusCode::EXPECTATION_FAILED
142    };
143
144    (status, Json(result)).into_response()
145}
146
147/// Verify that a request was never made
148async fn handle_never(Json(request): Json<VerificationRequest>) -> impl IntoResponse {
149    let logger = match get_global_logger() {
150        Some(logger) => logger,
151        None => {
152            return (
153                StatusCode::SERVICE_UNAVAILABLE,
154                Json(VerificationResult::failure(
155                    0,
156                    VerificationCount::Never,
157                    Vec::new(),
158                    "Request logger not initialized".to_string(),
159                )),
160            )
161                .into_response();
162        }
163    };
164
165    let result = verify_never(logger, &request).await;
166
167    let status = if result.matched {
168        StatusCode::OK
169    } else {
170        StatusCode::EXPECTATION_FAILED
171    };
172
173    (status, Json(result)).into_response()
174}
175
176/// Verify that a request was made at least N times
177#[derive(Debug, Clone, Serialize, Deserialize)]
178struct AtLeastRequest {
179    /// Pattern to match requests
180    pub pattern: VerificationRequest,
181    /// Minimum count
182    pub min: usize,
183}
184
185async fn handle_at_least(Json(request): Json<AtLeastRequest>) -> impl IntoResponse {
186    let logger = match get_global_logger() {
187        Some(logger) => logger,
188        None => {
189            return (
190                StatusCode::SERVICE_UNAVAILABLE,
191                Json(VerificationResult::failure(
192                    0,
193                    VerificationCount::AtLeast(request.min),
194                    Vec::new(),
195                    "Request logger not initialized".to_string(),
196                )),
197            )
198                .into_response();
199        }
200    };
201
202    let result = verify_at_least(logger, &request.pattern, request.min).await;
203
204    let status = if result.matched {
205        StatusCode::OK
206    } else {
207        StatusCode::EXPECTATION_FAILED
208    };
209
210    (status, Json(result)).into_response()
211}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216    use axum::body::Body;
217    use axum::http::Request;
218    use axum::http::StatusCode;
219    use tower::ServiceExt;
220
221    #[tokio::test]
222    async fn test_verification_router_creation() {
223        let router = verification_router();
224        // Router should be created without panicking
225        assert!(true);
226    }
227}