mockforge_core/
time_travel_handler.rs1use crate::time_travel::{ResponseScheduler, ScheduledResponse, VirtualClock};
7use axum::{
8 body::Body,
9 http::{HeaderValue, Response, StatusCode},
10 response::IntoResponse,
11};
12use std::sync::Arc;
13use tracing::info;
14
15pub struct TimeTravelHandler {
17 scheduler: Arc<ResponseScheduler>,
19 clock: Arc<VirtualClock>,
21}
22
23impl TimeTravelHandler {
24 pub fn new(scheduler: Arc<ResponseScheduler>, clock: Arc<VirtualClock>) -> Self {
26 Self { scheduler, clock }
27 }
28
29 pub fn check_for_scheduled_response(&self) -> Option<ScheduledResponseWrapper> {
31 if !self.clock.is_enabled() {
32 return None;
33 }
34
35 let due_responses = self.scheduler.get_due_responses();
36 if due_responses.is_empty() {
37 return None;
38 }
39
40 due_responses.into_iter().next().map(ScheduledResponseWrapper::new)
42 }
43
44 pub fn get_all_due_responses(&self) -> Vec<ScheduledResponseWrapper> {
46 if !self.clock.is_enabled() {
47 return Vec::new();
48 }
49
50 self.scheduler
51 .get_due_responses()
52 .into_iter()
53 .map(ScheduledResponseWrapper::new)
54 .collect()
55 }
56
57 pub fn is_enabled(&self) -> bool {
59 self.clock.is_enabled()
60 }
61}
62
63#[derive(Debug, Clone)]
65pub struct ScheduledResponseWrapper {
66 inner: ScheduledResponse,
67}
68
69impl ScheduledResponseWrapper {
70 pub fn new(response: ScheduledResponse) -> Self {
72 Self { inner: response }
73 }
74
75 pub fn inner(&self) -> &ScheduledResponse {
77 &self.inner
78 }
79
80 pub fn into_response(self) -> Response<Body> {
82 let mut response = Response::builder().status(self.inner.status);
83
84 if let Some(headers) = response.headers_mut() {
86 for (key, value) in &self.inner.headers {
87 if let Ok(header_name) = key.parse::<axum::http::HeaderName>() {
88 if let Ok(header_value) = HeaderValue::from_str(value) {
89 headers.insert(header_name, header_value);
90 }
91 }
92 }
93
94 headers.insert("X-MockForge-Scheduled-Response", HeaderValue::from_static("true"));
96
97 if let Some(name) = &self.inner.name {
98 if let Ok(value) = HeaderValue::from_str(name) {
99 headers.insert("X-MockForge-Schedule-Name", value);
100 }
101 }
102 }
103
104 let body_str = serde_json::to_string(&self.inner.body).unwrap_or_else(|_| "{}".to_string());
106 response.body(Body::from(body_str)).unwrap_or_else(|_| {
107 Response::builder()
108 .status(StatusCode::INTERNAL_SERVER_ERROR)
109 .body(Body::from("Failed to build response"))
110 .unwrap()
111 })
112 }
113}
114
115impl IntoResponse for ScheduledResponseWrapper {
116 fn into_response(self) -> axum::response::Response {
117 let mut response = Response::builder().status(self.inner.status);
118
119 let headers = response.headers_mut();
121 if let Some(headers) = headers {
122 for (key, value) in &self.inner.headers {
123 if let Ok(header_name) = key.parse::<axum::http::HeaderName>() {
124 if let Ok(header_value) = HeaderValue::from_str(value) {
125 headers.insert(header_name, header_value);
126 }
127 }
128 }
129
130 headers.insert("X-MockForge-Scheduled-Response", HeaderValue::from_static("true"));
132 }
133
134 let body_str = serde_json::to_string(&self.inner.body).unwrap_or_else(|_| "{}".to_string());
136 response.body(Body::from(body_str)).unwrap_or_else(|_| {
137 Response::builder()
138 .status(StatusCode::INTERNAL_SERVER_ERROR)
139 .body(Body::from("Failed to build response"))
140 .unwrap()
141 })
142 }
143}
144
145pub async fn time_travel_middleware(
147 handler: Arc<TimeTravelHandler>,
148 request: axum::http::Request<Body>,
149 next: axum::middleware::Next,
150) -> impl IntoResponse {
151 if let Some(scheduled) = handler.check_for_scheduled_response() {
153 info!("Returning scheduled response: {}", scheduled.inner().id);
154 return scheduled.into_response();
155 }
156
157 next.run(request).await
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164 use crate::time_travel::{ScheduledResponse, VirtualClock};
165 use chrono::{Duration, Utc};
166 use std::collections::HashMap;
167
168 #[test]
169 fn test_time_travel_handler_creation() {
170 let clock = Arc::new(VirtualClock::new());
171 let scheduler = Arc::new(ResponseScheduler::new(clock.clone()));
172 let handler = TimeTravelHandler::new(scheduler, clock);
173
174 assert!(!handler.is_enabled());
175 }
176
177 #[test]
178 fn test_scheduled_response_wrapper() {
179 let response = ScheduledResponse {
180 id: "test-1".to_string(),
181 trigger_time: Utc::now(),
182 body: serde_json::json!({"message": "Hello"}),
183 status: 200,
184 headers: HashMap::new(),
185 name: Some("test".to_string()),
186 repeat: None,
187 };
188
189 let wrapper = ScheduledResponseWrapper::new(response.clone());
190 assert_eq!(wrapper.inner().id, "test-1");
191 }
192
193 #[test]
194 fn test_check_for_scheduled_response() {
195 let clock = Arc::new(VirtualClock::new());
196 let test_time = Utc::now();
197 clock.enable_and_set(test_time);
198
199 let scheduler = Arc::new(ResponseScheduler::new(clock.clone()));
200
201 let response = ScheduledResponse {
202 id: "test-1".to_string(),
203 trigger_time: test_time + Duration::seconds(10),
204 body: serde_json::json!({"message": "Hello"}),
205 status: 200,
206 headers: HashMap::new(),
207 name: None,
208 repeat: None,
209 };
210
211 scheduler.schedule(response).unwrap();
212
213 let handler = TimeTravelHandler::new(scheduler, clock.clone());
214
215 assert!(handler.check_for_scheduled_response().is_none());
217
218 clock.advance(Duration::seconds(15));
220
221 assert!(handler.check_for_scheduled_response().is_some());
223 }
224}