1use axum::{
6 extract::{Path, State},
7 http::StatusCode,
8 response::{IntoResponse, Response},
9 Json,
10};
11use serde_json::Value;
12
13use super::AdminState;
14
15pub async fn proxy_chains_list(State(state): State<AdminState>) -> Response {
17 proxy_to_http_server(&state, "/chains", None).await
18}
19
20pub async fn proxy_chains_create(
22 State(state): State<AdminState>,
23 Json(body): Json<Value>,
24) -> Response {
25 proxy_to_http_server(&state, "/chains", Some(body)).await
26}
27
28pub async fn proxy_chain_get(State(state): State<AdminState>, Path(id): Path<String>) -> Response {
30 proxy_to_http_server(&state, &format!("/chains/{}", id), None).await
31}
32
33pub async fn proxy_chain_update(
35 State(state): State<AdminState>,
36 Path(id): Path<String>,
37 Json(body): Json<Value>,
38) -> Response {
39 proxy_to_http_server(&state, &format!("/chains/{}", id), Some(body)).await
40}
41
42pub async fn proxy_chain_delete(
44 State(state): State<AdminState>,
45 Path(id): Path<String>,
46) -> Response {
47 proxy_to_http_server(&state, &format!("/chains/{}", id), None).await
48}
49
50pub async fn proxy_chain_execute(
52 State(state): State<AdminState>,
53 Path(id): Path<String>,
54 Json(body): Json<Value>,
55) -> Response {
56 proxy_to_http_server(&state, &format!("/chains/{}/execute", id), Some(body)).await
57}
58
59pub async fn proxy_chain_validate(
61 State(state): State<AdminState>,
62 Path(id): Path<String>,
63) -> Response {
64 proxy_to_http_server(&state, &format!("/chains/{}/validate", id), None).await
65}
66
67pub async fn proxy_chain_history(
69 State(state): State<AdminState>,
70 Path(id): Path<String>,
71) -> Response {
72 proxy_to_http_server(&state, &format!("/chains/{}/history", id), None).await
73}
74
75async fn proxy_to_http_server(state: &AdminState, path: &str, body: Option<Value>) -> Response {
77 let Some(http_addr) = state.http_server_addr else {
78 return (StatusCode::SERVICE_UNAVAILABLE, "HTTP server address not configured")
79 .into_response();
80 };
81
82 let url = format!("http://{}/__mockforge{}", http_addr, path);
83
84 let client = reqwest::Client::new();
85 let mut request_builder = if body.is_some() {
86 client.post(&url)
87 } else {
88 client.get(&url)
89 };
90
91 if let Some(json_body) = body {
92 request_builder = request_builder.json(&json_body);
93 }
94
95 match request_builder.send().await {
96 Ok(response) => {
97 let status = response.status();
98 match response.text().await {
99 Ok(text) => {
100 if let Ok(json) = serde_json::from_str::<Value>(&text) {
102 (
103 StatusCode::from_u16(status.as_u16()).unwrap_or(StatusCode::OK),
104 Json(json),
105 )
106 .into_response()
107 } else {
108 (StatusCode::from_u16(status.as_u16()).unwrap_or(StatusCode::OK), text)
109 .into_response()
110 }
111 }
112 Err(e) => {
113 (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to read response: {}", e))
114 .into_response()
115 }
116 }
117 }
118 Err(e) => {
119 (StatusCode::BAD_GATEWAY, format!("Failed to proxy request: {}", e)).into_response()
120 }
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use std::net::SocketAddr;
128
129 fn create_test_state(http_addr: Option<SocketAddr>) -> AdminState {
130 AdminState::new(
131 http_addr, None, None, None, false, 8080, None, None, None, None, None, None, None,
132 None,
133 )
134 }
135
136 #[tokio::test]
139 async fn test_proxy_to_http_server_no_addr() {
140 let state = create_test_state(None);
141 let response = proxy_to_http_server(&state, "/test", None).await;
142
143 let _ = response;
146 }
147
148 #[tokio::test]
149 async fn test_proxy_to_http_server_with_path() {
150 let state = create_test_state(None);
151 let response = proxy_to_http_server(&state, "/chains/123", None).await;
152 let _ = response;
153 }
154
155 #[tokio::test]
156 async fn test_proxy_to_http_server_with_body() {
157 let state = create_test_state(None);
158 let body = serde_json::json!({"name": "test-chain"});
159 let response = proxy_to_http_server(&state, "/chains", Some(body)).await;
160 let _ = response;
161 }
162
163 #[test]
166 fn test_admin_state_creation() {
167 let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
168 let state = create_test_state(Some(addr));
169 assert!(state.http_server_addr.is_some());
170 }
171
172 #[test]
173 fn test_admin_state_no_http_addr() {
174 let state = create_test_state(None);
175 assert!(state.http_server_addr.is_none());
176 }
177
178 #[test]
181 fn test_chain_path_construction() {
182 let chain_id = "chain-123";
183 let path = format!("/chains/{}", chain_id);
184 assert_eq!(path, "/chains/chain-123");
185 }
186
187 #[test]
188 fn test_chain_execute_path_construction() {
189 let chain_id = "exec-chain";
190 let path = format!("/chains/{}/execute", chain_id);
191 assert_eq!(path, "/chains/exec-chain/execute");
192 }
193
194 #[test]
195 fn test_chain_validate_path_construction() {
196 let chain_id = "validate-chain";
197 let path = format!("/chains/{}/validate", chain_id);
198 assert_eq!(path, "/chains/validate-chain/validate");
199 }
200
201 #[test]
202 fn test_chain_history_path_construction() {
203 let chain_id = "history-chain";
204 let path = format!("/chains/{}/history", chain_id);
205 assert_eq!(path, "/chains/history-chain/history");
206 }
207
208 #[test]
211 fn test_url_construction_chains_endpoint() {
212 let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
213 let path = "/chains";
214 let url = format!("http://{}/__mockforge{}", addr, path);
215 assert_eq!(url, "http://127.0.0.1:8080/__mockforge/chains");
216 }
217
218 #[test]
219 fn test_url_construction_with_id() {
220 let addr: SocketAddr = "192.168.1.1:3000".parse().unwrap();
221 let chain_id = "abc123";
222 let url = format!("http://{}/__mockforge/chains/{}", addr, chain_id);
223 assert_eq!(url, "http://192.168.1.1:3000/__mockforge/chains/abc123");
224 }
225
226 #[test]
227 fn test_url_construction_ipv6() {
228 let addr: SocketAddr = "[::1]:8080".parse().unwrap();
229 let path = "/chains";
230 let url = format!("http://{}/__mockforge{}", addr, path);
231 assert_eq!(url, "http://[::1]:8080/__mockforge/chains");
232 }
233
234 #[test]
237 fn test_chain_create_body() {
238 let body = serde_json::json!({
239 "name": "test-chain",
240 "steps": [
241 {"type": "http", "endpoint": "/api/users"},
242 {"type": "transform", "expression": "$.data"}
243 ]
244 });
245
246 assert!(body.get("name").is_some());
247 assert!(body.get("steps").is_some());
248 let steps = body.get("steps").unwrap().as_array().unwrap();
249 assert_eq!(steps.len(), 2);
250 }
251
252 #[test]
253 fn test_chain_update_body() {
254 let body = serde_json::json!({
255 "name": "updated-chain",
256 "enabled": true,
257 "timeout_ms": 5000
258 });
259
260 assert_eq!(body.get("name").unwrap(), "updated-chain");
261 assert_eq!(body.get("enabled").unwrap(), true);
262 }
263
264 #[test]
265 fn test_chain_execute_body() {
266 let body = serde_json::json!({
267 "input": {
268 "user_id": 123,
269 "action": "fetch"
270 },
271 "options": {
272 "timeout": 10000,
273 "retry": true
274 }
275 });
276
277 assert!(body.get("input").is_some());
278 assert!(body.get("options").is_some());
279 }
280
281 #[test]
284 fn test_chain_id_with_special_characters() {
285 let chain_id = "chain-with-dashes_and_underscores";
286 let path = format!("/chains/{}", chain_id);
287 assert!(path.contains(chain_id));
288 }
289
290 #[test]
291 fn test_chain_id_uuid_format() {
292 let chain_id = "550e8400-e29b-41d4-a716-446655440000";
293 let path = format!("/chains/{}", chain_id);
294 assert!(path.contains(chain_id));
295 }
296
297 #[test]
298 fn test_empty_body_is_none() {
299 let body: Option<Value> = None;
300 assert!(body.is_none());
301 }
302
303 #[test]
304 fn test_empty_json_body() {
305 let body = serde_json::json!({});
306 assert!(body.is_object());
307 assert!(body.as_object().unwrap().is_empty());
308 }
309
310 #[test]
313 fn test_method_selection_with_body() {
314 let body: Option<Value> = Some(serde_json::json!({"test": true}));
315 assert!(body.is_some());
317 }
318
319 #[test]
320 fn test_method_selection_without_body() {
321 let body: Option<Value> = None;
322 assert!(body.is_none());
324 }
325
326 #[test]
329 fn test_status_code_conversion_success() {
330 let status = StatusCode::OK.as_u16();
331 let converted = StatusCode::from_u16(status);
332 assert!(converted.is_ok());
333 assert_eq!(converted.unwrap(), StatusCode::OK);
334 }
335
336 #[test]
337 fn test_status_code_conversion_not_found() {
338 let status = 404u16;
339 let converted = StatusCode::from_u16(status);
340 assert!(converted.is_ok());
341 assert_eq!(converted.unwrap(), StatusCode::NOT_FOUND);
342 }
343
344 #[test]
345 fn test_status_code_conversion_server_error() {
346 let status = 500u16;
347 let converted = StatusCode::from_u16(status);
348 assert!(converted.is_ok());
349 assert_eq!(converted.unwrap(), StatusCode::INTERNAL_SERVER_ERROR);
350 }
351
352 #[test]
353 fn test_status_code_conversion_created() {
354 let status = 201u16;
355 let converted = StatusCode::from_u16(status);
356 assert!(converted.is_ok());
357 assert_eq!(converted.unwrap(), StatusCode::CREATED);
358 }
359
360 #[test]
361 fn test_status_code_conversion_bad_gateway() {
362 let status = 502u16;
363 let converted = StatusCode::from_u16(status);
364 assert!(converted.is_ok());
365 assert_eq!(converted.unwrap(), StatusCode::BAD_GATEWAY);
366 }
367}