1use axum::{
6 extract::{Path, State},
7 http::StatusCode,
8 response::{IntoResponse, Json},
9 routing::{get, post},
10 Router,
11};
12use mockforge_core::config::ServerConfig;
13use mockforge_core::import::asyncapi_import::import_asyncapi_spec;
14use mockforge_core::import::openapi_import::import_openapi_spec;
15use serde::{Deserialize, Serialize};
16use std::sync::Arc;
17use tokio::sync::RwLock;
18use tracing::*;
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct EndpointConfig {
23 pub id: String,
25 pub protocol: Protocol,
27 pub name: String,
29 pub description: Option<String>,
31 pub enabled: bool,
33 pub config: EndpointProtocolConfig,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
39#[serde(rename_all = "lowercase")]
40pub enum Protocol {
41 Http,
43 Grpc,
45 Websocket,
47 Graphql,
49 Mqtt,
51 Smtp,
53 Kafka,
55 Amqp,
57 Ftp,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
63#[serde(tag = "type")]
64pub enum EndpointProtocolConfig {
65 Http(HttpEndpointConfig),
67 Grpc(GrpcEndpointConfig),
69 Websocket(WebsocketEndpointConfig),
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct HttpEndpointConfig {
76 pub method: String,
78 pub path: String,
80 pub request: Option<HttpRequestConfig>,
82 pub response: HttpResponseConfig,
84 pub behavior: Option<EndpointBehavior>,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct HttpRequestConfig {
91 pub validation: Option<ValidationConfig>,
93 pub headers: Option<Vec<HeaderConfig>>,
95 pub query_params: Option<Vec<QueryParamConfig>>,
97 pub body_schema: Option<serde_json::Value>,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct HttpResponseConfig {
104 pub status: u16,
106 pub headers: Option<Vec<HeaderConfig>>,
108 pub body: ResponseBody,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
114#[serde(tag = "type")]
115pub enum ResponseBody {
116 Static {
118 content: serde_json::Value,
120 },
121 Template {
123 template: String,
125 },
126 Faker {
128 schema: serde_json::Value,
130 },
131 AI {
133 prompt: String,
135 },
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct HeaderConfig {
141 pub name: String,
143 pub value: String,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct QueryParamConfig {
150 pub name: String,
152 pub required: bool,
154 pub schema: Option<serde_json::Value>,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct ValidationConfig {
161 pub mode: ValidationMode,
163 pub schema: Option<serde_json::Value>,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
169#[serde(rename_all = "lowercase")]
170pub enum ValidationMode {
171 Off,
173 Warn,
175 Enforce,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct EndpointBehavior {
182 pub latency: Option<LatencyConfig>,
184 pub failure: Option<FailureConfig>,
186 pub traffic_shaping: Option<TrafficShapingConfig>,
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct LatencyConfig {
193 pub base_ms: u64,
195 pub jitter_ms: u64,
197 pub distribution: LatencyDistribution,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize)]
203#[serde(rename_all = "lowercase")]
204pub enum LatencyDistribution {
205 Fixed,
207 Normal {
209 std_dev_ms: f64,
211 },
212 Pareto {
214 shape: f64,
216 },
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize)]
221pub struct FailureConfig {
222 pub error_rate: f64,
224 pub status_codes: Vec<u16>,
226 pub error_message: Option<String>,
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct TrafficShapingConfig {
233 pub bandwidth_limit_bps: Option<u64>,
235 pub packet_loss_rate: Option<f64>,
237}
238
239#[derive(Debug, Clone, Serialize, Deserialize)]
241pub struct GrpcEndpointConfig {
242 pub service: String,
244 pub method: String,
246 pub proto_file: String,
248 pub request_type: String,
250 pub response_type: String,
252 pub response: GrpcResponseConfig,
254 pub behavior: Option<EndpointBehavior>,
256}
257
258#[derive(Debug, Clone, Serialize, Deserialize)]
260pub struct GrpcResponseConfig {
261 pub body: ResponseBody,
263 pub metadata: Option<Vec<HeaderConfig>>,
265}
266
267#[derive(Debug, Clone, Serialize, Deserialize)]
269pub struct WebsocketEndpointConfig {
270 pub path: String,
272 pub on_connect: Option<WebsocketAction>,
274 pub on_message: Option<WebsocketAction>,
276 pub on_disconnect: Option<WebsocketAction>,
278 pub behavior: Option<EndpointBehavior>,
280}
281
282#[derive(Debug, Clone, Serialize, Deserialize)]
284#[serde(tag = "type")]
285pub enum WebsocketAction {
286 Send {
288 message: ResponseBody,
290 },
291 Broadcast {
293 message: ResponseBody,
295 },
296 Echo,
298 Close {
300 code: u16,
302 reason: String,
304 },
305}
306
307#[derive(Debug, Clone, Serialize, Deserialize)]
309pub struct ValidationResult {
310 pub valid: bool,
312 pub errors: Vec<ValidationError>,
314 pub warnings: Vec<String>,
316}
317
318#[derive(Debug, Clone, Serialize, Deserialize)]
320pub struct ValidationError {
321 pub field: String,
323 pub message: String,
325}
326
327#[derive(Clone)]
329pub struct UIBuilderState {
330 pub endpoints: Arc<RwLock<Vec<EndpointConfig>>>,
332 pub server_config: Arc<RwLock<ServerConfig>>,
334}
335
336impl UIBuilderState {
337 pub fn new(server_config: ServerConfig) -> Self {
342 Self {
343 endpoints: Arc::new(RwLock::new(Vec::new())),
344 server_config: Arc::new(RwLock::new(server_config)),
345 }
346 }
347}
348
349async fn list_endpoints(State(state): State<UIBuilderState>) -> Json<serde_json::Value> {
351 let endpoints = state.endpoints.read().await;
352 Json(serde_json::json!({
353 "endpoints": *endpoints,
354 "total": endpoints.len(),
355 "enabled": endpoints.iter().filter(|e| e.enabled).count(),
356 "by_protocol": {
357 "http": endpoints.iter().filter(|e| e.protocol == Protocol::Http).count(),
358 "grpc": endpoints.iter().filter(|e| e.protocol == Protocol::Grpc).count(),
359 "websocket": endpoints.iter().filter(|e| e.protocol == Protocol::Websocket).count(),
360 }
361 }))
362}
363
364async fn get_endpoint(
366 State(state): State<UIBuilderState>,
367 Path(id): Path<String>,
368) -> Result<Json<EndpointConfig>, StatusCode> {
369 let endpoints = state.endpoints.read().await;
370 endpoints
371 .iter()
372 .find(|e| e.id == id)
373 .cloned()
374 .map(Json)
375 .ok_or(StatusCode::NOT_FOUND)
376}
377
378async fn create_endpoint(
380 State(state): State<UIBuilderState>,
381 Json(mut endpoint): Json<EndpointConfig>,
382) -> Result<Json<EndpointConfig>, StatusCode> {
383 let mut endpoints = state.endpoints.write().await;
384
385 if endpoint.id.is_empty() {
387 endpoint.id = uuid::Uuid::new_v4().to_string();
388 }
389
390 if endpoints.iter().any(|e| e.id == endpoint.id) {
392 return Err(StatusCode::CONFLICT);
393 }
394
395 info!(
396 endpoint_id = %endpoint.id,
397 protocol = ?endpoint.protocol,
398 "Creating new endpoint"
399 );
400
401 endpoints.push(endpoint.clone());
402 Ok(Json(endpoint))
403}
404
405async fn update_endpoint(
407 State(state): State<UIBuilderState>,
408 Path(id): Path<String>,
409 Json(updated): Json<EndpointConfig>,
410) -> Result<Json<EndpointConfig>, StatusCode> {
411 let mut endpoints = state.endpoints.write().await;
412
413 let endpoint = endpoints.iter_mut().find(|e| e.id == id).ok_or(StatusCode::NOT_FOUND)?;
414
415 info!(
416 endpoint_id = %id,
417 protocol = ?updated.protocol,
418 "Updating endpoint"
419 );
420
421 *endpoint = updated.clone();
422 Ok(Json(updated))
423}
424
425async fn delete_endpoint(
427 State(state): State<UIBuilderState>,
428 Path(id): Path<String>,
429) -> Result<StatusCode, StatusCode> {
430 let mut endpoints = state.endpoints.write().await;
431
432 let index = endpoints.iter().position(|e| e.id == id).ok_or(StatusCode::NOT_FOUND)?;
433
434 info!(endpoint_id = %id, "Deleting endpoint");
435
436 endpoints.remove(index);
437 Ok(StatusCode::NO_CONTENT)
438}
439
440async fn validate_endpoint(
442 State(_state): State<UIBuilderState>,
443 Json(endpoint): Json<EndpointConfig>,
444) -> Json<ValidationResult> {
445 let mut errors = Vec::new();
446 let mut warnings = Vec::new();
447
448 match &endpoint.config {
450 EndpointProtocolConfig::Http(http_config) => {
451 let valid_methods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
453 if !valid_methods.contains(&http_config.method.to_uppercase().as_str()) {
454 errors.push(ValidationError {
455 field: "method".to_string(),
456 message: format!("Invalid HTTP method: {}", http_config.method),
457 });
458 }
459
460 if !http_config.path.starts_with('/') {
462 errors.push(ValidationError {
463 field: "path".to_string(),
464 message: "Path must start with /".to_string(),
465 });
466 }
467
468 if !(100..600).contains(&http_config.response.status) {
470 errors.push(ValidationError {
471 field: "status".to_string(),
472 message: "Status code must be between 100 and 599".to_string(),
473 });
474 }
475 }
476 EndpointProtocolConfig::Grpc(grpc_config) => {
477 if grpc_config.service.is_empty() {
479 errors.push(ValidationError {
480 field: "service".to_string(),
481 message: "Service name is required".to_string(),
482 });
483 }
484 if grpc_config.method.is_empty() {
485 errors.push(ValidationError {
486 field: "method".to_string(),
487 message: "Method name is required".to_string(),
488 });
489 }
490 }
491 EndpointProtocolConfig::Websocket(ws_config) => {
492 if !ws_config.path.starts_with('/') {
494 errors.push(ValidationError {
495 field: "path".to_string(),
496 message: "Path must start with /".to_string(),
497 });
498 }
499 }
500 }
501
502 if let Some(EndpointProtocolConfig::Http(http_config)) = Some(&endpoint.config) {
504 if let Some(behavior) = &http_config.behavior {
505 if let Some(failure) = &behavior.failure {
506 if failure.error_rate > 0.5 {
507 warnings.push("High error rate configured (>50%)".to_string());
508 }
509 }
510 }
511 }
512
513 Json(ValidationResult {
514 valid: errors.is_empty(),
515 errors,
516 warnings,
517 })
518}
519
520async fn export_config(
522 State(state): State<UIBuilderState>,
523) -> Result<impl IntoResponse, StatusCode> {
524 let server_config = state.server_config.read().await;
525
526 match serde_yaml::to_string(&*server_config) {
527 Ok(yaml) => Ok((StatusCode::OK, [("Content-Type", "application/x-yaml")], yaml)),
528 Err(e) => {
529 error!(error = %e, "Failed to serialize config to YAML");
530 Err(StatusCode::INTERNAL_SERVER_ERROR)
531 }
532 }
533}
534
535#[derive(Debug, Deserialize)]
537struct ImportRequest {
538 config: String,
539 format: ConfigFormat,
540}
541
542#[derive(Debug, Deserialize)]
543#[serde(rename_all = "lowercase")]
544enum ConfigFormat {
545 Yaml,
546 Json,
547}
548
549async fn import_config(
550 State(state): State<UIBuilderState>,
551 Json(request): Json<ImportRequest>,
552) -> Result<Json<serde_json::Value>, StatusCode> {
553 let config: ServerConfig = match request.format {
554 ConfigFormat::Yaml => serde_yaml::from_str(&request.config).map_err(|e| {
555 error!(error = %e, "Failed to parse YAML config");
556 StatusCode::BAD_REQUEST
557 })?,
558 ConfigFormat::Json => serde_json::from_str(&request.config).map_err(|e| {
559 error!(error = %e, "Failed to parse JSON config");
560 StatusCode::BAD_REQUEST
561 })?,
562 };
563
564 let mut server_config = state.server_config.write().await;
565 *server_config = config;
566
567 info!("Configuration imported successfully");
568
569 Ok(Json(serde_json::json!({
570 "success": true,
571 "message": "Configuration imported successfully"
572 })))
573}
574
575async fn get_config(State(state): State<UIBuilderState>) -> Json<ServerConfig> {
577 let config = state.server_config.read().await;
578 Json(config.clone())
579}
580
581async fn update_config(
583 State(state): State<UIBuilderState>,
584 Json(new_config): Json<ServerConfig>,
585) -> Result<Json<ServerConfig>, StatusCode> {
586 let mut config = state.server_config.write().await;
587 *config = new_config.clone();
588
589 info!("Server configuration updated");
590
591 Ok(Json(new_config))
592}
593
594#[derive(Debug, Deserialize)]
596struct ImportOpenApiRequest {
597 content: String,
598 base_url: Option<String>,
599 auto_enable: Option<bool>,
600}
601
602#[derive(Debug, Serialize)]
604struct ImportOpenApiResponse {
605 success: bool,
606 endpoints_created: usize,
607 warnings: Vec<String>,
608 spec_info: OpenApiSpecInfoResponse,
609}
610
611#[derive(Debug, Serialize)]
612struct OpenApiSpecInfoResponse {
613 title: String,
614 version: String,
615 description: Option<String>,
616 openapi_version: String,
617 servers: Vec<String>,
618}
619
620async fn import_openapi_spec_handler(
622 State(state): State<UIBuilderState>,
623 Json(request): Json<ImportOpenApiRequest>,
624) -> Result<Json<ImportOpenApiResponse>, (StatusCode, Json<serde_json::Value>)> {
625 info!("Importing OpenAPI specification");
626
627 let import_result = import_openapi_spec(&request.content, request.base_url.as_deref())
629 .map_err(|e| {
630 error!(error = %e, "Failed to import OpenAPI spec");
631 (
632 StatusCode::BAD_REQUEST,
633 Json(serde_json::json!({
634 "error": "Failed to import OpenAPI specification",
635 "details": e
636 })),
637 )
638 })?;
639
640 let auto_enable = request.auto_enable.unwrap_or(true);
641 let mut endpoints = state.endpoints.write().await;
642 let mut created_count = 0;
643
644 for route in import_result.routes {
646 let endpoint_id = uuid::Uuid::new_v4().to_string();
647
648 let response_body = ResponseBody::Static {
650 content: route.response.body,
651 };
652
653 let response_headers: Option<Vec<HeaderConfig>> = if route.response.headers.is_empty() {
655 None
656 } else {
657 Some(
658 route
659 .response
660 .headers
661 .into_iter()
662 .map(|(name, value)| HeaderConfig { name, value })
663 .collect(),
664 )
665 };
666
667 let endpoint = EndpointConfig {
668 id: endpoint_id.clone(),
669 protocol: Protocol::Http,
670 name: format!("{} {}", route.method.to_uppercase(), route.path),
671 description: Some(format!(
672 "Auto-generated from OpenAPI spec: {} v{}",
673 import_result.spec_info.title, import_result.spec_info.version
674 )),
675 enabled: auto_enable,
676 config: EndpointProtocolConfig::Http(HttpEndpointConfig {
677 method: route.method.to_uppercase(),
678 path: route.path,
679 request: None,
680 response: HttpResponseConfig {
681 status: route.response.status,
682 headers: response_headers,
683 body: response_body,
684 },
685 behavior: None,
686 }),
687 };
688
689 info!(
690 endpoint_id = %endpoint_id,
691 method = %endpoint.name,
692 "Created endpoint from OpenAPI spec"
693 );
694
695 endpoints.push(endpoint);
696 created_count += 1;
697 }
698
699 Ok(Json(ImportOpenApiResponse {
700 success: true,
701 endpoints_created: created_count,
702 warnings: import_result.warnings,
703 spec_info: OpenApiSpecInfoResponse {
704 title: import_result.spec_info.title,
705 version: import_result.spec_info.version,
706 description: import_result.spec_info.description,
707 openapi_version: import_result.spec_info.openapi_version,
708 servers: import_result.spec_info.servers,
709 },
710 }))
711}
712
713async fn export_openapi_spec_handler(
715 State(state): State<UIBuilderState>,
716) -> Result<impl IntoResponse, StatusCode> {
717 let endpoints = state.endpoints.read().await;
718 let server_config = state.server_config.read().await;
719
720 let http_host = &server_config.http.host;
722 let http_port = server_config.http.port;
723
724 let mut spec = serde_json::json!({
725 "openapi": "3.0.0",
726 "info": {
727 "title": "MockForge Generated API",
728 "version": "1.0.0",
729 "description": "API specification generated from MockForge endpoints"
730 },
731 "servers": [
732 {
733 "url": format!("http://{}:{}", http_host, http_port)
734 }
735 ],
736 "paths": {}
737 });
738
739 let paths = spec["paths"].as_object_mut().ok_or(StatusCode::INTERNAL_SERVER_ERROR)?;
741
742 for endpoint in endpoints.iter() {
744 if endpoint.protocol != Protocol::Http {
745 continue; }
747
748 if let EndpointProtocolConfig::Http(http_config) = &endpoint.config {
749 let path = &http_config.path;
750
751 if !paths.contains_key(path) {
752 paths.insert(path.clone(), serde_json::json!({}));
753 }
754
755 let method = http_config.method.to_lowercase();
756
757 let response_body_content = match &http_config.response.body {
759 ResponseBody::Static { content } => content.clone(),
760 ResponseBody::Template { template } => serde_json::json!({
761 "type": "string",
762 "example": template
763 }),
764 ResponseBody::Faker { schema } => schema.clone(),
765 ResponseBody::AI { prompt } => serde_json::json!({
766 "type": "string",
767 "description": prompt
768 }),
769 };
770
771 let operation = serde_json::json!({
772 "summary": &endpoint.name,
773 "description": endpoint.description.as_ref().unwrap_or(&String::new()),
774 "operationId": &endpoint.id,
775 "responses": {
776 http_config.response.status.to_string(): {
777 "description": format!("Response with status {}", http_config.response.status),
778 "content": {
779 "application/json": {
780 "schema": {
781 "type": "object"
782 },
783 "example": response_body_content
784 }
785 }
786 }
787 }
788 });
789
790 paths[path][&method] = operation;
791 }
792 }
793
794 match serde_json::to_string_pretty(&spec) {
795 Ok(json) => Ok((StatusCode::OK, [("Content-Type", "application/json")], json)),
796 Err(e) => {
797 error!(error = %e, "Failed to serialize OpenAPI spec to JSON");
798 Err(StatusCode::INTERNAL_SERVER_ERROR)
799 }
800 }
801}
802
803#[derive(Debug, Deserialize)]
805struct ImportAsyncApiRequest {
806 content: String,
807 base_url: Option<String>,
808 auto_enable: Option<bool>,
809}
810
811#[derive(Debug, Serialize)]
813struct ImportAsyncApiResponse {
814 success: bool,
815 endpoints_created: usize,
816 warnings: Vec<String>,
817 spec_info: AsyncApiSpecInfoResponse,
818}
819
820#[derive(Debug, Serialize)]
821struct AsyncApiSpecInfoResponse {
822 title: String,
823 version: String,
824 description: Option<String>,
825 asyncapi_version: String,
826 servers: Vec<String>,
827}
828
829async fn import_asyncapi_spec_handler(
831 State(state): State<UIBuilderState>,
832 Json(request): Json<ImportAsyncApiRequest>,
833) -> Result<Json<ImportAsyncApiResponse>, (StatusCode, Json<serde_json::Value>)> {
834 info!("Importing AsyncAPI specification");
835
836 let import_result = import_asyncapi_spec(&request.content, request.base_url.as_deref())
838 .map_err(|e| {
839 error!(error = %e, "Failed to import AsyncAPI spec");
840 (
841 StatusCode::BAD_REQUEST,
842 Json(serde_json::json!({
843 "error": "Failed to import AsyncAPI specification",
844 "details": e
845 })),
846 )
847 })?;
848
849 let auto_enable = request.auto_enable.unwrap_or(true);
850 let mut endpoints = state.endpoints.write().await;
851 let mut created_count = 0;
852
853 for channel in import_result.channels {
855 let endpoint_id = uuid::Uuid::new_v4().to_string();
856
857 let (protocol, config) = match channel.protocol {
859 mockforge_core::import::asyncapi_import::ChannelProtocol::Websocket => {
860 let on_message = if let Some(op) = channel.operations.first() {
862 if let Some(example) = &op.example_message {
863 Some(WebsocketAction::Send {
864 message: ResponseBody::Static {
865 content: example.clone(),
866 },
867 })
868 } else {
869 Some(WebsocketAction::Echo)
870 }
871 } else {
872 Some(WebsocketAction::Echo)
873 };
874
875 (
876 Protocol::Websocket,
877 EndpointProtocolConfig::Websocket(WebsocketEndpointConfig {
878 path: channel.path.clone(),
879 on_connect: None,
880 on_message,
881 on_disconnect: None,
882 behavior: None,
883 }),
884 )
885 }
886 _ => {
887 warn!(
890 "Skipping channel '{}' with protocol {:?} - not yet supported for UI Builder",
891 channel.name, channel.protocol
892 );
893 continue;
894 }
895 };
896
897 let endpoint = EndpointConfig {
898 id: endpoint_id.clone(),
899 protocol,
900 name: format!("{} - {}", channel.name, channel.path),
901 description: channel.description.or_else(|| {
902 Some(format!(
903 "Auto-generated from AsyncAPI spec: {} v{}",
904 import_result.spec_info.title, import_result.spec_info.version
905 ))
906 }),
907 enabled: auto_enable,
908 config,
909 };
910
911 info!(
912 endpoint_id = %endpoint_id,
913 name = %endpoint.name,
914 "Created endpoint from AsyncAPI spec"
915 );
916
917 endpoints.push(endpoint);
918 created_count += 1;
919 }
920
921 Ok(Json(ImportAsyncApiResponse {
922 success: true,
923 endpoints_created: created_count,
924 warnings: import_result.warnings,
925 spec_info: AsyncApiSpecInfoResponse {
926 title: import_result.spec_info.title,
927 version: import_result.spec_info.version,
928 description: import_result.spec_info.description,
929 asyncapi_version: import_result.spec_info.asyncapi_version,
930 servers: import_result.spec_info.servers,
931 },
932 }))
933}
934
935pub async fn resolve_response_body_tokens(
937 body: &ResponseBody,
938) -> Result<serde_json::Value, String> {
939 use crate::token_response::resolve_response_tokens;
940
941 match body {
942 ResponseBody::Static { content } => resolve_response_tokens(content.clone()).await,
943 ResponseBody::Template { template } => {
944 let value = serde_json::Value::String(template.clone());
946 resolve_response_tokens(value).await
947 }
948 ResponseBody::Faker { schema } => {
949 resolve_response_tokens(schema.clone()).await
951 }
952 ResponseBody::AI { prompt: _ } => {
953 Ok(serde_json::json!({"_ai_prompt": true}))
956 }
957 }
958}
959
960pub fn create_ui_builder_router(state: UIBuilderState) -> Router {
962 Router::new()
963 .route("/endpoints", get(list_endpoints).post(create_endpoint))
964 .route(
965 "/endpoints/{id}",
966 get(get_endpoint).put(update_endpoint).delete(delete_endpoint),
967 )
968 .route("/endpoints/validate", post(validate_endpoint))
969 .route("/config", get(get_config).put(update_config))
970 .route("/config/export", get(export_config))
971 .route("/config/import", post(import_config))
972 .route("/openapi/import", post(import_openapi_spec_handler))
973 .route("/openapi/export", get(export_openapi_spec_handler))
974 .route("/asyncapi/import", post(import_asyncapi_spec_handler))
975 .with_state(state)
976}
977
978#[cfg(test)]
979mod tests {
980 use super::*;
981
982 fn create_test_http_endpoint() -> EndpointConfig {
985 EndpointConfig {
986 id: "test-1".to_string(),
987 protocol: Protocol::Http,
988 name: "Test Endpoint".to_string(),
989 description: Some("A test endpoint".to_string()),
990 enabled: true,
991 config: EndpointProtocolConfig::Http(HttpEndpointConfig {
992 method: "GET".to_string(),
993 path: "/test".to_string(),
994 request: None,
995 response: HttpResponseConfig {
996 status: 200,
997 headers: None,
998 body: ResponseBody::Static {
999 content: serde_json::json!({"message": "Hello"}),
1000 },
1001 },
1002 behavior: None,
1003 }),
1004 }
1005 }
1006
1007 #[test]
1010 fn test_protocol_http_serialization() {
1011 let protocol = Protocol::Http;
1012 let json = serde_json::to_string(&protocol).unwrap();
1013 assert_eq!(json, "\"http\"");
1014 }
1015
1016 #[test]
1017 fn test_protocol_grpc_serialization() {
1018 let protocol = Protocol::Grpc;
1019 let json = serde_json::to_string(&protocol).unwrap();
1020 assert_eq!(json, "\"grpc\"");
1021 }
1022
1023 #[test]
1024 fn test_protocol_websocket_serialization() {
1025 let protocol = Protocol::Websocket;
1026 let json = serde_json::to_string(&protocol).unwrap();
1027 assert_eq!(json, "\"websocket\"");
1028 }
1029
1030 #[test]
1031 fn test_protocol_graphql_serialization() {
1032 let protocol = Protocol::Graphql;
1033 let json = serde_json::to_string(&protocol).unwrap();
1034 assert_eq!(json, "\"graphql\"");
1035 }
1036
1037 #[test]
1038 fn test_protocol_mqtt_serialization() {
1039 let protocol = Protocol::Mqtt;
1040 let json = serde_json::to_string(&protocol).unwrap();
1041 assert_eq!(json, "\"mqtt\"");
1042 }
1043
1044 #[test]
1045 fn test_protocol_smtp_serialization() {
1046 let protocol = Protocol::Smtp;
1047 let json = serde_json::to_string(&protocol).unwrap();
1048 assert_eq!(json, "\"smtp\"");
1049 }
1050
1051 #[test]
1052 fn test_protocol_kafka_serialization() {
1053 let protocol = Protocol::Kafka;
1054 let json = serde_json::to_string(&protocol).unwrap();
1055 assert_eq!(json, "\"kafka\"");
1056 }
1057
1058 #[test]
1059 fn test_protocol_amqp_serialization() {
1060 let protocol = Protocol::Amqp;
1061 let json = serde_json::to_string(&protocol).unwrap();
1062 assert_eq!(json, "\"amqp\"");
1063 }
1064
1065 #[test]
1066 fn test_protocol_ftp_serialization() {
1067 let protocol = Protocol::Ftp;
1068 let json = serde_json::to_string(&protocol).unwrap();
1069 assert_eq!(json, "\"ftp\"");
1070 }
1071
1072 #[test]
1073 fn test_protocol_deserialization() {
1074 let json = "\"http\"";
1075 let protocol: Protocol = serde_json::from_str(json).unwrap();
1076 assert_eq!(protocol, Protocol::Http);
1077 }
1078
1079 #[test]
1080 fn test_protocol_equality() {
1081 assert_eq!(Protocol::Http, Protocol::Http);
1082 assert_ne!(Protocol::Http, Protocol::Grpc);
1083 }
1084
1085 #[test]
1086 fn test_protocol_clone() {
1087 let protocol = Protocol::Websocket;
1088 let cloned = protocol.clone();
1089 assert_eq!(protocol, cloned);
1090 }
1091
1092 #[test]
1095 fn test_validation_mode_off_serialization() {
1096 let mode = ValidationMode::Off;
1097 let json = serde_json::to_string(&mode).unwrap();
1098 assert_eq!(json, "\"off\"");
1099 }
1100
1101 #[test]
1102 fn test_validation_mode_warn_serialization() {
1103 let mode = ValidationMode::Warn;
1104 let json = serde_json::to_string(&mode).unwrap();
1105 assert_eq!(json, "\"warn\"");
1106 }
1107
1108 #[test]
1109 fn test_validation_mode_enforce_serialization() {
1110 let mode = ValidationMode::Enforce;
1111 let json = serde_json::to_string(&mode).unwrap();
1112 assert_eq!(json, "\"enforce\"");
1113 }
1114
1115 #[test]
1116 fn test_validation_mode_deserialization() {
1117 let json = "\"enforce\"";
1118 let mode: ValidationMode = serde_json::from_str(json).unwrap();
1119 assert!(matches!(mode, ValidationMode::Enforce));
1120 }
1121
1122 #[test]
1125 fn test_header_config_creation() {
1126 let header = HeaderConfig {
1127 name: "Content-Type".to_string(),
1128 value: "application/json".to_string(),
1129 };
1130 assert_eq!(header.name, "Content-Type");
1131 assert_eq!(header.value, "application/json");
1132 }
1133
1134 #[test]
1135 fn test_header_config_serialization() {
1136 let header = HeaderConfig {
1137 name: "X-Custom-Header".to_string(),
1138 value: "custom-value".to_string(),
1139 };
1140 let json = serde_json::to_string(&header).unwrap();
1141 let deserialized: HeaderConfig = serde_json::from_str(&json).unwrap();
1142 assert_eq!(header.name, deserialized.name);
1143 assert_eq!(header.value, deserialized.value);
1144 }
1145
1146 #[test]
1147 fn test_header_config_clone() {
1148 let header = HeaderConfig {
1149 name: "Authorization".to_string(),
1150 value: "Bearer token".to_string(),
1151 };
1152 let cloned = header.clone();
1153 assert_eq!(header.name, cloned.name);
1154 assert_eq!(header.value, cloned.value);
1155 }
1156
1157 #[test]
1160 fn test_query_param_config_required() {
1161 let param = QueryParamConfig {
1162 name: "page".to_string(),
1163 required: true,
1164 schema: Some(serde_json::json!({"type": "integer"})),
1165 };
1166 assert!(param.required);
1167 assert!(param.schema.is_some());
1168 }
1169
1170 #[test]
1171 fn test_query_param_config_optional() {
1172 let param = QueryParamConfig {
1173 name: "filter".to_string(),
1174 required: false,
1175 schema: None,
1176 };
1177 assert!(!param.required);
1178 assert!(param.schema.is_none());
1179 }
1180
1181 #[test]
1182 fn test_query_param_config_serialization() {
1183 let param = QueryParamConfig {
1184 name: "limit".to_string(),
1185 required: true,
1186 schema: Some(serde_json::json!({"type": "integer", "maximum": 100})),
1187 };
1188 let json = serde_json::to_string(¶m).unwrap();
1189 let deserialized: QueryParamConfig = serde_json::from_str(&json).unwrap();
1190 assert_eq!(param.name, deserialized.name);
1191 assert_eq!(param.required, deserialized.required);
1192 }
1193
1194 #[test]
1197 fn test_validation_config_with_schema() {
1198 let config = ValidationConfig {
1199 mode: ValidationMode::Enforce,
1200 schema: Some(serde_json::json!({
1201 "type": "object",
1202 "properties": {
1203 "name": {"type": "string"}
1204 }
1205 })),
1206 };
1207 assert!(matches!(config.mode, ValidationMode::Enforce));
1208 assert!(config.schema.is_some());
1209 }
1210
1211 #[test]
1212 fn test_validation_config_without_schema() {
1213 let config = ValidationConfig {
1214 mode: ValidationMode::Off,
1215 schema: None,
1216 };
1217 assert!(matches!(config.mode, ValidationMode::Off));
1218 assert!(config.schema.is_none());
1219 }
1220
1221 #[test]
1224 fn test_latency_config_fixed() {
1225 let config = LatencyConfig {
1226 base_ms: 100,
1227 jitter_ms: 20,
1228 distribution: LatencyDistribution::Fixed,
1229 };
1230 assert_eq!(config.base_ms, 100);
1231 assert_eq!(config.jitter_ms, 20);
1232 }
1233
1234 #[test]
1235 fn test_latency_config_normal_distribution() {
1236 let config = LatencyConfig {
1237 base_ms: 50,
1238 jitter_ms: 10,
1239 distribution: LatencyDistribution::Normal { std_dev_ms: 15.0 },
1240 };
1241 assert!(matches!(config.distribution, LatencyDistribution::Normal { .. }));
1242 }
1243
1244 #[test]
1245 fn test_latency_config_pareto_distribution() {
1246 let config = LatencyConfig {
1247 base_ms: 75,
1248 jitter_ms: 25,
1249 distribution: LatencyDistribution::Pareto { shape: 1.5 },
1250 };
1251 assert!(matches!(config.distribution, LatencyDistribution::Pareto { .. }));
1252 }
1253
1254 #[test]
1255 fn test_latency_config_serialization() {
1256 let config = LatencyConfig {
1257 base_ms: 200,
1258 jitter_ms: 50,
1259 distribution: LatencyDistribution::Fixed,
1260 };
1261 let json = serde_json::to_string(&config).unwrap();
1262 let deserialized: LatencyConfig = serde_json::from_str(&json).unwrap();
1263 assert_eq!(config.base_ms, deserialized.base_ms);
1264 assert_eq!(config.jitter_ms, deserialized.jitter_ms);
1265 }
1266
1267 #[test]
1270 fn test_failure_config_creation() {
1271 let config = FailureConfig {
1272 error_rate: 0.1,
1273 status_codes: vec![500, 502, 503],
1274 error_message: Some("Server error".to_string()),
1275 };
1276 assert!((config.error_rate - 0.1).abs() < 0.001);
1277 assert_eq!(config.status_codes.len(), 3);
1278 assert!(config.error_message.is_some());
1279 }
1280
1281 #[test]
1282 fn test_failure_config_high_error_rate() {
1283 let config = FailureConfig {
1284 error_rate: 0.75,
1285 status_codes: vec![500],
1286 error_message: None,
1287 };
1288 assert!(config.error_rate > 0.5);
1289 }
1290
1291 #[test]
1292 fn test_failure_config_serialization() {
1293 let config = FailureConfig {
1294 error_rate: 0.25,
1295 status_codes: vec![429, 500],
1296 error_message: Some("Rate limited".to_string()),
1297 };
1298 let json = serde_json::to_string(&config).unwrap();
1299 let deserialized: FailureConfig = serde_json::from_str(&json).unwrap();
1300 assert_eq!(config.status_codes, deserialized.status_codes);
1301 }
1302
1303 #[test]
1306 fn test_traffic_shaping_config_bandwidth_limit() {
1307 let config = TrafficShapingConfig {
1308 bandwidth_limit_bps: Some(1024 * 1024), packet_loss_rate: None,
1310 };
1311 assert_eq!(config.bandwidth_limit_bps, Some(1024 * 1024));
1312 }
1313
1314 #[test]
1315 fn test_traffic_shaping_config_packet_loss() {
1316 let config = TrafficShapingConfig {
1317 bandwidth_limit_bps: None,
1318 packet_loss_rate: Some(0.05),
1319 };
1320 assert!(config.packet_loss_rate.is_some());
1321 }
1322
1323 #[test]
1324 fn test_traffic_shaping_config_both_enabled() {
1325 let config = TrafficShapingConfig {
1326 bandwidth_limit_bps: Some(500_000),
1327 packet_loss_rate: Some(0.02),
1328 };
1329 assert!(config.bandwidth_limit_bps.is_some());
1330 assert!(config.packet_loss_rate.is_some());
1331 }
1332
1333 #[test]
1336 fn test_endpoint_behavior_with_latency_only() {
1337 let behavior = EndpointBehavior {
1338 latency: Some(LatencyConfig {
1339 base_ms: 100,
1340 jitter_ms: 10,
1341 distribution: LatencyDistribution::Fixed,
1342 }),
1343 failure: None,
1344 traffic_shaping: None,
1345 };
1346 assert!(behavior.latency.is_some());
1347 assert!(behavior.failure.is_none());
1348 }
1349
1350 #[test]
1351 fn test_endpoint_behavior_with_failure_only() {
1352 let behavior = EndpointBehavior {
1353 latency: None,
1354 failure: Some(FailureConfig {
1355 error_rate: 0.1,
1356 status_codes: vec![500],
1357 error_message: None,
1358 }),
1359 traffic_shaping: None,
1360 };
1361 assert!(behavior.failure.is_some());
1362 }
1363
1364 #[test]
1365 fn test_endpoint_behavior_full_config() {
1366 let behavior = EndpointBehavior {
1367 latency: Some(LatencyConfig {
1368 base_ms: 50,
1369 jitter_ms: 10,
1370 distribution: LatencyDistribution::Fixed,
1371 }),
1372 failure: Some(FailureConfig {
1373 error_rate: 0.05,
1374 status_codes: vec![503],
1375 error_message: None,
1376 }),
1377 traffic_shaping: Some(TrafficShapingConfig {
1378 bandwidth_limit_bps: Some(100_000),
1379 packet_loss_rate: Some(0.01),
1380 }),
1381 };
1382 assert!(behavior.latency.is_some());
1383 assert!(behavior.failure.is_some());
1384 assert!(behavior.traffic_shaping.is_some());
1385 }
1386
1387 #[test]
1390 fn test_response_body_static() {
1391 let body = ResponseBody::Static {
1392 content: serde_json::json!({"message": "Hello"}),
1393 };
1394 if let ResponseBody::Static { content } = body {
1395 assert_eq!(content["message"], "Hello");
1396 } else {
1397 panic!("Expected Static response body");
1398 }
1399 }
1400
1401 #[test]
1402 fn test_response_body_template() {
1403 let body = ResponseBody::Template {
1404 template: "Hello, {{name}}!".to_string(),
1405 };
1406 if let ResponseBody::Template { template } = body {
1407 assert!(template.contains("{{name}}"));
1408 } else {
1409 panic!("Expected Template response body");
1410 }
1411 }
1412
1413 #[test]
1414 fn test_response_body_faker() {
1415 let body = ResponseBody::Faker {
1416 schema: serde_json::json!({
1417 "type": "object",
1418 "properties": {
1419 "id": {"type": "integer"},
1420 "name": {"type": "string"}
1421 }
1422 }),
1423 };
1424 if let ResponseBody::Faker { schema } = body {
1425 assert_eq!(schema["type"], "object");
1426 } else {
1427 panic!("Expected Faker response body");
1428 }
1429 }
1430
1431 #[test]
1432 fn test_response_body_ai() {
1433 let body = ResponseBody::AI {
1434 prompt: "Generate a user profile".to_string(),
1435 };
1436 if let ResponseBody::AI { prompt } = body {
1437 assert!(prompt.contains("user profile"));
1438 } else {
1439 panic!("Expected AI response body");
1440 }
1441 }
1442
1443 #[test]
1446 fn test_http_request_config_with_validation() {
1447 let config = HttpRequestConfig {
1448 validation: Some(ValidationConfig {
1449 mode: ValidationMode::Enforce,
1450 schema: Some(serde_json::json!({"type": "object"})),
1451 }),
1452 headers: None,
1453 query_params: None,
1454 body_schema: None,
1455 };
1456 assert!(config.validation.is_some());
1457 }
1458
1459 #[test]
1460 fn test_http_request_config_with_headers() {
1461 let config = HttpRequestConfig {
1462 validation: None,
1463 headers: Some(vec![HeaderConfig {
1464 name: "Authorization".to_string(),
1465 value: "Bearer token".to_string(),
1466 }]),
1467 query_params: None,
1468 body_schema: None,
1469 };
1470 assert_eq!(config.headers.as_ref().unwrap().len(), 1);
1471 }
1472
1473 #[test]
1474 fn test_http_request_config_with_query_params() {
1475 let config = HttpRequestConfig {
1476 validation: None,
1477 headers: None,
1478 query_params: Some(vec![
1479 QueryParamConfig {
1480 name: "page".to_string(),
1481 required: true,
1482 schema: None,
1483 },
1484 QueryParamConfig {
1485 name: "limit".to_string(),
1486 required: false,
1487 schema: None,
1488 },
1489 ]),
1490 body_schema: None,
1491 };
1492 assert_eq!(config.query_params.as_ref().unwrap().len(), 2);
1493 }
1494
1495 #[test]
1498 fn test_http_response_config_ok() {
1499 let config = HttpResponseConfig {
1500 status: 200,
1501 headers: None,
1502 body: ResponseBody::Static {
1503 content: serde_json::json!({"success": true}),
1504 },
1505 };
1506 assert_eq!(config.status, 200);
1507 }
1508
1509 #[test]
1510 fn test_http_response_config_not_found() {
1511 let config = HttpResponseConfig {
1512 status: 404,
1513 headers: Some(vec![HeaderConfig {
1514 name: "X-Error-Code".to_string(),
1515 value: "NOT_FOUND".to_string(),
1516 }]),
1517 body: ResponseBody::Static {
1518 content: serde_json::json!({"error": "Not found"}),
1519 },
1520 };
1521 assert_eq!(config.status, 404);
1522 assert!(config.headers.is_some());
1523 }
1524
1525 #[test]
1526 fn test_http_response_config_server_error() {
1527 let config = HttpResponseConfig {
1528 status: 500,
1529 headers: None,
1530 body: ResponseBody::Static {
1531 content: serde_json::json!({"error": "Internal server error"}),
1532 },
1533 };
1534 assert_eq!(config.status, 500);
1535 }
1536
1537 #[test]
1540 fn test_http_endpoint_config_get() {
1541 let config = HttpEndpointConfig {
1542 method: "GET".to_string(),
1543 path: "/api/users".to_string(),
1544 request: None,
1545 response: HttpResponseConfig {
1546 status: 200,
1547 headers: None,
1548 body: ResponseBody::Static {
1549 content: serde_json::json!([]),
1550 },
1551 },
1552 behavior: None,
1553 };
1554 assert_eq!(config.method, "GET");
1555 assert!(config.path.starts_with('/'));
1556 }
1557
1558 #[test]
1559 fn test_http_endpoint_config_post_with_request() {
1560 let config = HttpEndpointConfig {
1561 method: "POST".to_string(),
1562 path: "/api/users".to_string(),
1563 request: Some(HttpRequestConfig {
1564 validation: Some(ValidationConfig {
1565 mode: ValidationMode::Enforce,
1566 schema: Some(serde_json::json!({
1567 "type": "object",
1568 "required": ["name", "email"]
1569 })),
1570 }),
1571 headers: None,
1572 query_params: None,
1573 body_schema: None,
1574 }),
1575 response: HttpResponseConfig {
1576 status: 201,
1577 headers: None,
1578 body: ResponseBody::Static {
1579 content: serde_json::json!({"id": 1}),
1580 },
1581 },
1582 behavior: None,
1583 };
1584 assert_eq!(config.method, "POST");
1585 assert!(config.request.is_some());
1586 }
1587
1588 #[test]
1591 fn test_grpc_endpoint_config_creation() {
1592 let config = GrpcEndpointConfig {
1593 service: "users.UserService".to_string(),
1594 method: "GetUser".to_string(),
1595 proto_file: "/path/to/user.proto".to_string(),
1596 request_type: "GetUserRequest".to_string(),
1597 response_type: "GetUserResponse".to_string(),
1598 response: GrpcResponseConfig {
1599 body: ResponseBody::Static {
1600 content: serde_json::json!({"id": 1, "name": "John"}),
1601 },
1602 metadata: None,
1603 },
1604 behavior: None,
1605 };
1606 assert_eq!(config.service, "users.UserService");
1607 assert_eq!(config.method, "GetUser");
1608 }
1609
1610 #[test]
1611 fn test_grpc_endpoint_config_with_metadata() {
1612 let config = GrpcEndpointConfig {
1613 service: "example.ExampleService".to_string(),
1614 method: "DoSomething".to_string(),
1615 proto_file: "/path/to/example.proto".to_string(),
1616 request_type: "Request".to_string(),
1617 response_type: "Response".to_string(),
1618 response: GrpcResponseConfig {
1619 body: ResponseBody::Static {
1620 content: serde_json::json!({}),
1621 },
1622 metadata: Some(vec![HeaderConfig {
1623 name: "x-request-id".to_string(),
1624 value: "12345".to_string(),
1625 }]),
1626 },
1627 behavior: None,
1628 };
1629 assert!(config.response.metadata.is_some());
1630 }
1631
1632 #[test]
1635 fn test_websocket_endpoint_config_basic() {
1636 let config = WebsocketEndpointConfig {
1637 path: "/ws".to_string(),
1638 on_connect: None,
1639 on_message: Some(WebsocketAction::Echo),
1640 on_disconnect: None,
1641 behavior: None,
1642 };
1643 assert_eq!(config.path, "/ws");
1644 }
1645
1646 #[test]
1647 fn test_websocket_endpoint_config_with_send() {
1648 let config = WebsocketEndpointConfig {
1649 path: "/notifications".to_string(),
1650 on_connect: Some(WebsocketAction::Send {
1651 message: ResponseBody::Static {
1652 content: serde_json::json!({"type": "connected"}),
1653 },
1654 }),
1655 on_message: None,
1656 on_disconnect: None,
1657 behavior: None,
1658 };
1659 assert!(config.on_connect.is_some());
1660 }
1661
1662 #[test]
1663 fn test_websocket_endpoint_config_with_broadcast() {
1664 let config = WebsocketEndpointConfig {
1665 path: "/chat".to_string(),
1666 on_connect: None,
1667 on_message: Some(WebsocketAction::Broadcast {
1668 message: ResponseBody::Template {
1669 template: "{{message}}".to_string(),
1670 },
1671 }),
1672 on_disconnect: None,
1673 behavior: None,
1674 };
1675 if let Some(WebsocketAction::Broadcast { .. }) = config.on_message {
1676 } else {
1678 panic!("Expected Broadcast action");
1679 }
1680 }
1681
1682 #[test]
1683 fn test_websocket_endpoint_config_with_close() {
1684 let config = WebsocketEndpointConfig {
1685 path: "/stream".to_string(),
1686 on_connect: None,
1687 on_message: None,
1688 on_disconnect: Some(WebsocketAction::Close {
1689 code: 1000,
1690 reason: "Normal closure".to_string(),
1691 }),
1692 behavior: None,
1693 };
1694 if let Some(WebsocketAction::Close { code, reason }) = config.on_disconnect {
1695 assert_eq!(code, 1000);
1696 assert_eq!(reason, "Normal closure");
1697 } else {
1698 panic!("Expected Close action");
1699 }
1700 }
1701
1702 #[test]
1705 fn test_validation_result_valid() {
1706 let result = ValidationResult {
1707 valid: true,
1708 errors: vec![],
1709 warnings: vec![],
1710 };
1711 assert!(result.valid);
1712 assert!(result.errors.is_empty());
1713 }
1714
1715 #[test]
1716 fn test_validation_result_with_errors() {
1717 let result = ValidationResult {
1718 valid: false,
1719 errors: vec![ValidationError {
1720 field: "method".to_string(),
1721 message: "Invalid HTTP method".to_string(),
1722 }],
1723 warnings: vec![],
1724 };
1725 assert!(!result.valid);
1726 assert_eq!(result.errors.len(), 1);
1727 }
1728
1729 #[test]
1730 fn test_validation_result_with_warnings() {
1731 let result = ValidationResult {
1732 valid: true,
1733 errors: vec![],
1734 warnings: vec!["High error rate configured".to_string()],
1735 };
1736 assert!(result.valid);
1737 assert_eq!(result.warnings.len(), 1);
1738 }
1739
1740 #[test]
1743 fn test_validation_error_creation() {
1744 let error = ValidationError {
1745 field: "path".to_string(),
1746 message: "Path must start with /".to_string(),
1747 };
1748 assert_eq!(error.field, "path");
1749 assert!(error.message.contains('/'));
1750 }
1751
1752 #[test]
1753 fn test_validation_error_clone() {
1754 let error = ValidationError {
1755 field: "status".to_string(),
1756 message: "Invalid status code".to_string(),
1757 };
1758 let cloned = error.clone();
1759 assert_eq!(error.field, cloned.field);
1760 assert_eq!(error.message, cloned.message);
1761 }
1762
1763 #[test]
1766 fn test_ui_builder_state_creation() {
1767 let config = ServerConfig::default();
1768 let state = UIBuilderState::new(config);
1769 let _ = state;
1771 }
1772
1773 #[test]
1774 fn test_ui_builder_state_clone() {
1775 let config = ServerConfig::default();
1776 let state = UIBuilderState::new(config);
1777 let cloned = state.clone();
1778 let _ = (state, cloned);
1780 }
1781
1782 #[test]
1785 fn test_endpoint_serialization() {
1786 let endpoint = EndpointConfig {
1787 id: "test-1".to_string(),
1788 protocol: Protocol::Http,
1789 name: "Test Endpoint".to_string(),
1790 description: Some("A test endpoint".to_string()),
1791 enabled: true,
1792 config: EndpointProtocolConfig::Http(HttpEndpointConfig {
1793 method: "GET".to_string(),
1794 path: "/test".to_string(),
1795 request: None,
1796 response: HttpResponseConfig {
1797 status: 200,
1798 headers: None,
1799 body: ResponseBody::Static {
1800 content: serde_json::json!({"message": "Hello"}),
1801 },
1802 },
1803 behavior: None,
1804 }),
1805 };
1806
1807 let json = serde_json::to_string(&endpoint).unwrap();
1808 let deserialized: EndpointConfig = serde_json::from_str(&json).unwrap();
1809
1810 assert_eq!(endpoint.id, deserialized.id);
1811 assert_eq!(endpoint.protocol, deserialized.protocol);
1812 }
1813
1814 #[test]
1815 fn test_endpoint_config_disabled() {
1816 let endpoint = EndpointConfig {
1817 id: "disabled-1".to_string(),
1818 protocol: Protocol::Http,
1819 name: "Disabled Endpoint".to_string(),
1820 description: None,
1821 enabled: false,
1822 config: EndpointProtocolConfig::Http(HttpEndpointConfig {
1823 method: "GET".to_string(),
1824 path: "/disabled".to_string(),
1825 request: None,
1826 response: HttpResponseConfig {
1827 status: 200,
1828 headers: None,
1829 body: ResponseBody::Static {
1830 content: serde_json::json!({}),
1831 },
1832 },
1833 behavior: None,
1834 }),
1835 };
1836 assert!(!endpoint.enabled);
1837 assert!(endpoint.description.is_none());
1838 }
1839
1840 #[test]
1841 fn test_endpoint_config_grpc() {
1842 let endpoint = EndpointConfig {
1843 id: "grpc-1".to_string(),
1844 protocol: Protocol::Grpc,
1845 name: "gRPC Endpoint".to_string(),
1846 description: Some("A gRPC endpoint".to_string()),
1847 enabled: true,
1848 config: EndpointProtocolConfig::Grpc(GrpcEndpointConfig {
1849 service: "test.Service".to_string(),
1850 method: "Call".to_string(),
1851 proto_file: "/test.proto".to_string(),
1852 request_type: "Request".to_string(),
1853 response_type: "Response".to_string(),
1854 response: GrpcResponseConfig {
1855 body: ResponseBody::Static {
1856 content: serde_json::json!({}),
1857 },
1858 metadata: None,
1859 },
1860 behavior: None,
1861 }),
1862 };
1863 assert_eq!(endpoint.protocol, Protocol::Grpc);
1864 }
1865
1866 #[test]
1867 fn test_endpoint_config_websocket() {
1868 let endpoint = EndpointConfig {
1869 id: "ws-1".to_string(),
1870 protocol: Protocol::Websocket,
1871 name: "WebSocket Endpoint".to_string(),
1872 description: None,
1873 enabled: true,
1874 config: EndpointProtocolConfig::Websocket(WebsocketEndpointConfig {
1875 path: "/ws".to_string(),
1876 on_connect: None,
1877 on_message: Some(WebsocketAction::Echo),
1878 on_disconnect: None,
1879 behavior: None,
1880 }),
1881 };
1882 assert_eq!(endpoint.protocol, Protocol::Websocket);
1883 }
1884
1885 #[test]
1886 fn test_validation() {
1887 let endpoint = EndpointConfig {
1889 id: "test-1".to_string(),
1890 protocol: Protocol::Http,
1891 name: "Test".to_string(),
1892 description: None,
1893 enabled: true,
1894 config: EndpointProtocolConfig::Http(HttpEndpointConfig {
1895 method: "INVALID".to_string(),
1896 path: "/test".to_string(),
1897 request: None,
1898 response: HttpResponseConfig {
1899 status: 200,
1900 headers: None,
1901 body: ResponseBody::Static {
1902 content: serde_json::json!({}),
1903 },
1904 },
1905 behavior: None,
1906 }),
1907 };
1908
1909 assert_eq!(endpoint.protocol, Protocol::Http);
1911 }
1912
1913 #[test]
1916 fn test_config_format_yaml_deserialization() {
1917 let json = r#"{"config": "test", "format": "yaml"}"#;
1918 let request: ImportRequest = serde_json::from_str(json).unwrap();
1919 assert!(matches!(request.format, ConfigFormat::Yaml));
1920 }
1921
1922 #[test]
1923 fn test_config_format_json_deserialization() {
1924 let json = r#"{"config": "test", "format": "json"}"#;
1925 let request: ImportRequest = serde_json::from_str(json).unwrap();
1926 assert!(matches!(request.format, ConfigFormat::Json));
1927 }
1928
1929 #[tokio::test]
1932 async fn test_list_endpoints_empty() {
1933 let config = ServerConfig::default();
1934 let state = UIBuilderState::new(config);
1935 let result = list_endpoints(State(state)).await;
1936 let response = result.0;
1937 assert_eq!(response["total"], 0);
1938 assert_eq!(response["enabled"], 0);
1939 }
1940
1941 #[tokio::test]
1942 async fn test_create_and_get_endpoint() {
1943 let config = ServerConfig::default();
1944 let state = UIBuilderState::new(config);
1945
1946 let endpoint = create_test_http_endpoint();
1947 let create_result = create_endpoint(State(state.clone()), Json(endpoint.clone())).await;
1948 assert!(create_result.is_ok());
1949
1950 let get_result = get_endpoint(State(state), Path("test-1".to_string())).await;
1951 assert!(get_result.is_ok());
1952 assert_eq!(get_result.unwrap().0.id, "test-1");
1953 }
1954
1955 #[tokio::test]
1956 async fn test_get_endpoint_not_found() {
1957 let config = ServerConfig::default();
1958 let state = UIBuilderState::new(config);
1959
1960 let result = get_endpoint(State(state), Path("nonexistent".to_string())).await;
1961 assert!(result.is_err());
1962 assert_eq!(result.err().unwrap(), StatusCode::NOT_FOUND);
1963 }
1964
1965 #[tokio::test]
1966 async fn test_create_endpoint_duplicate_id() {
1967 let config = ServerConfig::default();
1968 let state = UIBuilderState::new(config);
1969
1970 let endpoint = create_test_http_endpoint();
1971 let _ = create_endpoint(State(state.clone()), Json(endpoint.clone())).await;
1972
1973 let result = create_endpoint(State(state), Json(endpoint)).await;
1975 assert!(result.is_err());
1976 assert_eq!(result.err().unwrap(), StatusCode::CONFLICT);
1977 }
1978
1979 #[tokio::test]
1980 async fn test_create_endpoint_auto_generate_id() {
1981 let config = ServerConfig::default();
1982 let state = UIBuilderState::new(config);
1983
1984 let mut endpoint = create_test_http_endpoint();
1985 endpoint.id = String::new(); let result = create_endpoint(State(state), Json(endpoint)).await;
1988 assert!(result.is_ok());
1989 let created = result.unwrap().0;
1990 assert!(!created.id.is_empty());
1991 }
1992
1993 #[tokio::test]
1994 async fn test_update_endpoint() {
1995 let config = ServerConfig::default();
1996 let state = UIBuilderState::new(config);
1997
1998 let endpoint = create_test_http_endpoint();
1999 let _ = create_endpoint(State(state.clone()), Json(endpoint.clone())).await;
2000
2001 let mut updated = endpoint.clone();
2002 updated.name = "Updated Name".to_string();
2003
2004 let result = update_endpoint(State(state), Path("test-1".to_string()), Json(updated)).await;
2005 assert!(result.is_ok());
2006 assert_eq!(result.unwrap().0.name, "Updated Name");
2007 }
2008
2009 #[tokio::test]
2010 async fn test_update_endpoint_not_found() {
2011 let config = ServerConfig::default();
2012 let state = UIBuilderState::new(config);
2013
2014 let endpoint = create_test_http_endpoint();
2015 let result =
2016 update_endpoint(State(state), Path("nonexistent".to_string()), Json(endpoint)).await;
2017 assert!(result.is_err());
2018 assert_eq!(result.err().unwrap(), StatusCode::NOT_FOUND);
2019 }
2020
2021 #[tokio::test]
2022 async fn test_delete_endpoint() {
2023 let config = ServerConfig::default();
2024 let state = UIBuilderState::new(config);
2025
2026 let endpoint = create_test_http_endpoint();
2027 let _ = create_endpoint(State(state.clone()), Json(endpoint)).await;
2028
2029 let result = delete_endpoint(State(state), Path("test-1".to_string())).await;
2030 assert!(result.is_ok());
2031 assert_eq!(result.unwrap(), StatusCode::NO_CONTENT);
2032 }
2033
2034 #[tokio::test]
2035 async fn test_delete_endpoint_not_found() {
2036 let config = ServerConfig::default();
2037 let state = UIBuilderState::new(config);
2038
2039 let result = delete_endpoint(State(state), Path("nonexistent".to_string())).await;
2040 assert!(result.is_err());
2041 assert_eq!(result.err().unwrap(), StatusCode::NOT_FOUND);
2042 }
2043
2044 #[tokio::test]
2045 async fn test_validate_endpoint_valid_http() {
2046 let config = ServerConfig::default();
2047 let state = UIBuilderState::new(config);
2048
2049 let endpoint = create_test_http_endpoint();
2050 let result = validate_endpoint(State(state), Json(endpoint)).await;
2051 assert!(result.0.valid);
2052 assert!(result.0.errors.is_empty());
2053 }
2054
2055 #[tokio::test]
2056 async fn test_validate_endpoint_invalid_method() {
2057 let config = ServerConfig::default();
2058 let state = UIBuilderState::new(config);
2059
2060 let mut endpoint = create_test_http_endpoint();
2061 if let EndpointProtocolConfig::Http(ref mut http_config) = endpoint.config {
2062 http_config.method = "INVALID".to_string();
2063 }
2064
2065 let result = validate_endpoint(State(state), Json(endpoint)).await;
2066 assert!(!result.0.valid);
2067 assert!(!result.0.errors.is_empty());
2068 }
2069
2070 #[tokio::test]
2071 async fn test_validate_endpoint_invalid_path() {
2072 let config = ServerConfig::default();
2073 let state = UIBuilderState::new(config);
2074
2075 let mut endpoint = create_test_http_endpoint();
2076 if let EndpointProtocolConfig::Http(ref mut http_config) = endpoint.config {
2077 http_config.path = "no-leading-slash".to_string();
2078 }
2079
2080 let result = validate_endpoint(State(state), Json(endpoint)).await;
2081 assert!(!result.0.valid);
2082 }
2083
2084 #[tokio::test]
2085 async fn test_validate_endpoint_invalid_status() {
2086 let config = ServerConfig::default();
2087 let state = UIBuilderState::new(config);
2088
2089 let mut endpoint = create_test_http_endpoint();
2090 if let EndpointProtocolConfig::Http(ref mut http_config) = endpoint.config {
2091 http_config.response.status = 999; }
2093
2094 let result = validate_endpoint(State(state), Json(endpoint)).await;
2095 assert!(!result.0.valid);
2096 }
2097
2098 #[tokio::test]
2099 async fn test_validate_endpoint_high_error_rate_warning() {
2100 let config = ServerConfig::default();
2101 let state = UIBuilderState::new(config);
2102
2103 let mut endpoint = create_test_http_endpoint();
2104 if let EndpointProtocolConfig::Http(ref mut http_config) = endpoint.config {
2105 http_config.behavior = Some(EndpointBehavior {
2106 latency: None,
2107 failure: Some(FailureConfig {
2108 error_rate: 0.75,
2109 status_codes: vec![500],
2110 error_message: None,
2111 }),
2112 traffic_shaping: None,
2113 });
2114 }
2115
2116 let result = validate_endpoint(State(state), Json(endpoint)).await;
2117 assert!(result.0.valid); assert!(!result.0.warnings.is_empty());
2119 }
2120
2121 #[tokio::test]
2122 async fn test_validate_grpc_endpoint_empty_service() {
2123 let config = ServerConfig::default();
2124 let state = UIBuilderState::new(config);
2125
2126 let endpoint = EndpointConfig {
2127 id: "grpc-test".to_string(),
2128 protocol: Protocol::Grpc,
2129 name: "gRPC Test".to_string(),
2130 description: None,
2131 enabled: true,
2132 config: EndpointProtocolConfig::Grpc(GrpcEndpointConfig {
2133 service: String::new(), method: "Method".to_string(),
2135 proto_file: "/test.proto".to_string(),
2136 request_type: "Request".to_string(),
2137 response_type: "Response".to_string(),
2138 response: GrpcResponseConfig {
2139 body: ResponseBody::Static {
2140 content: serde_json::json!({}),
2141 },
2142 metadata: None,
2143 },
2144 behavior: None,
2145 }),
2146 };
2147
2148 let result = validate_endpoint(State(state), Json(endpoint)).await;
2149 assert!(!result.0.valid);
2150 }
2151
2152 #[tokio::test]
2153 async fn test_validate_websocket_endpoint_invalid_path() {
2154 let config = ServerConfig::default();
2155 let state = UIBuilderState::new(config);
2156
2157 let endpoint = EndpointConfig {
2158 id: "ws-test".to_string(),
2159 protocol: Protocol::Websocket,
2160 name: "WebSocket Test".to_string(),
2161 description: None,
2162 enabled: true,
2163 config: EndpointProtocolConfig::Websocket(WebsocketEndpointConfig {
2164 path: "no-slash".to_string(), on_connect: None,
2166 on_message: None,
2167 on_disconnect: None,
2168 behavior: None,
2169 }),
2170 };
2171
2172 let result = validate_endpoint(State(state), Json(endpoint)).await;
2173 assert!(!result.0.valid);
2174 }
2175
2176 #[tokio::test]
2177 async fn test_get_config() {
2178 let config = ServerConfig::default();
2179 let state = UIBuilderState::new(config);
2180
2181 let result = get_config(State(state)).await;
2182 let _ = result.0;
2184 }
2185
2186 #[tokio::test]
2187 async fn test_update_config() {
2188 let config = ServerConfig::default();
2189 let state = UIBuilderState::new(config.clone());
2190
2191 let result = update_config(State(state), Json(config)).await;
2192 assert!(result.is_ok());
2193 }
2194
2195 #[tokio::test]
2196 async fn test_export_config() {
2197 let config = ServerConfig::default();
2198 let state = UIBuilderState::new(config);
2199
2200 let result = export_config(State(state)).await;
2201 assert!(result.is_ok());
2202 }
2203
2204 #[tokio::test]
2205 async fn test_list_endpoints_with_multiple_protocols() {
2206 let config = ServerConfig::default();
2207 let state = UIBuilderState::new(config);
2208
2209 let http_endpoint = create_test_http_endpoint();
2211 let _ = create_endpoint(State(state.clone()), Json(http_endpoint)).await;
2212
2213 let ws_endpoint = EndpointConfig {
2215 id: "ws-1".to_string(),
2216 protocol: Protocol::Websocket,
2217 name: "WS Endpoint".to_string(),
2218 description: None,
2219 enabled: true,
2220 config: EndpointProtocolConfig::Websocket(WebsocketEndpointConfig {
2221 path: "/ws".to_string(),
2222 on_connect: None,
2223 on_message: Some(WebsocketAction::Echo),
2224 on_disconnect: None,
2225 behavior: None,
2226 }),
2227 };
2228 let _ = create_endpoint(State(state.clone()), Json(ws_endpoint)).await;
2229
2230 let result = list_endpoints(State(state)).await;
2231 let response = result.0;
2232 assert_eq!(response["total"], 2);
2233 assert_eq!(response["by_protocol"]["http"], 1);
2234 assert_eq!(response["by_protocol"]["websocket"], 1);
2235 }
2236
2237 #[test]
2240 fn test_create_ui_builder_router() {
2241 let config = ServerConfig::default();
2242 let state = UIBuilderState::new(config);
2243 let router = create_ui_builder_router(state);
2244 let _ = router;
2246 }
2247}