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(http_addr, None, None, None, false, 8080, None, None, None, None, None)
131 }
132
133 #[tokio::test]
136 async fn test_proxy_to_http_server_no_addr() {
137 let state = create_test_state(None);
138 let response = proxy_to_http_server(&state, "/test", None).await;
139
140 let _ = response;
143 }
144
145 #[tokio::test]
146 async fn test_proxy_to_http_server_with_path() {
147 let state = create_test_state(None);
148 let response = proxy_to_http_server(&state, "/chains/123", None).await;
149 let _ = response;
150 }
151
152 #[tokio::test]
153 async fn test_proxy_to_http_server_with_body() {
154 let state = create_test_state(None);
155 let body = serde_json::json!({"name": "test-chain"});
156 let response = proxy_to_http_server(&state, "/chains", Some(body)).await;
157 let _ = response;
158 }
159
160 #[test]
163 fn test_admin_state_creation() {
164 let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
165 let state = create_test_state(Some(addr));
166 assert!(state.http_server_addr.is_some());
167 }
168
169 #[test]
170 fn test_admin_state_no_http_addr() {
171 let state = create_test_state(None);
172 assert!(state.http_server_addr.is_none());
173 }
174
175 #[test]
178 fn test_chain_path_construction() {
179 let chain_id = "chain-123";
180 let path = format!("/chains/{}", chain_id);
181 assert_eq!(path, "/chains/chain-123");
182 }
183
184 #[test]
185 fn test_chain_execute_path_construction() {
186 let chain_id = "exec-chain";
187 let path = format!("/chains/{}/execute", chain_id);
188 assert_eq!(path, "/chains/exec-chain/execute");
189 }
190
191 #[test]
192 fn test_chain_validate_path_construction() {
193 let chain_id = "validate-chain";
194 let path = format!("/chains/{}/validate", chain_id);
195 assert_eq!(path, "/chains/validate-chain/validate");
196 }
197
198 #[test]
199 fn test_chain_history_path_construction() {
200 let chain_id = "history-chain";
201 let path = format!("/chains/{}/history", chain_id);
202 assert_eq!(path, "/chains/history-chain/history");
203 }
204
205 #[test]
208 fn test_url_construction_chains_endpoint() {
209 let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
210 let path = "/chains";
211 let url = format!("http://{}/__mockforge{}", addr, path);
212 assert_eq!(url, "http://127.0.0.1:8080/__mockforge/chains");
213 }
214
215 #[test]
216 fn test_url_construction_with_id() {
217 let addr: SocketAddr = "192.168.1.1:3000".parse().unwrap();
218 let chain_id = "abc123";
219 let url = format!("http://{}/__mockforge/chains/{}", addr, chain_id);
220 assert_eq!(url, "http://192.168.1.1:3000/__mockforge/chains/abc123");
221 }
222
223 #[test]
224 fn test_url_construction_ipv6() {
225 let addr: SocketAddr = "[::1]:8080".parse().unwrap();
226 let path = "/chains";
227 let url = format!("http://{}/__mockforge{}", addr, path);
228 assert_eq!(url, "http://[::1]:8080/__mockforge/chains");
229 }
230
231 #[test]
234 fn test_chain_create_body() {
235 let body = serde_json::json!({
236 "name": "test-chain",
237 "steps": [
238 {"type": "http", "endpoint": "/api/users"},
239 {"type": "transform", "expression": "$.data"}
240 ]
241 });
242
243 assert!(body.get("name").is_some());
244 assert!(body.get("steps").is_some());
245 let steps = body.get("steps").unwrap().as_array().unwrap();
246 assert_eq!(steps.len(), 2);
247 }
248
249 #[test]
250 fn test_chain_update_body() {
251 let body = serde_json::json!({
252 "name": "updated-chain",
253 "enabled": true,
254 "timeout_ms": 5000
255 });
256
257 assert_eq!(body.get("name").unwrap(), "updated-chain");
258 assert_eq!(body.get("enabled").unwrap(), true);
259 }
260
261 #[test]
262 fn test_chain_execute_body() {
263 let body = serde_json::json!({
264 "input": {
265 "user_id": 123,
266 "action": "fetch"
267 },
268 "options": {
269 "timeout": 10000,
270 "retry": true
271 }
272 });
273
274 assert!(body.get("input").is_some());
275 assert!(body.get("options").is_some());
276 }
277
278 #[test]
281 fn test_chain_id_with_special_characters() {
282 let chain_id = "chain-with-dashes_and_underscores";
283 let path = format!("/chains/{}", chain_id);
284 assert!(path.contains(chain_id));
285 }
286
287 #[test]
288 fn test_chain_id_uuid_format() {
289 let chain_id = "550e8400-e29b-41d4-a716-446655440000";
290 let path = format!("/chains/{}", chain_id);
291 assert!(path.contains(chain_id));
292 }
293
294 #[test]
295 fn test_empty_body_is_none() {
296 let body: Option<Value> = None;
297 assert!(body.is_none());
298 }
299
300 #[test]
301 fn test_empty_json_body() {
302 let body = serde_json::json!({});
303 assert!(body.is_object());
304 assert!(body.as_object().unwrap().is_empty());
305 }
306
307 #[test]
310 fn test_method_selection_with_body() {
311 let body: Option<Value> = Some(serde_json::json!({"test": true}));
312 assert!(body.is_some());
314 }
315
316 #[test]
317 fn test_method_selection_without_body() {
318 let body: Option<Value> = None;
319 assert!(body.is_none());
321 }
322
323 #[test]
326 fn test_status_code_conversion_success() {
327 let status = reqwest::StatusCode::OK.as_u16();
328 let converted = StatusCode::from_u16(status);
329 assert!(converted.is_ok());
330 assert_eq!(converted.unwrap(), StatusCode::OK);
331 }
332
333 #[test]
334 fn test_status_code_conversion_not_found() {
335 let status = 404u16;
336 let converted = StatusCode::from_u16(status);
337 assert!(converted.is_ok());
338 assert_eq!(converted.unwrap(), StatusCode::NOT_FOUND);
339 }
340
341 #[test]
342 fn test_status_code_conversion_server_error() {
343 let status = 500u16;
344 let converted = StatusCode::from_u16(status);
345 assert!(converted.is_ok());
346 assert_eq!(converted.unwrap(), StatusCode::INTERNAL_SERVER_ERROR);
347 }
348
349 #[test]
350 fn test_status_code_conversion_created() {
351 let status = 201u16;
352 let converted = StatusCode::from_u16(status);
353 assert!(converted.is_ok());
354 assert_eq!(converted.unwrap(), StatusCode::CREATED);
355 }
356
357 #[test]
358 fn test_status_code_conversion_bad_gateway() {
359 let status = 502u16;
360 let converted = StatusCode::from_u16(status);
361 assert!(converted.is_ok());
362 assert_eq!(converted.unwrap(), StatusCode::BAD_GATEWAY);
363 }
364}